Dev & EngARTIGO

PostgreSQL 16 para 18: como migrar com replicação lógica e stall de escrita de 181 ms

Dimitri Fontaine detalha, na Planet PostgreSQL, como trocar de versão maior do banco usando replicação lógica, reduzindo a parada de uma janela de manutenção para um stall de escrita abaixo de 200 ms.

PostgreSQL 16 para 18: como migrar com replicação lógica e stall de escrita de 181 ms
Imagem gerada por IA

O cenário: trocar de versão maior sem desligar o banco

Todo upgrade de versão maior do PostgreSQL↳PostgreSQL11 conteúdosPostgreSQL via SSL com GolangData · abr 20195 itens legais sobre data types do PostgreSQLData · mar 20195 serviços gratuitos na cloud para bancos de dados PostgresData · fev 2025Ver tudo em Data → esbarra no mesmo problema: binários novos, diretório de dados novo e, tradicionalmente, uma janela em que o banco fica parado. Dimitri Fontaine, mantenedor do pgcopydb, publicou na Planet PostgreSQL a terceira parte de uma série sobre replicação lógica, desta vez tratando exatamente desse cenário: subir um PostgreSQL 16 para a versão 18 sem parar a aplicação.

O artigo compara duas rotas. A primeira é ficar com pg_upgrade, cujo modo --link é rápido mas ainda exige os dois servidores parados durante a troca. A segunda é usar replicação lógica: o servidor antigo publica, o novo assina, e a parada que antes durava minutos vira uma interrupção de escrita medida em menos de 200 milissegundos, sem tirar o serviço do ar.

RTO, RPO e por que cada peça extra precisa se justificar

Fontaine ancora a decisão em duas métricas: RTO (quanto tempo a aplicação fica de fato fora do ar) e RPO (quantos dados committed você aceita perder). Uma janela de manutenção clássica zera o RPO, porque nada é aceito durante ela, e torna o RTO igual ao tamanho da própria janela - resposta legítima quando o negócio aguenta esperar.

O caminho de replicação lógica troca isso por um RTO medido em milissegundos (181 ms, na medição do autor) sem gastar RPO para chegar lá. Cada peça extra do desenho - assinatura reversa, linha marcadora, comparação de LSN - só se justifica porque reduz RTO ou RPO, nunca porque "é possível".

A escada de rollback

  • pg_upgrade em modo cópia simples (padrão): cluster antigo intacto, pode ser reiniciado a qualquer momento.
  • pg_upgrade --link: mais rápido, mas a própria documentação do PostgreSQL diz que, após a primeira escrita no cluster novo, o antigo precisa ser restaurado de backup, não reiniciado (--clone, onde o sistema de arquivos permite, tem a velocidade do --link sem esse risco).
  • Assinatura reversa de replicação lógica, mantida ativa o tempo todo: "voltar atrás" vira questão de algumas centenas de milissegundos, não um restore.

Cada degrau acima remove risco e adiciona uma peça que também pode falhar, inclusive a própria assinatura reversa.

Continuous Migration: testar tudo, ensaiar a janela, só então cortar

Fontaine chama a disciplina por trás disso de Continuous Migration: não é uma ferramenta, é separar duas coisas, o teste e a janela de corte. O teste é a maior parte do trabalho - não só o caminho de requisições, mas também job em lote e cron que tocam o banco, todos rodados contra o alvo novo até não sobrar nada sem testar.

A janela de corte em si é curta e ensaiada até virar número conhecido, não chute. Os tempos de escrita parada e contagens de falha citados no artigo vêm desse ensaio, não de uma execução sortuda única. Upgrade de versão maior tem menos para testar que uma portabilidade entre bancos diferentes (o SQL é o mesmo, nada para portar), mas a forma de dois passos se mantém: testar tudo que toca o banco, ensaiar a janela curta, cortar.

O que testar antes do dia D

pg_upgrade --check pega a maioria dos problemas estruturais, mas não uma mudança de collation: se o cluster novo usa uma versão diferente de glibc ou ICU, índices de texto são herdados ordenados por uma regra que não bate mais, silenciosamente. A correção é REINDEX seguido de ALTER COLLATION ... REFRESH VERSION, problema já documentado por Daniel Vérité para a mudança do ICU 73.

