# Roteiro Replicação Slony - Fiscal S1 > Documentação do roteiro de replicação do sistema Fiscal S1 para o Atende.Net já considerando a versão 16 do PostgreSQL em produção nos clientes. > > Esse documento é referente a configuração Inicial do Slony. Caso deseje apenas adicionar uma nova replicação ou remover uma já existente, siga a documentação listada abaixo nos processos adicionais! > > Caso deseje ativar a replicação entre o Master 11.5 e Slave 16, deve-se utilizar esta documentação: [`Replicação Nativa - Fiscal S-1`](../docs/replicacao_nativa_fiscal_s1.md) ## Processos Adicionais > Os links abaixos são referentes a adição ou remoção de um node. [`1 - Adicionando Node`](../docs/replicacao_slonyfiscal_s1_adicionar_node.md) [`2 - Remover Node`](../docs/replicacao_slonyfiscal_s1_remover_node.md) [`3 - Remover Estrutura Slony`](../docs/replicacao_slonyfiscal_s1_remover_estrutura_node.md) ### Abaixo a representação do funcionamento da estrutura: ![Diagrama_Funcionamento_Replicacao](../img/diagrama_replicacao_postgres16.png) ## Destino - Banco Fiscal Slony (00002_www.nfs-e.net_SGDB_1150_SLONY - 192.168.23.11) Checar se o Slony esta instalado e executando: ``` rpm -qi slony1_11 ``` Caso receba o retorno que a aplicação não está instalada , execute a instalação conforme os passos abaixo: ``` cd /root/ wget https://datacenter.atende.net/updates/slony1_11.zip unzip slony1_11.zip cd slony rpm -Uvh slony1_11-2.2.10-1.rhel8.x86_64.rpm perl-Switch-2.17-10.el8.noarch.rpm perl-DBD-Pg-3.7.4-4.module_el8.3.0+426+0b4e9c0a.x86_64.rpm perl-Text-Balanced-2.03-395.el8.noarch.rpm perl-Filter-1.58-2.el8.x86_64.rpm perl-SelfLoader-1.23-416.el8.noarch.rpm ``` Verificar se o IP do banco Master do Fiscal S1 esta liberado no pg_hba.conf ``` vim /var/lib/pgsql/11/data/pg_hba.conf Ex.: host all all 192.168.23.10/32 trust (Libera Fiscal S1) Ex: host all all 172.16.XXX.XXX/32 trust (IP INT_RT do Cliente) systemctl reload postgresql-11.service ``` Conferir as configurações do postgresql.conf (/var/lib/pgsql/11/data/postgresql.conf) ``` wal_level = logical max_wal_senders = 3 max_replication_slots = 3 max_sync_workers_per_subscription = 2 ``` Conferir se o usuário replicação existe no Banco de Dados (via pgAdmin): ``` login roles > replicacao Caso não existe, crie o mesmo: CREATE ROLE replicacao WITH LOGIN SUPERUSER INHERIT REPLICATION CONNECTION LIMIT -1 PASSWORD '123456'; ``` Efetue a criação do database conforme o cliente que você esta ativando a replicação via Slony ``` Exemplo: 00000_cliente CREATE DATABASE "00000_cliente" WITH OWNER = dbaipm ENCODING = 'LATIN1' TABLESPACE = pg_default LC_COLLATE = 'pt_BR' LC_CTYPE = 'pt_BR' CONNECTION LIMIT = -1; ``` Setar os defaults privilégios para o Data Base (substituir o nome 00000_cliente pelo código e nome do cliente): ``` GRANT ALL ON DATABASE "00000_cliente" TO dbaipm; GRANT ALL ON DATABASE "00000_cliente" TO administradores; GRANT CONNECT ON DATABASE "00000_cliente" TO consultas; GRANT ALL ON DATABASE "00000_cliente" TO migradores; GRANT ALL ON DATABASE "00000_cliente" TO sistema; REVOKE ALL ON DATABASE "00000_cliente" FROM public; ALTER DEFAULT PRIVILEGES GRANT INSERT, SELECT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON TABLES TO administradores WITH GRANT OPTION; ALTER DEFAULT PRIVILEGES GRANT SELECT ON TABLES TO consultas WITH GRANT OPTION; ALTER DEFAULT PRIVILEGES GRANT INSERT, SELECT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON TABLES TO migradores WITH GRANT OPTION; ALTER DEFAULT PRIVILEGES GRANT INSERT, SELECT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON TABLES TO sistema WITH GRANT OPTION; ALTER DEFAULT PRIVILEGES GRANT INSERT, SELECT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON TABLES TO atualizacao; ALTER DEFAULT PRIVILEGES GRANT SELECT, UPDATE, USAGE ON SEQUENCES TO administradores WITH GRANT OPTION; ALTER DEFAULT PRIVILEGES GRANT USAGE ON SEQUENCES TO consultas WITH GRANT OPTION; ALTER DEFAULT PRIVILEGES GRANT SELECT, UPDATE, USAGE ON SEQUENCES TO migradores WITH GRANT OPTION; ALTER DEFAULT PRIVILEGES GRANT SELECT, UPDATE, USAGE ON SEQUENCES TO sistema WITH GRANT OPTION; ALTER DEFAULT PRIVILEGES GRANT SELECT, UPDATE, USAGE ON SEQUENCES TO atendenetweb; ALTER DEFAULT PRIVILEGES GRANT EXECUTE ON FUNCTIONS TO public; ALTER DEFAULT PRIVILEGES GRANT EXECUTE ON FUNCTIONS TO administradores WITH GRANT OPTION; ALTER DEFAULT PRIVILEGES GRANT EXECUTE ON FUNCTIONS TO consultas WITH GRANT OPTION; ALTER DEFAULT PRIVILEGES GRANT EXECUTE ON FUNCTIONS TO migradores WITH GRANT OPTION; ALTER DEFAULT PRIVILEGES GRANT EXECUTE ON FUNCTIONS TO sistema WITH GRANT OPTION; ALTER DEFAULT PRIVILEGES GRANT EXECUTE ON FUNCTIONS TO dbaipm; ``` Criar as extensões ``` CREATE EXTENSION pg_buffercache; CREATE EXTENSION pg_freespacemap; CREATE EXTENSION pg_stat_statements; CREATE EXTENSION btree_gist; CREATE EXTENSION postgis; CREATE EXTENSION postgis_sfcgal; ``` Execute o SQL para criação da estrutura (schema replicacao) no SGBD de destino na database criada no passo anterior: ```ruby --drop SCHEMA replicacao cascade; CREATE SCHEMA replicacao; CREATE SEQUENCE replicacao.sqttblog INCREMENT 1 START 1 MINVALUE 0 MAXVALUE 9223372036854775807 CACHE 1; CREATE TABLE replicacao.tblog ( logcodigo bigint NOT NULL DEFAULT nextval('replicacao.sqttblog'::regclass), logdatahora timestamp not null default now(), logip varchar(30) not null default inet_client_addr(), logpid bigint not null default pg_backend_pid(), logacao text not null, logquery text not null default current_query() ); COMMENT ON COLUMN replicacao.tblog.logcodigo IS 'Código do log'; COMMENT ON COLUMN replicacao.tblog.logdatahora IS 'Datahora da ocorrencia'; COMMENT ON COLUMN replicacao.tblog.logip IS 'Ip do client'; COMMENT ON COLUMN replicacao.tblog.logpid IS 'Pid da conexao'; COMMENT ON COLUMN replicacao.tblog.logacao IS 'Acao sendo executada'; COMMENT ON COLUMN replicacao.tblog.logquery IS 'Query'; ALTER TABLE replicacao.tblog ADD CONSTRAINT pk_tblog PRIMARY KEY(logcodigo); CREATE SEQUENCE replicacao.sqttbddl INCREMENT 1 START 1 MINVALUE 0 MAXVALUE 9223372036854775807 CACHE 1; CREATE TABLE replicacao.tbddl ( ddlcodigo bigint NOT NULL DEFAULT nextval('replicacao.sqttbddl'::regclass), ddlcomando text COLLATE pg_catalog."default" NOT NULL, ddldatahora timestamp default now(), CONSTRAINT pk_tbddl PRIMARY KEY (ddlcodigo) ); --Altera o tablespace das tabelas do IPM Fiscal create or replace function replicacao.fn_altera_tablespace() returns boolean as $body$ declare r_objeto record; begin --Tabelas for r_objeto in (select 'alter table "'||nspname||'"."'||pg_class.relname||'" set tablespace replicacao;' as cmd, '"'||nspname||'"."'||pg_class.relname||'"' as nome from pg_namespace left join pg_class on pg_class.relnamespace = pg_namespace.oid where translate(pg_namespace.nspname, '1234567890', '') in ('dcarq', '_fiarq', '_gtarq', '_isarq', '_nfarq', '_spunico', '_wgt', '_wnp', '_wsn', 'saudeweb', 'saudewebglo', 'spunico') and pg_class.relkind = 'r' and pg_class.reltablespace <> (select oid from pg_tablespace where spcname = 'replicacao') order by pg_namespace.nspname, pg_class.relname) loop insert into replicacao.tblog (logacao, logquery) values ('Alterando tablespace da tabela '||r_objeto.nome, r_objeto.cmd); raise notice 'Alterando tablespace da tabela %.', r_objeto.nome; execute r_objeto.cmd; end loop; --Índices for r_objeto in (select 'alter index "'||nspname||'"."'||pg_class.relname||'" set tablespace replicacao;' as cmd, '"'||nspname||'"."'||pg_class.relname||'"' as nome from pg_namespace left join pg_class on pg_class.relnamespace = pg_namespace.oid where translate(pg_namespace.nspname, '1234567890', '') in ('dcarq', '_fiarq', '_gtarq', '_isarq', '_nfarq', '_spunico', '_wgt', '_wnp', '_wsn', 'saudeweb', 'saudewebglo', 'spunico') and pg_class.relkind = 'i' and pg_class.reltablespace <> (select oid from pg_tablespace where spcname = 'replicacao') order by pg_namespace.nspname, pg_class.relname) loop insert into replicacao.tblog (logacao, logquery) values ('Alterando tablespace da tabela '||r_objeto.nome, r_objeto.cmd); raise notice 'Alterando tablespace do índice %.', r_objeto.nome; execute r_objeto.cmd; end loop; return true; end; $body$ LANGUAGE plpgsql VOLATILE; create or replace function replicacao.fn_remove_relacao_atendenet() returns boolean as $body$ declare r_fk record; begin for r_fk in (select *, 'alter table "'||schema_filha||'"."'||tabela_filha||'" drop constraint "'||conname||'";' as cmd from (SELECT pg_constraint.conname, schema_pai.nspname as schema_pai, tabela_pai.relname as tabela_pai, schema_filha.nspname as schema_filha, tabela_filha.relname as tabela_filha FROM pg_constraint INNER JOIN pg_class tabela_filha ON tabela_filha.oid = pg_constraint.conrelid INNER JOIN pg_namespace schema_filha ON schema_filha.oid = tabela_filha.relnamespace INNER JOIN pg_class tabela_pai ON tabela_pai.oid = pg_constraint.confrelid INNER JOIN pg_namespace schema_pai ON schema_pai.oid = tabela_pai.relnamespace where pg_constraint.contype = 'f' and (translate(substr(schema_pai.nspname,1,1),'1234567890','') = '' or translate(substr(schema_filha.nspname,1,1),'1234567890','') = '' or schema_pai.nspname in ('saudeweb', 'saudewebglo', 'spunico') or schema_filha.nspname in ('saudeweb', 'saudewebglo', 'spunico')) and pg_constraint.confupdtype <> 'c') orig where translate(schema_pai, '1234567890', '') not in ('dcarq', '_fiarq', '_gtarq', '_isarq', '_nfarq', '_spunico', '_wgt', '_wnp', '_wsn', 'saudeweb', 'saudewebglo', 'spunico') or translate(schema_filha, '1234567890', '') not in ('dcarq', '_fiarq', '_gtarq', '_isarq', '_nfarq', '_spunico', '_wgt', '_wnp', '_wsn', 'saudeweb', 'saudewebglo', 'spunico')) loop insert into replicacao.tblog (logacao, logquery) values ('Apagando a FK "'||r_fk.conname||'" da tabela "'||r_fk.schema_filha||'"."'||r_fk.tabela_filha||'".', r_fk.cmd); raise notice 'Apagando a FK "%" da tabela "%".', r_fk.conname, r_fk.schema_filha||'"."'||r_fk.tabela_filha; execute r_fk.cmd; end loop; return true; end; $body$ LANGUAGE plpgsql VOLATILE; ``` Verificar se o disco de 1,2 TB esta adicionado no Destino: ``` Disco: /var/lib/pgsql/11/tablespaces/replicacao [root@00002-NFSE_SGDB_1150_SLONY ~]# df -h Sist. Arq. Tam. Usado Disp. Uso% Montado em devtmpfs 8,8G 0 8,8G 0% /dev tmpfs 8,8G 12K 8,8G 1% /dev/shm tmpfs 8,8G 8,8M 8,8G 1% /run tmpfs 8,8G 0 8,8G 0% /sys/fs/cgroup /dev/mapper/cl_00000--clean--centos8-root 15G 4,9G 11G 33% / /dev/mapper/cl_00000--clean--centos8-home 2,4G 50M 2,4G 3% /home /dev/sda2 976M 179M 731M 20% /boot /dev/mapper/sgdb04-dados 119G 921M 119G 1% /var/lib/pgsql/11 /dev/sda1 599M 6,7M 593M 2% /boot/efi /dev/mapper/sgdb01-pg_log 19G 168M 19G 1% /var/lib/pgsql/11/data/log /dev/mapper/sgdb03-indices 89G 668M 89G 1% /var/lib/pgsql/11/tablespaces/indice /dev/mapper/sgdb02-pg_wal 29G 272M 29G 1% /var/lib/pgsql/11/data/pg_wal /dev/mapper/cl_00000--clean--centos8-var_log 15G 347M 15G 3% /var/log tmpfs 1,8G 0 1,8G 0% /run/user/0 /dev/mapper/sgdb10-replicacao 1,3T 8,7G 1,2T 1% /var/lib/pgsql/11/tablespaces/replicacao [root@00002-NFSE_SGDB_1150_SLONY ~]# ``` IMPORTANTE: EXECUTAR O PASSO ABAIXO APENAS SE O DISCO NÃO EXISTIR NO SERVIDOR LINUX E O TABLESPACE REPLICACAO NÃO EXISTA NO BANCO DE DADOS DE DESTINO! Deve-se adicionar um HD temporário ao servidor SLAVE (destino) do banco de dados ao qual receberá o tablespace replicacao. Como sugestão, adicionar o HD com tamanho de 1234GB, no formato Thin Provision com a controladora SCSI Controller 1, desta forma o HD crescerá somente o necessário e será fácil de identificá-lo para posterior remoção. ``` lsblk fdisk /dev/sde #Dispositivo Inicializar Início Fim Setores Tamanho Id Tipo #/dev/sde1 2048 2587885567 2587883520 1,2T 8e Linux LVM vgcreate sgdb10 /dev/sde1 lvcreate -L +1233G -n replicacao sgdb10 mkfs.xfs /dev/mapper/sgdb10-replicacao vim /etc/fstab /dev/mapper/sgdb10-replicacao /var/lib/pgsql/11/tablespaces/replicacao xfs defaults 0 1 mkdir -p /var/lib/pgsql/11/tablespaces/replicacao mount /var/lib/pgsql/11/tablespaces/replicacao chown postgres.postgres /var/lib/pgsql/11/tablespaces/replicacao/ -R chmod 700 /var/lib/pgsql/11/tablespaces/replicacao/ -R ``` Criar tablespace replicacao: ```ruby CREATE TABLESPACE replicacao OWNER postgres LOCATION '/var/lib/pgsql/11/tablespaces/replicacao'; ``` ## Destino - Banco Cliente Versão 16 (00000-clean-bdp-01-1600.atende.net) No banco do cliente (PostgreSQL16), criar um disco para a replicação do Fiscal: >Deve-se adicionar um HD temporário ao servidor SLAVE (destino) do banco de dados ao qual receberá o tablespace replicacao. >Como sugestão, adicionar o HD com tamanho de 1234GB, no formato Thin Provision com a controladora SCSI Controller 1, desta forma o HD >crescerá somente o necessário e será fácil de identificá-lo para posterior remoção. ``` su - root Criando pasta: mkdir -p /var/lib/postgresql/16/tablespaces/replicacao Adicionando disco: lsblk pvcreate /dev/sde vgcreate postgresql-vg10 /dev/sde lvcreate -l +100%FREE -n lv-replicacao postgresql-vg10 mkfs.xfs /dev/mapper/postgresql--vg10-lv--replicacao blkid | grep replicacao blkid | grep replicacao | grep "0cbfc3a7-07d2-483e-885a-73936788b157" ls -l /dev/disk/by-id/ | grep dm-7 #Retorno: dm-uuid-LVM-My295Ba6z7Vl5TX7eyue3b5bCCOtX1NfqddL0ZoIJLfOjD0N9deI8yySYvvRff3h -> ../../dm-7 vim /etc/fstab # disco replicacao slony /dev/disk/by-id/dm-uuid-LVM-My295Ba6z7Vl5TX7eyue3b5bCCOtX1NfqddL0ZoIJLfOjD0N9deI8yySYvvRff3h /var/lib/postgresql/16/tablespaces/replicacao xfs defaults,noatime,nodiratime 0 1 Aplicando permissão para o diretório: chown postgres.postgres /var/lib/postgresql/16/tablespaces/replicacao ``` Verificar se o IP do banco Master do Fiscal S1 esta liberado no pg_hba.conf ``` vim /etc/postgresql/16/main/pg_hba.conf Ex.: host all all 172.16.1.18/32 trust (Libera Fiscal S1) systemctl reload postgresql.service ``` Conferir configurações no arquivo postgresql.conf: ``` cd /etc/postgresql/16/main/ cp postgresql.conf postgresql.bkp-antesslony vim postgresql.conf wal_level = logical max_wal_senders = 3 max_replication_slots = 3 max_sync_workers_per_subscription = 2 wal_receiver_status_interval = 60s wal_receiver_timeout = 300s OBSERVAÇÃO: Se o parametro estiver max_worker_processes = 3, deverá ser alterado para max_worker_processes = 4 ``` ATENÇÃO: O RESTART DO SERVIÇO DE BANCO DE DADOS DEVERÁ SER EXECUTADO APÓS AS 18H! (systemctl restart postgresql.service) Criar a tablespace replicacao para recebimento dos dados: ```ruby CREATE TABLESPACE replicacao OWNER postgres LOCATION '/var/lib/postgresql/16/tablespaces/replicacao'; ``` ```ruby --drop SCHEMA replicacao cascade; CREATE SCHEMA replicacao; CREATE SEQUENCE replicacao.sqttblog INCREMENT 1 START 1 MINVALUE 0 MAXVALUE 9223372036854775807 CACHE 1; CREATE TABLE replicacao.tblog ( logcodigo bigint NOT NULL DEFAULT nextval('replicacao.sqttblog'::regclass), logdatahora timestamp not null default now(), logip varchar(30) not null default inet_client_addr(), logpid bigint not null default pg_backend_pid(), logacao text not null, logquery text not null default current_query() ); COMMENT ON COLUMN replicacao.tblog.logcodigo IS 'Código do log'; COMMENT ON COLUMN replicacao.tblog.logdatahora IS 'Datahora da ocorrencia'; COMMENT ON COLUMN replicacao.tblog.logip IS 'Ip do client'; COMMENT ON COLUMN replicacao.tblog.logpid IS 'Pid da conexao'; COMMENT ON COLUMN replicacao.tblog.logacao IS 'Acao sendo executada'; COMMENT ON COLUMN replicacao.tblog.logquery IS 'Query'; ALTER TABLE replicacao.tblog ADD CONSTRAINT pk_tblog PRIMARY KEY(logcodigo); CREATE SEQUENCE replicacao.sqttbddl INCREMENT 1 START 1 MINVALUE 0 MAXVALUE 9223372036854775807 CACHE 1; CREATE TABLE replicacao.tbddl ( ddlcodigo bigint NOT NULL DEFAULT nextval('replicacao.sqttbddl'::regclass), ddlcomando text COLLATE pg_catalog."default" NOT NULL, ddldatahora timestamp default now(), CONSTRAINT pk_tbddl PRIMARY KEY (ddlcodigo) ); --Altera o tablespace das tabelas do IPM Fiscal create or replace function replicacao.fn_altera_tablespace() returns boolean as $body$ declare r_objeto record; begin --Tabelas for r_objeto in (select 'alter table "'||nspname||'"."'||pg_class.relname||'" set tablespace replicacao;' as cmd, '"'||nspname||'"."'||pg_class.relname||'"' as nome from pg_namespace left join pg_class on pg_class.relnamespace = pg_namespace.oid where translate(pg_namespace.nspname, '1234567890', '') in ('dcarq', '_fiarq', '_gtarq', '_isarq', '_nfarq', '_spunico', '_wgt', '_wnp', '_wsn', 'saudeweb', 'saudewebglo', 'spunico') and pg_class.relkind = 'r' and pg_class.reltablespace <> (select oid from pg_tablespace where spcname = 'replicacao') order by pg_namespace.nspname, pg_class.relname) loop insert into replicacao.tblog (logacao, logquery) values ('Alterando tablespace da tabela '||r_objeto.nome, r_objeto.cmd); raise notice 'Alterando tablespace da tabela %.', r_objeto.nome; execute r_objeto.cmd; end loop; --Índices for r_objeto in (select 'alter index "'||nspname||'"."'||pg_class.relname||'" set tablespace replicacao;' as cmd, '"'||nspname||'"."'||pg_class.relname||'"' as nome from pg_namespace left join pg_class on pg_class.relnamespace = pg_namespace.oid where translate(pg_namespace.nspname, '1234567890', '') in ('dcarq', '_fiarq', '_gtarq', '_isarq', '_nfarq', '_spunico', '_wgt', '_wnp', '_wsn', 'saudeweb', 'saudewebglo', 'spunico') and pg_class.relkind = 'i' and pg_class.reltablespace <> (select oid from pg_tablespace where spcname = 'replicacao') order by pg_namespace.nspname, pg_class.relname) loop insert into replicacao.tblog (logacao, logquery) values ('Alterando tablespace da tabela '||r_objeto.nome, r_objeto.cmd); raise notice 'Alterando tablespace do índice %.', r_objeto.nome; execute r_objeto.cmd; end loop; return true; end; $body$ LANGUAGE plpgsql VOLATILE; create or replace function replicacao.fn_remove_relacao_atendenet() returns boolean as $body$ declare r_fk record; begin for r_fk in (select *, 'alter table "'||schema_filha||'"."'||tabela_filha||'" drop constraint "'||conname||'";' as cmd from (SELECT pg_constraint.conname, schema_pai.nspname as schema_pai, tabela_pai.relname as tabela_pai, schema_filha.nspname as schema_filha, tabela_filha.relname as tabela_filha FROM pg_constraint INNER JOIN pg_class tabela_filha ON tabela_filha.oid = pg_constraint.conrelid INNER JOIN pg_namespace schema_filha ON schema_filha.oid = tabela_filha.relnamespace INNER JOIN pg_class tabela_pai ON tabela_pai.oid = pg_constraint.confrelid INNER JOIN pg_namespace schema_pai ON schema_pai.oid = tabela_pai.relnamespace where pg_constraint.contype = 'f' and (translate(substr(schema_pai.nspname,1,1),'1234567890','') = '' or translate(substr(schema_filha.nspname,1,1),'1234567890','') = '' or schema_pai.nspname in ('saudeweb', 'saudewebglo', 'spunico') or schema_filha.nspname in ('saudeweb', 'saudewebglo', 'spunico')) and pg_constraint.confupdtype <> 'c') orig where translate(schema_pai, '1234567890', '') not in ('dcarq', '_fiarq', '_gtarq', '_isarq', '_nfarq', '_spunico', '_wgt', '_wnp', '_wsn', 'saudeweb', 'saudewebglo', 'spunico') or translate(schema_filha, '1234567890', '') not in ('dcarq', '_fiarq', '_gtarq', '_isarq', '_nfarq', '_spunico', '_wgt', '_wnp', '_wsn', 'saudeweb', 'saudewebglo', 'spunico')) loop insert into replicacao.tblog (logacao, logquery) values ('Apagando a FK "'||r_fk.conname||'" da tabela "'||r_fk.schema_filha||'"."'||r_fk.tabela_filha||'".', r_fk.cmd); raise notice 'Apagando a FK "%" da tabela "%".', r_fk.conname, r_fk.schema_filha||'"."'||r_fk.tabela_filha; execute r_fk.cmd; end loop; return true; end; $body$ LANGUAGE plpgsql VOLATILE; ``` ## Origem - Banco Fiscal (00002_www.nfs-e.net_SGDB - 192.168.23.10) Altere os dados conforme os do cliente que irá fazer a replicação: ``` vim /usr/local/slony1/etc/slon_tools.conf $CLUSTER_NAME = 'replication_ipmfiscal'; add_node( node => 1, host => '192.168.23.10', dbname => '00002_ipmfiscal', port => 5432, user => 'postgres', password => '' ); add_node( node => 2, host => '192.168.23.11', dbname => '00000_teste', port => 5432, user => 'postgres', password => '' ); ``` Quando finalizada a configuração do Slave, deve-se prosseguir com o próximo passo: ``` /usr/local/slony1/bin/slonik_init_cluster | /usr/local/slony1/bin/slonik ``` Após, inicie o cluster: ``` /usr/local/slony1/bin/slon_start 1 /usr/local/slony1/bin/slon_start 2 ``` Atualize as funções no master: ```ruby CREATE SCHEMA replicacao; CREATE TABLE replicacao.tbclientenode ( clicodigo integer not null, nodcodigo integer not null ); ALTER TABLE replicacao.tbclientenode ADD CONSTRAINT pk_tbclientenode PRIMARY KEY(nodcodigo); CREATE UNIQUE INDEX idx_unique_tbclientenode_cliente ON replicacao.tbclientenode (clicodigo); CREATE SEQUENCE replicacao.sqttblog INCREMENT 1 START 1 MINVALUE 0 MAXVALUE 9223372036854775807 CACHE 1; CREATE TABLE replicacao.tblog ( logcodigo bigint NOT NULL DEFAULT nextval('replicacao.sqttblog'::regclass), logdatahora timestamp not null default now(), logip varchar(30) not null default inet_client_addr(), logpid bigint not null default pg_backend_pid(), logacao text not null, logquery text not null default current_query() ); COMMENT ON COLUMN replicacao.tblog.logcodigo IS 'Código do log'; COMMENT ON COLUMN replicacao.tblog.logdatahora IS 'Datahora da ocorrencia'; COMMENT ON COLUMN replicacao.tblog.logip IS 'Ip do client'; COMMENT ON COLUMN replicacao.tblog.logpid IS 'Pid da conexao'; COMMENT ON COLUMN replicacao.tblog.logacao IS 'Acao sendo executada'; COMMENT ON COLUMN replicacao.tblog.logquery IS 'Query'; ALTER TABLE replicacao.tblog ADD CONSTRAINT pk_tblog PRIMARY KEY(logcodigo); drop function if exists replicacao.fn_inicia_replicacao(s_contexto text, a_tabelas text[], i_clicodigo integer); create or replace function replicacao.fn_get_schema_slony() returns text as $body$ begin return (select schemaname from pg_tables where tablename = 'sl_node'); end; $body$ LANGUAGE plpgsql VOLATILE; create or replace function replicacao.fn_adiciona_schema_slony_search_path() returns boolean as $body$ declare s_search_path text; s_schema_name text; begin s_schema_name := (select replicacao.fn_get_schema_slony()); s_search_path := pg_catalog.current_setting('search_path'); s_search_path := s_search_path||', '||s_schema_name; PERFORM pg_catalog.set_config('search_path', s_search_path, true); return true; end; $body$ LANGUAGE plpgsql VOLATILE; create or replace function replicacao.fn_remove_schema_slony_search_path() returns boolean as $body$ declare s_search_path text; s_schema_name text; begin s_schema_name := (select replicacao.fn_get_schema_slony()); s_search_path := pg_catalog.current_setting('search_path'); s_search_path := replace(s_search_path, ', '||s_schema_name, ''); PERFORM pg_catalog.set_config('search_path', s_search_path, true); return true; end; $body$ LANGUAGE plpgsql VOLATILE; create or replace function replicacao.status() returns table(cliente integer, lagtime interval, lastevent bigint, lastreceived bigint, sincronizado boolean) as $body$ begin perform (select replicacao.fn_adiciona_schema_slony_search_path()); drop table if exists tbreturn; create temp table tbreturn as select tbclientenode.clicodigo as cliente, sl_status.st_lag_time as lagtime, sl_status.st_last_event as lastevent, sl_status.st_last_received as lastreceived, sl_status.st_last_event = sl_status.st_last_received as sincronizado from sl_status left join replicacao.tbclientenode on tbclientenode.nodcodigo = sl_status.st_received; perform (select replicacao.fn_remove_schema_slony_search_path()); return query(select * from tbreturn); end; $body$ LANGUAGE plpgsql VOLATILE; CREATE OR REPLACE FUNCTION replicacao.fn_get_node_by_clicodigo(i_clicodigo integer) RETURNS integer AS $BODY$ declare i_node integer; begin select nodcodigo into i_node from replicacao.tbclientenode where clicodigo = i_clicodigo; if (i_node is null) then raise exception 'Não foi possível encontrar um node de replicação para o cliente %.', i_clicodigo; end if; return i_node; end $BODY$ LANGUAGE plpgsql VOLATILE COST 100; CREATE OR REPLACE FUNCTION replicacao.fn_prepara_contexto( s_contexto text, a_tabelas text[]) RETURNS boolean AS $BODY$ declare i_set integer; s_pk text; r_tabela record; begin perform replicacao.fn_adiciona_schema_slony_search_path(); insert into replicacao.tblog (logacao) values ('fn_prepara_contexto'); lock table sl_event; if exists(select 1 from pg_event_trigger where evtname = 'tg_event_trigger_on_execute_ddl_replicacao_ipm') then ALTER EVENT TRIGGER tg_event_trigger_on_execute_ddl_replicacao_ipm DISABLE; end if; if exists(select 1 from sl_set where set_comment = s_contexto) then raise exception 'O contexto "%", já existe.', s_contexto; else select coalesce(max(set_id),0)+1 into i_set from sl_set; perform storeset(i_set, s_contexto); end if; for r_tabela in select unnest(a_tabelas) as nometabela loop if (position('webbased.' in lower(r_tabela.nometabela)) > 0) then raise exception 'A tabela "%", está relacionada ao schema webbased, o qual não deve ser replicado.', r_tabela.nometabela; end if; if (exists(select 1 from sl_table where tab_nspname||'.'||tab_relname = r_tabela.nometabela)) then raise exception 'A tabela "%", já está sendo replicada.', r_tabela.nometabela; end if; select indice.relname into s_pk from pg_class tabela left join pg_namespace on pg_namespace.oid = tabela.relnamespace left join pg_index on pg_index.indrelid = tabela.oid left join pg_class indice on indice.oid = pg_index.indexrelid where pg_namespace.nspname||'.'||tabela.relname = r_tabela.nometabela and pg_index.indisprimary; if coalesce(s_pk,'') = '' then raise exception 'A tabela "%", não possui chave primária.', r_tabela.nometabela; end if; perform setaddtable(i_set, (select coalesce(max(tab_id),0)+1 from sl_table), r_tabela.nometabela, s_pk, ''); end loop; if exists(select 1 from pg_event_trigger where evtname = 'tg_event_trigger_on_execute_ddl_replicacao_ipm') then ALTER EVENT TRIGGER tg_event_trigger_on_execute_ddl_replicacao_ipm ENABLE; end if; perform replicacao.fn_remove_schema_slony_search_path(); return true; end $BODY$ LANGUAGE plpgsql VOLATILE COST 100; CREATE OR REPLACE FUNCTION replicacao.fn_inicia_replicacao_contexto( s_contexto text, i_clicodigo integer) RETURNS boolean AS $BODY$ begin perform replicacao.fn_adiciona_schema_slony_search_path(); insert into replicacao.tblog (logacao) values ('fn_inicia_replicacao_contexto'); if exists(select 1 from pg_event_trigger where evtname = 'tg_event_trigger_on_execute_ddl_replicacao_ipm') then ALTER EVENT TRIGGER tg_event_trigger_on_execute_ddl_replicacao_ipm DISABLE; end if; perform replicacao.fn_adiciona_slave_replicacao(s_contexto, i_clicodigo); if exists(select 1 from pg_event_trigger where evtname = 'tg_event_trigger_on_execute_ddl_replicacao_ipm') then ALTER EVENT TRIGGER tg_event_trigger_on_execute_ddl_replicacao_ipm ENABLE; end if; perform replicacao.fn_remove_schema_slony_search_path(); return true; end $BODY$ LANGUAGE plpgsql VOLATILE COST 100; CREATE OR REPLACE FUNCTION replicacao.fn_interrompe_replicacao(s_contexto text) RETURNS boolean AS $BODY$ declare i_set integer; begin perform replicacao.fn_adiciona_schema_slony_search_path(); insert into replicacao.tblog (logacao) values ('fn_interrompe_replicacao('''||s_contexto||''')'); if not exists(select 1 from sl_set where set_comment = s_contexto) then raise exception 'O contexto "%", não existe.', s_contexto; else select set_id into i_set from sl_set where set_comment = s_contexto; end if; perform dropset(i_set); perform replicacao.fn_remove_schema_slony_search_path(); return true; end $BODY$ LANGUAGE plpgsql VOLATILE COST 100; CREATE OR REPLACE FUNCTION replicacao.fn_interrompe_replicacao_tabela(s_tabela text) RETURNS boolean AS $BODY$ begin perform replicacao.fn_adiciona_schema_slony_search_path(); insert into replicacao.tblog (logacao) values ('fn_interrompe_replicacao_tabela('''||s_tabela||''')'); if (not exists(select tab_id from sl_table where tab_nspname||'.'||tab_relname = s_tabela)) then raise exception 'A tabela "%", não está sendo replicada.', s_tabela; end if; perform setdroptable((select tab_id from sl_table where tab_nspname||'.'||tab_relname = s_tabela)); perform replicacao.fn_remove_schema_slony_search_path(); return true; end $BODY$ LANGUAGE plpgsql VOLATILE COST 100; CREATE OR REPLACE FUNCTION replicacao.fn_adiciona_slave_replicacao( s_contexto text, i_clicodigo integer) RETURNS boolean AS $BODY$ declare i_set integer; i_node_master integer; i_node_slave integer; begin perform replicacao.fn_adiciona_schema_slony_search_path(); i_node_master = 1; i_node_slave = replicacao.fn_get_node_by_clicodigo(i_clicodigo); insert into replicacao.tblog (logacao) values ('fn_adiciona_slave_replicacao'); if not exists(select 1 from sl_set where set_comment = s_contexto) then raise exception 'O contexto "%", não existe.', s_contexto; else select set_id into i_set from sl_set where set_comment = s_contexto; if exists(select 1 from sl_set where set_comment = s_contexto and age(xmin) = 0) then raise exception 'O contexto "%", não deve ser preparado e iniciado na mesma execução.', s_contexto; end if; end if; if not exists(select 1 from sl_node where no_id = i_node_master) then raise exception 'O node de replicação "%", não existe.', i_node_master; end if; if not exists(select 1 from sl_node where no_id = i_node_slave) then raise exception 'O node de replicação "%", não existe.', i_node_slave; end if; perform subscribeset(i_set, i_node_master, i_node_slave, false, false); perform replicacao.fn_remove_schema_slony_search_path(); return true; end $BODY$ LANGUAGE plpgsql VOLATILE COST 100; CREATE OR REPLACE FUNCTION replicacao.fn_interrompe_replicacao_tabelas_sem_slave() RETURNS boolean AS $BODY$ declare s_objetos text; r_record record; begin perform replicacao.fn_adiciona_schema_slony_search_path(); insert into replicacao.tblog (logacao) values ('fn_interrompe_replicacao_tabelas_sem_slave'); lock table sl_set; for r_record in select sl_table.tab_nspname||'.'||sl_table.tab_relname as table from sl_table left join sl_set on sl_set.set_id = sl_table.tab_set left join sl_subscribe on sl_subscribe.sub_set = sl_set.set_id where sl_subscribe.sub_receiver is null loop perform replicacao.fn_interrompe_replicacao_tabela(r_record.table); end loop; perform replicacao.fn_remove_schema_slony_search_path(); perform replicacao.fn_adiciona_schema_slony_search_path(); for r_record in select sl_set.set_comment as contexto from sl_set left join sl_subscribe on sl_subscribe.sub_set = sl_set.set_id where sl_subscribe.sub_receiver is null loop perform replicacao.fn_interrompe_replicacao(r_record.contexto); end loop; perform replicacao.fn_remove_schema_slony_search_path(); return true; end $BODY$ LANGUAGE plpgsql VOLATILE COST 100; CREATE OR REPLACE FUNCTION replicacao.fn_remove_slave_replicacao( s_contexto text, i_clicodigo integer) RETURNS text AS $BODY$ declare i_set integer; i_node_master integer; i_node_slave integer; begin perform replicacao.fn_adiciona_schema_slony_search_path(); i_node_master = 1; i_node_slave = replicacao.fn_get_node_by_clicodigo(i_clicodigo); insert into replicacao.tblog (logacao) values ('fn_remove_slave_replicacao'); if not exists(select 1 from sl_set where set_comment = s_contexto) then raise exception 'O contexto "%", não existe.', s_contexto; else select set_id into i_set from sl_set where set_comment = s_contexto; if exists(select 1 from sl_set where set_comment = s_contexto and age(xmin) = 0) then raise exception 'O contexto "%", não deve ser preparado e iniciado na mesma execução.', s_contexto; end if; end if; if not exists(select 1 from sl_node where no_id = i_node_master) then raise exception 'O node de replicação "%", não existe.', i_node_master; end if; if not exists(select 1 from sl_node where no_id = i_node_slave) then raise exception 'O node de replicação "%", não existe.', i_node_slave; end if; raise notice 'Executar no slave: "select * from _replication_ipmfiscal.unsubscribeset(%, %, false);"', i_set, i_node_slave; /*perform unsubscribeset(i_set, i_node_slave, false);*/ perform replicacao.fn_remove_schema_slony_search_path(); return format('Executar no slave: "select * from _replication_ipmfiscal.unsubscribeset(%s, %s, false);"', i_set, i_node_slave); end $BODY$ LANGUAGE plpgsql VOLATILE COST 100; CREATE SEQUENCE replicacao.sqttbddl INCREMENT 1 START 1 MINVALUE 0 MAXVALUE 9223372036854775807 CACHE 1; CREATE TABLE replicacao.tbddl ( ddlcodigo bigint NOT NULL DEFAULT nextval('replicacao.sqttbddl'::regclass), ddlcomando text COLLATE pg_catalog."default" NOT NULL, ddldatahora timestamp default now(), CONSTRAINT pk_tbddl PRIMARY KEY (ddlcodigo) ); CREATE OR REPLACE FUNCTION replicacao.fn_event_trigger_on_execute_ddl() RETURNS event_trigger LANGUAGE 'plpgsql' COST 100 VOLATILE NOT LEAKPROOF AS $BODY$ BEGIN if position((select replicacao.fn_get_schema_slony()) in current_query()) = 0 then insert into replicacao.tbddl (ddlcomando) values (current_query()); end if; END $BODY$; ``` Acessar o banco do fiscal, schema replicação, tabela tbclientenode (alterar o código do cliente) não necessário o SQL abaixo: ```ruby insert into replicacao.tbclientenode (clicodigo, nodcodigo) values (55, 2); clicodigo = (Codigo do sistema) do cliente no Atende.Net nodcodigo - Código do node criado no slon_tools.conf ou no arquivo relacionado a sua adição no master. Obs: Para cada node SLAVE (Destino) adicional deve-se executar o procedimento descrito no chamado: 480647, anexo 9 (Migração Fiscal - Criando Nodes) ``` Ao final, deve-se executar o script para criar a estrutura no banco slave, para isto edite o script de backup que fica no servidor master(IPMFiscal) ```ruby su - postgres vim backup_estrutura_slony.sh # Altere cod_fiscal="8043" -- Código do schema (Fiscal) # Destino 11 Fiscal Slony IP_Destino_11="192.168.23.11" (IP do Banco de Dados do Slony intermediário, não precisa ser alterado) Porta_Destino_11="5432" (Portal default sem necessidade de alterar) BD_Destino_11="00000_teste" (Informe o database do cliente que irá ativar a replicacao) # Destino Cliente Atende IP_Destino_16="172.16.XXX.XXX ou 172.24.XXX.XXX" (Informe a INT_RT do cliente Atende.Net - Banco de Dados PostgreSQL 16) Porta_Destino_16="65432" (Porta default) BD_Destino_16="00000_teste" (Informe o nome da database do cliente) # Execute o script - O backup e o log serão criados na storage de backup "X:\STORE\Transferir\00002_ipmfiscal" su - postgres ./backup_estrutura_slony.sh # Ao final, confira os log´s. cat 8043*.log | grep ERROR | uniq -u # Conferir se os Schemas foram criados, o comando deve retornar os Schemas restaurados, caso não existe favor verificar cat 8043*.log | grep "creating SCHEMA 8043" | uniq -u (Substituir pelo Código do Schema) ``` ## Validações (00002_www.nfs-e.net_SGDB_1150_SLONY - 192.168.23.11) Validar Schema e Authorization no banco destino: ```ruby -- Schema: migracao_fiscal -- DROP SCHEMA migracao_fiscal; CREATE SCHEMA migracao_fiscal AUTHORIZATION dbaipm; GRANT ALL ON SCHEMA migracao_fiscal TO dbaipm; GRANT ALL ON SCHEMA migracao_fiscal TO administradores; GRANT USAGE ON SCHEMA migracao_fiscal TO consultas; GRANT ALL ON SCHEMA migracao_fiscal TO migradores; GRANT ALL ON SCHEMA migracao_fiscal TO sistema; ``` Setar privilégios schemas fiscal: ```ruby do $body$ declare r_objeto record; begin for r_objeto in (select 'GRANT ALL ON SCHEMA "'||pg_namespace.nspname||'" TO dbaipm; GRANT ALL ON SCHEMA "'||pg_namespace.nspname||'" TO administradores; GRANT USAGE ON SCHEMA "'||pg_namespace.nspname||'" TO consultas; GRANT ALL ON SCHEMA "'||pg_namespace.nspname||'" TO migradores; GRANT ALL ON SCHEMA "'||pg_namespace.nspname||'" TO sistema;' as cmd, '"'||pg_namespace.nspname||'"' as nome from pg_namespace where translate(pg_namespace.nspname, '1234567890', '') in ('dcarq', '_fiarq', '_gtarq', '_isarq', '_nfarq', '_spunico', '_wgt', '_wnp', '_wsn', 'saudeweb', 'saudewebglo', 'spunico', 'migracao_fiscal', 'migracao_saude') ) loop insert into replicacao.tblog (logacao, logquery) values ('Alterando grants do schema '||r_objeto.nome, r_objeto.cmd); raise notice 'Alterando grants do schema %.', r_objeto.nome; execute r_objeto.cmd; end loop; end; $body$; ``` Liberar porta 65434 no Firewall do cliente, para a conexão entre PG11 x PG16: Vá em: Firewall > Rules > LAN Destination: 65434 Description: Bdp-1150-SLONY ![Libera Porta Firewall](../img/libera_porta_firewall_replicacao.png) ## Script de teste ```ruby # Master (Origem) create table replicacao.tbteste4(col1 integer primary key, col2 text); insert into replicacao.tbteste4 values(1,'1'); select * from replicacao.tbteste4; # Slave (11.5) create table replicacao.tbteste4(col1 integer primary key, col2 text); # Destino (16) create table replicacao.tbteste4(col1 integer primary key, col2 text); # Master (origem) select replicacao.fn_prepara_contexto('Teste Inicial 4', string_to_array('replicacao.tbteste4',',')); select replicacao.fn_inicia_replicacao_contexto('Teste Inicial 4', 55); --Segundo parametro é o código do cliente (CÓDIGO DO SISTEMA NO GDT) # Slave (11.5) select * from replicacao.tbteste4; --Já deve ter 1 registro CREATE PUBLICATION publication_mig_ipm_fiscal_00000_cliente; ALTER PUBLICATION publication_mig_ipm_fiscal_00000_cliente ADD table replicacao.tbteste4; # Destino (16) CREATE SUBSCRIPTION subscription_mig_ipm_fiscal_00000_cliente CONNECTION 'host=172.16.1.18 port=65434 user=replicacao dbname=00000_cliente' PUBLICATION publication_mig_ipm_fiscal_00000_cliente; select * from replicacao.tbteste4; # Master (origem) insert into replicacao.tbteste4 values(2,'2'); # Slave (11.5) select * from replicacao.tbteste4; --Deve ter 2 registro --Tudo deve estar funcionando. Pode apagar a tabela de teste # Destino (16) select * from replicacao.tbteste4; --Deve ter 2 registro --Tudo deve estar funcionando. Pode apagar a tabela de teste # Master (origem) select replicacao.fn_interrompe_replicacao('Teste Inicial 4'); drop table replicacao.tbteste4; # Slave (11.5) drop PUBLICATION publication_mig_ipm_fiscal_00000_cliente; drop table replicacao.tbteste4; # Destino (16) ALTER SUBSCRIPTION subscription_mig_ipm_fiscal_00000_cliente DISABLE; DROP SUBSCRIPTION subscription_mig_ipm_fiscal_00000_cliente; drop table replicacao.tbteste4; ``` Parar o serviço e iniciar novamente, após, verifique o Lag Time: ``` /usr/local/slony1/bin/slon_kill /usr/local/slony1/bin/slon_start 1 /usr/local/slony1/bin/slon_start 2 ``` ```ruby select * from replicacao.status(); ```