Padrão Expand/Contract
O padrão expand/contract (também chamado de mudança paralela) envia alterações de esquema aditivas enquanto as versões antiga e nova do aplicativo estão em execução, e então remove o esquema obsoleto somente após o tráfego ter migrado completamente.
Receita
Fase 1 EXPAND - migração adiciona nova coluna/tabela (nullable ou não utilizada)
Fase 2 DEPLOY - app v2 escreve em ambos, antigo e novo
Fase 3 BACKFILL - job preenche nova coluna para linhas históricas
Fase 4 DEPLOY - app v3 lê apenas o novo
Fase 5 CONTRACT - migração remove coluna antiga
Quando usar isso: Renomear colunas, alterar tipos, dividir tabelas ou qualquer DDL que quebre em tabelas sob tráfego ativo.
Exemplo de Trabalho
Renomear full_name para display_name sem downtime:
-- Migração V10 EXPAND
ALTER TABLE app.users ADD COLUMN display_name text;-- App v2 dual-write (código da aplicação)
INSERT INTO app.users (full_name, display_name, ...)
VALUES ($1, $1, ...);
UPDATE app.users SET display_name = full_name WHERE display_name IS NULL;-- Migração V11 BACKFILL (lote na aplicação ou job SQL)
UPDATE app.users
SET display_name = full_name
WHERE display_name IS NULL AND id BETWEEN $1 AND $2;-- Migração V12 CONTRACT (após o envio do app v3)
ALTER TABLE app.users DROP COLUMN full_name;
ALTER TABLE app.users ALTER COLUMN display_name SET NOT NULL;O que isso demonstra:
- A coluna antiga vive por pelo menos dois ciclos de deploy
- O backfill pode ser dividido por intervalos de chaves primárias
- A fase de contrato é irreversível sem restauração - controle por métricas
Análise Detalhada
Técnicas de Expansão
| Tipo de Mudança | Etapa de Expansão |
|---|---|
| Renomear coluna | Adicionar nova coluna; dual-write |
| Restrição NOT NULL | Adicionar nullable; backfill; então SET NOT NULL |
| Dividir tabela | Criar nova tabela; trigger ou dual-write do app |
| Alterar tipo | Adicionar amount_cents bigint; migrar de amount numeric |
| Alterar alvo de FK | Adicionar nova coluna FK nullable; backfill; alternar |
Checklist de Segurança de Contrato
- Nenhum código de produção referencia o objeto antigo (grep + grafo de deploy)
- Nenhum dashboard de BI consulta a coluna antiga
- Replicação/assinantes incluem a nova forma
- Backup feito antes do DROP
- Migração de contrato em janela de baixo tráfego (DROP ainda bloqueia brevemente)
Feature Flags
-- Opcional: rastrear fase de migração em tabela de configuração
INSERT INTO app.feature_flags (key, enabled) VALUES ('use_display_name', true);- Coordenar a fase do esquema com as feature flags do aplicativo.
- Reverter a flag do aplicativo sem a migração de contrato se for apenas expansão até agora.
Armadilhas
- Contrato antes do backfill completo - NOT NULL falha ou o app lê NULLs. Correção:
SELECT count(*) WHERE new_col IS NULLcomo gate no pipeline de deploy. - Dual-write esquecido em um caminho de código - Ferramenta de administração ainda escreve apenas na coluna antiga. Correção: Busca no código de todos os escritores; testes de integração afirmam ambas as colunas.
- DROP na mesma release do expand - Downtime ou falha na ordem de deploy. Correção: Mínimo de uma release entre expand e contract.
- View depende da coluna antiga - Migração de contrato falha ou quebra relatórios. Correção: Atualizar views na migração de expansão para usar ponte COALESCE.
- Cache de esquema ORM - Pods antigos travam após o contrato. Correção: Deploy em rolo garante zero pods antigos antes da execução do job de contrato.
Alternativas
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
| DROP em janela de manutenção | Pequeno app interno | SLA 24/7 |
| Banco de dados Blue/green | Mudança de forma massiva | Equipe não tem habilidade em replicação lógica |
| Views como camada de compatibilidade | Renomear com muitos leitores | Caminhos com muitas escritas através de views |
FAQs
Quantas releases entre expand e contract?
Normalmente 2-3: expand, deploy dual-write, deploy de corte de leitura, contract. Ajuste à sua cadência de release.
Triggers para dual-write?
Possível em SQL para aplicação uniforme; dual-write do app é mais fácil de testar. Triggers adicionam amplificação de escrita.
Renomear vs adicionar/remover?
RENAME COLUMN verdadeiro é rápido, mas quebra o app antigo instantaneamente. Expand/contract com novo nome é mais seguro para zero-downtime.
Relacionados
- Zero-Downtime DDL - índices concorrentes e NOT VALID
- Large Table Migrations - backfill em lotes
- Migrations Basics - disciplina SQL versionada
Versões do Stack: Esta página foi escrita para PostgreSQL 18.4 (estável 18, manutenção 17), pgvector 0.8+, PgBouncer 1.x, Patroni 3.x, e PostGIS 3.5+.