postgresql.conf merece a mesma atenção: um parâmetro pode manter o nome e mudar de padrão ou de significado entre versões - vale comparar com o arquivo de exemplo da versão nova e ler as release notes. O próprio modo de upgrade entra nesse teste: o PostgreSQL 18 adicionou --swap, que supera --link em clusters com muitas relações, mas é um ponto sem volta mais duro que --link jamais foi. Fontaine recomenda medir --swap contra --link e --clone no próprio schema antes da execução real.

--link existe desde o PostgreSQL 9.0 e nunca copia dados de tabela: ele cria hard links dos arquivos e reescreve o catálogo, então seu tempo de execução acompanha o número de objetos, não o número de linhas. Fontaine mediu isso no próprio schema de demonstração:

CenárioTempo
Tabela de 1.000 linhas, um índice1,69 s
Tabela de 5 milhões de linhas (591 MB), um índice1,64 s
5.000 tabelas quase vazias6,47 s

Quinhentas vezes mais dados não mudou nada mensurável; cinco mil vezes mais tabelas, sim. Para a maioria dos schemas, --link já é questão de segundos, não de noite de manutenção, mas continua sendo parada total: os dois servidores fora do ar, sem leitura nem escrita, pelo tempo que levar. É essa lacuna - a leitura também fica fora do ar - que a replicação lógica fecha: um stall de escrita abaixo de 200 ms, com leituras (e a maior parte das escritas) disponíveis o tempo todo.

O que o PostgreSQL 17 mudou: slots e assinaturas sobrevivem ao upgrade

Desde o PostgreSQL 17, o pg_upgrade carrega o estado da replicação lógica: as release notes dizem que ele migra slots e subscriptions válidos, mas só quando o cluster antigo já é 17 ou mais recente. Fontaine testou subindo um cluster 17 para 18 com pg_upgrade --link e registrou o comportamento:

  • Um assinante mantém sua subscription, o estado de cada tabela e a posição de origem, e continua habilitado.
  • Um publicador só mantém o slot se ele já consumiu todo o WAL; caso contrário, o upgrade falha com The slot "sub" has not consumed the WAL yet.
  • postgresql.conf e pg_hba.conf continuam por conta de quem administra; contadores de estatística não são carregados.
  • O PostgreSQL 18 recusa subir um cluster sem checksums de dados, a menos que o cluster novo seja inicializado com --no-data-checksums.

Servidor anterior ao PostgreSQL 10 não serve como publicador nativo, porque CREATE SUBSCRIPTION depende de pgoutput, que depende de CREATE PUBLICATION, ambos adicionados no 10. Para ir além desse piso, pglogical cobre a partir do 9.4, e pgcopydb follow --plugin test_decoding chega ao mesmo piso sem instalar nada extra no servidor de origem. Também desde o 17, failover = true na criação da subscription sincroniza slots lógicos com um standby físico do publicador, permitindo que um standby promovido continue servindo a assinatura sem reconstruí-la do zero - tema que Fontaine deixa para um próximo post da série.

Montando a replicação lógica na prática

O esquema vai primeiro, porque DDL não é replicado. Papéis via pg_dumpall, schema via pg_dump, rodados com as ferramentas da versão nova contra o servidor antigo:

bash
pg_dumpall -h old --roles-only | psql -X -q -h new -d postgres
psql -X -h new -d postgres -c 'create database app owner app'
pg_dump -h old -d app --schema-only | psql -X -q -v ON_ERROR_STOP=1 -h new -d app

No servidor antigo, uma publicação cobrindo tudo:

sql
create publication app_pub for all tables;

É aqui que uma tabela sem chave primária aparece: publicar não muda nada até um UPDATE ou DELETE acertar a tabela, e o erro aponta direto para a correção (REPLICA IDENTITY FULL, que loga a linha inteira, ou um índice único sobre colunas not null com REPLICA IDENTITY USING INDEX).

No servidor novo, a subscription com cópia inicial, com o tráfego seguindo normalmente no antigo:

sql
create subscription app_sub
  connection 'host=old dbname=app user=repl password=repl'
  publication app_pub
  with (copy_data = true);

Uma consulta a pg_subscription_rel acompanha a cópia pelos estados i (inicializando), d (copiando dados), f (cópia terminada) e r (pronta). No teste de Fontaine, cerca de 100 MB levaram um segundo para copiar.

O que a cópia inicial não resolve

A lista do que fica de fora da cópia automática é o ponto mais prático do artigo para quem vai operar isso em produção:

  • Sequências ficam com last_value nulo no servidor novo, e o primeiro insert falha por chave primária duplicada.
  • Materialized view existe mas não vem populada (materialized view "sales_by_day" has not been populated).
  • Objetos grandes (large objects) não são replicados; a coluna oid que aponta para eles é, então o erro aparece só depois, como large object N does not exist.
  • Coluna gerada é recalculada pelo assinante - esse, ao contrário dos anteriores, é o comportamento desejado.
  • DDL rodado durante a migração não é percebido pelo lado antigo; o worker do lado novo trava com missing replicated column, e tabela criada depois só entra na assinatura após ALTER SUBSCRIPTION ... REFRESH PUBLICATION.

Cada um desses itens é motivo para rodar a bateria de testes completa antes do corte, não durante ele.

pg_dump, pgcopydb e nuvens gerenciadas: quando a réplica lógica não é a resposta certa

Fontaine não descarta o caminho mais antigo. pg_dump/pg_restore cobre sequência, conteúdo de materialized view, large object e DDL rodado no meio - tudo que a replicação lógica deixa de fora -, porque reconstrói o banco inteiro em vez de aplicar só mudanças de linha. O custo é que o tempo de parada escala com o volume de dados, não com o número de objetos.

pgcopydb, ferramenta que o próprio Fontaine mantém, paraleliza esse mesmo trabalho (schema por pg_dump/pg_restore, dados por COPY paralelo) com a mesma cobertura e o mesmo piso de versão de origem, reduzindo a parada sem eliminá-la. pgcopydb clone --follow adiciona captura de mudanças por cima, com o mesmo piso do PostgreSQL 10 via pgoutput.

Nas nuvens gerenciadas, o quadro varia: RDS, Aurora, Cloud SQL e Azure↳Azure76 conteúdosDeploy de Azure Stream Analytics job com CI/CD usando Azure PipelinesDevSecOps · mai 2019Azure – Como criar uma base de dados SQL no Microsoft Azure pronta para ser utilizadaData · abr 2019Azure Static Web Apps com Vue.js e Visual Studio CodeDevSecOps · out 2023Ver tudo em DevSecOps → Database for PostgreSQL Flexible Server oferecem upgrade in-place verdadeiro. A Aurora vai além com Blue/Green Deployments, uma versão gerenciada do próprio caminho de replicação lógica descrito aqui, com troca de tráfego sob um minuto. O Neon, por outro lado, não tem upgrade in-place algum: toda troca de versão maior exige um projeto novo e migração via pg_dump/pg_restore ou replicação lógica.

O que muda para quem cuida do banco

Para quem opera PostgreSQL em produção no Brasil, com SLA de disponibilidade e janela de manutenção cada vez mais difícil de negociar com o negócio, o artigo de Fontaine é menos uma novidade de versão e mais um roteiro de decisão. pg_upgrade --link já resolve a maioria dos upgrades em segundos, não em uma noite inteira; vale medir isso no próprio schema antes de montar qualquer coisa mais elaborada.

A replicação lógica só se justifica quando a janela de minutos do --link ainda é cara demais, ou quando o destino é outro hardware, outra região ou outra nuvem. E, mesmo aí, a lista de exceções (sequência, materialized view, large object, DDL concorrente) exige testar cada rotina que toca o banco antes de confiar o corte a 181 milissegundos de stall.

Fonte: Planet PostgreSQL

Este artigo foi escrito por Roberto Diniz, colunista de banco de dados. Conteúdo produzido por agente de IA da redação iMasters, sob revisão editorial humana. Saiba como produzimos no expediente.

Roberto DinizColunista

Especialista virtual de banco de dados e engenharia de dados. DBA veterano, TI tradicional: modelagem, performance de query, integridade e governança. Formal e criterioso — desconfia de modinha e preza consistência, backup e o plano de execução.

Mais de Roberto Diniz
Ver perfil →
Leia também