Voltar ao blog Migration Architecture

Preparação para migrações de banco de dados em aplicações auto-hospedadas: é possível reverter com segurança?

Use este checklist de reversão de migração de banco de dados antes de atualizar uma aplicação auto-hospedada. Separe a reversão de código da recuperação do banco, avalie compatibilidade e bloqueios, verifique backups e defina decisões de recuperação antes da implantação.

Operador analisando um checklist de migração de banco de dados antes de implantar uma atualização de aplicação auto-hospedada

Por que a reversão da aplicação e a reversão do banco de dados são diferentes

Substituir um novo contêiner da aplicação pela imagem anterior pode ser rápido. Isso é uma reversão de código. Ela não devolve automaticamente o banco de dados à estrutura e ao conteúdo esperados pela versão anterior.

Uma migração de banco de dados pode adicionar tabelas, renomear colunas, transformar valores armazenados, criar índices, tornar restrições mais rígidas, excluir dados ou iniciar trabalhos que continuam após a implantação. Depois que uma migração é confirmada, um ROLLBACK no nível da transação não está mais disponível para esse trabalho já confirmado. Uma recuperação segura pode exigir, em vez disso, restaurar dados, reparar dados ou concluir uma correção futura.

Trate a versão da aplicação e a alteração no banco de dados como dois objetos de implantação relacionados, mas separados. A aprovação deve depender de a aplicação antiga conseguir operar com segurança no estado do banco de dados após a migração, e não apenas de haver uma imagem de contêiner mais antiga disponível.

  • Pergunta sobre reversão de código: a versão anterior da aplicação pode ser iniciada e redirecionada novamente para atender o tráfego?
  • Pergunta sobre recuperação do banco de dados: o esquema anterior e os dados de negócio necessários podem ser restaurados ou reconstruídos sem perda inaceitável?
  • Pergunta sobre compatibilidade: o código antigo, o código novo e o banco de dados alterado podem coexistir durante a transição planejada?
  • Regra de decisão: não descreva uma versão como reversível até que os caminhos de código e de banco de dados tenham sido analisados.
Por que a reversão da aplicação e a reversão do banco de dados são diferentes

Mapeie todas as alterações que afetam o banco de dados antes da aprovação

Crie um mapa de alterações a partir das notas de versão da aplicação, dos arquivos de migração, dos scripts de implantação e dos comandos do banco de dados. Não se baseie em um rótulo como “migração automática”; identifique o que essa automação realmente altera e quando ela é executada.

Classifique cada operação pelo seu efeito no esquema, nos dados, na disponibilidade e na reversibilidade. O objetivo não é prever todos os detalhes de implementação. É revelar operações que alteram a estratégia de recuperação ou exigem controles operacionais.

Em implantações com contêineres, identifique também onde os dados persistentes do banco de dados ficam armazenados. Volumes do Docker Compose são armazenamentos persistentes gerenciados pelo mecanismo de contêineres; portanto, o volume do banco, qualquer serviço de banco de dados externo e a origem do backup devem estar claros. Recriar uma carga de trabalho da aplicação não é prova de que o banco de dados foi preservado.

  • Esquema: tabelas, colunas, tipos, índices, chaves estrangeiras, padrões e restrições adicionados ou removidos.
  • Transformação de dados: preenchimentos retroativos, conversões de valores, deduplicação, alterações de criptografia, reescrita de identificadores e exclusões.
  • Desempenho e bloqueios: regravações de tabelas, criação de índices, consultas de longa duração e operações que podem aguardar outras transações.
  • Trabalho em segundo plano: tarefas enfileiradas, workers, tarefas agendadas ou tarefas de inicialização da aplicação que continuam alterando dados após a migração de esquema.
  • Dependências: ferramentas de relatórios, integrações, exportações, views, consumidores de API e scripts personalizados que podem depender de campos ou valores existentes.
  • Metadados da migração: o identificador da migração, a ordem de execução, a ferramenta ou framework e se cada etapa possui uma operação reversa documentada.
Mapeie todas as alterações que afetam o banco de dados antes da aprovação

Verifique explicitamente a compatibilidade retroativa e progressiva

Uma implantação segura frequentemente depende de uma janela de compatibilidade: um período em que as versões anterior e nova da aplicação podem usar o mesmo estado do banco de dados. Sem essa janela, uma reversão de código após a migração pode falhar mesmo que a imagem antiga seja iniciada normalmente.

Analise os comportamentos de leitura e escrita separadamente. O código antigo pode tolerar uma nova coluna que aceite valores nulos, mas falhar se uma coluna em que ele grava tiver sido removida, renomeada, tornada obrigatória ou alterada em seu significado. O código novo pode iniciar antes da conclusão de um preenchimento retroativo apenas se conseguir lidar corretamente com as representações antiga e nova.

Torne a compatibilidade uma decisão registrada, e não uma suposição. Se o código antigo não for compatível com o banco de dados migrado, o plano de recuperação deverá priorizar uma restauração, um reparo de dados ou uma correção futura em vez de uma simples reversão de código.

  • Código antigo com esquema expandido: ele consegue ignorar tabelas, colunas e índices adicionados?
  • Código novo antes de a migração de dados terminar: ele consegue ler os formatos de valores antigo e novo?
  • Código antigo após um preenchimento retroativo: ele sobrescreverá dados transformados ou criará registros em um formato obsoleto?
  • Momento de aplicação das restrições: uma nova restrição NOT NULL, de unicidade ou de chave estrangeira rejeitará gravações feitas por uma versão mais antiga?
  • Consumidores externos: as integrações dependem de um nome de coluna, formato de saída, identificador ou comportamento de API afetado pela alteração?
  • Operação com versões mistas: se vários workers da aplicação forem reiniciados gradualmente, a operação simultânea de versões antiga e nova é suportada?

Sinalize operações destrutivas e alterações sensíveis a bloqueios

Algumas alterações merecem um plano de recuperação explícito porque removem informações, tornam premissas antigas inválidas ou afetam a disponibilidade. A remoção de uma coluna no PostgreSQL também pode remover índices e restrições de tabela que envolvam essa coluna; dependências externas à tabela, como chaves estrangeiras ou views, podem exigir ações adicionais. Uma alteração destrutiva nunca deve ser aprovada apenas porque aparece no fim de uma sequência de migração.

Os efeitos de bloqueio são igualmente importantes. No PostgreSQL, os requisitos de bloqueio de ALTER TABLE variam conforme o subcomando, e ACCESS EXCLUSIVE é o padrão, salvo quando a documentação indicar o contrário. Analise as instruções exatas em vez de tratar toda operação ALTER TABLE como equivalente.

A criação de índices também exige uma escolha de implantação. Uma criação de índice padrão no PostgreSQL bloqueia gravações enquanto é executada. CREATE INDEX CONCURRENTLY evita bloquear inserções, atualizações e exclusões simultâneas, mas não pode ser executado dentro de um bloco de transação, realiza varreduras adicionais da tabela e aguarda a conclusão de transações relevantes. Isso altera tanto o tempo quanto o tratamento de falhas.

As formas de ALTER TABLE que regravam tabelas e o TRUNCATE exigem análise reforçada quando há acesso simultâneo. O PostgreSQL documenta ressalvas de MVCC para essas operações, incluindo casos em que snapshots simultâneos podem ver uma visão vazia ou inconsistente após a confirmação.

  • Destrutivas: DROP COLUMN, DROP TABLE, TRUNCATE, preenchimentos retroativos com exclusão, conversão irreversível de valores e substituição de identificadores.
  • Que quebram compatibilidade: renomear ou remover um campo usado pelo código antigo, tornar uma restrição mais rígida e alterar o significado de valores armazenados.
  • Sensíveis à disponibilidade: regravações de tabelas, operações ALTER TABLE com bloqueios intensos e criações normais de índice em tabelas que recebem gravações ativas.
  • Etapas que não ocorrem em uma única transação: criação concorrente de índices e trabalhos em segundo plano que ocorrem fora de um limite transacional único.
  • Resposta necessária: defina o método exato de recuperação para cada operação sinalizada antes da implantação.

Prefira expandir, migrar e reduzir quando a aplicação oferecer suporte

Para alterações significativas, use o padrão expandir–migrar–reduzir quando a aplicação e as orientações de seu fornecedor oferecerem suporte. Isso separa o trabalho de compatibilidade da limpeza destrutiva, criando espaço para validar e reverter o código antes da introdução de alterações irreversíveis.

Expandir significa adicionar novas estruturas sem remover as antigas: por exemplo, um novo campo que aceite valores nulos, uma tabela ou um índice. Migrar significa preencher dados retroativamente e ensinar a nova versão da aplicação a ler e gravar a representação compatível. Reduzir significa remover estruturas obsoletas somente após o fim da janela de compatibilidade e a conclusão da validação.

Não imponha esse padrão a uma aplicação cujo caminho de migração fornecido não ofereça suporte a versões em etapas. Nesse caso, documente a sequência exigida pelo fornecedor, teste-a fielmente e escolha um plano adequado de manutenção e recuperação. O princípio útil é separar riscos, não reescrever artificialmente migrações de terceiros.

  • Expandir: adicione a nova estrutura e confirme que o código antigo continua funcionando.
  • Implantar código compatível: assegure que o código novo lide com ambas as representações, quando necessário.
  • Migrar: execute preenchimentos retroativos em lotes observáveis quando a aplicação oferecer suporte a essa abordagem.
  • Validar: compare contagens, registros obrigatórios, permissões, integrações e fluxos de trabalho essenciais.
  • Reduzir: remova campos ou formatos legados somente depois que a janela de reversão tiver sido intencionalmente encerrada.
  • Registre o ponto sem retorno: declare exatamente quando uma simples reversão de código deixa de ser segura.

Reúna evidências de implantação, não apenas o status de um backup

Um backup só é útil quando seu método, cobertura, localização e processo de restauração são compreendidos. O PostgreSQL diferencia dumps SQL, backups de sistema de arquivos e arquivamento contínuo; cada um tem pontos fortes e limitações diferentes. Registre qual método protege essa alteração, em vez de usar a declaração genérica “backup concluído”.

Um dump lógico do PostgreSQL é um snapshot internamente consistente do momento em que pg_dump começa, mas operações que exigem um bloqueio exclusivo, incluindo a maioria das formas de ALTER TABLE, são exceções ao seu comportamento normalmente não bloqueante. Confirme que o momento e o método do backup são compatíveis com os requisitos de bloqueio e recuperação da migração.

A recuperação para um ponto no tempo não é uma promessa genérica. No PostgreSQL, ela exige um backup físico anterior adequado e logs write-ahead arquivados que cubram o momento-alvo. Se esses pré-requisitos não existirem, não liste a recuperação para um ponto no tempo como uma opção disponível.

A Airbip oferece backups configuráveis diários, semanais e mensais para implantações de aplicações. As equipes ainda devem verificar o escopo, a configuração de retenção, a cobertura do banco de dados e o procedimento de restauração aplicáveis à sua própria instância antes de depender desses backups para uma decisão de migração.

  • Identidade do backup: método, horário de conclusão, escopo, local de armazenamento e a pessoa que o verificou.
  • Confiança na restauração: um teste de restauração recente, etapas estimadas, credenciais necessárias, ambiente de destino e limitações conhecidas.
  • Evidências da migração: versão exata da aplicação ou referência da imagem, identificadores de migração, horários de início e término, logs e erros.
  • Verificações de linha de base: registre contagens importantes, registros representativos, fluxos de trabalho críticos e status das integrações antes da alteração.
  • Verificações pós-alteração: registre as mesmas verificações após a migração e defina diferenças aceitáveis.
  • Limite de recuperação: decida o máximo aceitável de indisponibilidade e exposição à perda de dados antes de começar.

Decida se é necessária uma janela de manutenção ou uma restrição temporária de gravações

Uma janela de manutenção é justificada quando a migração pode bloquear gravações, criar estados mistos incompatíveis, levar um tempo incerto ou exigir uma restauração que não consiga incorporar com segurança alterações posteriores feitas pelos usuários. Uma restrição temporária de gravações pode ser suficiente quando as leituras permanecem seguras, mas as gravações entrariam em conflito com um preenchimento retroativo, uma alteração de esquema ou uma possível reversão.

Baseie a decisão nas operações reais e no impacto para o negócio. Por exemplo, uma criação normal de índice no PostgreSQL bloqueia gravações, enquanto uma criação concorrente de índice evita esse bloqueio específico de gravações, mas tem características operacionais de execução mais longa e não pode compartilhar um limite transacional normal. Nenhuma das opções é automaticamente mais segura sem considerar carga de trabalho, tempo e recuperação.

Defina o que os usuários verão e o que os operadores farão. Uma restrição de gravações pode significar pausar workers em segundo plano, desativar importações agendadas, colocar uma aplicação em um modo de manutenção suportado pelo fornecedor ou rejeitar temporariamente solicitações de gravação. Garanta que integrações e administradores recebam a mesma instrução; caso contrário, eles poderão criar dados que complicam a recuperação.

  • Use uma janela completa de manutenção quando alterações de esquema ou restauração tornarem inseguras as gravações simultâneas.
  • Use uma restrição de gravações direcionada quando o acesso de leitura puder continuar com segurança e a aplicação oferecer suporte a esse modo.
  • Pause ou considere workers em segundo plano, importações, webhooks e tarefas agendadas.
  • Defina um horário de início, duração esperada, ponto de decisão para extensão e canal de comunicação com usuários.
  • Confirme como o trabalho enfileirado será retomado, deduplicado ou reconciliado após a implantação ou recuperação.
  • Interrompa se bloqueios, duração ou taxas de erro excederem o limite aprovado.

Escolha o caminho de recuperação antes de implantar

A recuperação é uma árvore de decisões, não um único botão de reversão. Pré-aprove as condições em que a equipe reverterá o código da aplicação, restaurará dados, reparará um conjunto limitado de registros ou continuará com uma correção futura. Defina quem pode autorizar cada ação, especialmente uma restauração que possa descartar gravações legítimas feitas após o ponto de backup selecionado.

Uma reversão de código é apropriada somente quando a compatibilidade foi confirmada e a migração não criou um estado inseguro do banco de dados para a versão mais antiga. Uma restauração é apropriada quando o próprio estado dos dados precisa voltar a um ponto conhecido, mas exige tratamento cuidadoso das gravações ocorridas após o backup. Um reparo de dados pode funcionar para um erro pequeno, totalmente compreendido e auditável. Uma correção futura costuma ser mais segura quando a restauração perderia mais atividade de negócio válida do que a correção do defeito.

O comportamento do framework também é importante. Por exemplo, o Django identifica etapas RunPython sem reverse_code e etapas RunSQL sem reverse_sql como irreversíveis. O comportamento transacional das migrações do Django também varia conforme o mecanismo do banco de dados: seu tratamento padrão difere entre mecanismos com transações DDL, como PostgreSQL e SQLite, e mecanismos como MySQL e Oracle. Leia as orientações do framework de migração da aplicação e as orientações específicas para o banco de dados antes de supor que a reversão está disponível.

  • Reversão de código: especifique a versão anterior compatível e as verificações necessárias antes de redirecionar o tráfego de volta.
  • Restauração: especifique o ponto de recuperação, método, indisponibilidade esperada, implicações de perda de dados e responsável pela reconciliação.
  • Reparo de dados: especifique os registros afetados, script de reparo, trilha de auditoria, consulta de validação e método de reversão do próprio reparo.
  • Correção futura: especifique o estado intermediário seguro, responsável, caminho de escalonamento e controles de impacto para os usuários.
  • Autoridade: nomeie o operador técnico, o responsável pelos dados de negócio e o decisor final para cada opção de recuperação.
  • Comunicações: prepare mensagens internas e voltadas aos usuários para manutenção prolongada, reconciliação de dados ou restauração do serviço.

Perguntas frequentes

Posso reverter uma migração de banco de dados revertendo o contêiner da aplicação?

Não necessariamente. Retornar a uma imagem mais antiga da aplicação altera o código, não o esquema ou os dados já confirmados no banco de dados. Use a reversão de código somente após confirmar que a versão anterior é compatível com o estado do banco após a migração.

O que um checklist de reversão de migração de banco de dados deve incluir?

Inclua as alterações exatas de esquema e dados, a reversibilidade da migração, a compatibilidade entre código antigo e novo, os riscos de bloqueio e indisponibilidade, o método e escopo do backup, evidências de teste de restauração, a validação antes e depois da alteração, um plano de controle de gravações, opções de recuperação e os responsáveis pelas decisões.

Quando um backup não é suficiente para uma reversão segura?

Um backup isolado é insuficiente quando a equipe não sabe se ele inclui o banco de dados, se pode ser restaurado, que ponto no tempo ele representa ou como serão tratadas as alterações dos usuários feitas após o backup. Um plano de restauração precisa de evidências e de uma decisão de negócio sobre a perda de dados aceitável.

CREATE INDEX CONCURRENTLY é sempre a opção certa no PostgreSQL?

Não. Ele evita bloquear inserções, atualizações e exclusões simultâneas, mas não pode ser executado dentro de um bloco de transação, usa varreduras adicionais da tabela e aguarda transações relevantes. Escolha-o com base na carga de trabalho, nas ferramentas de implantação, na duração e no tratamento de falhas.

Quando devemos usar uma janela de manutenção para uma migração de banco de dados?

Use uma quando as operações puderem bloquear gravações, quando versões antiga e nova da aplicação não puderem coexistir com segurança, quando atividades em segundo plano complicarem a recuperação ou quando uma restauração for a resposta provável a uma falha. Uma restrição temporária de gravações pode ser suficiente para alterações de menor impacto quando as leituras puderem continuar com segurança.

Como a Airbip se encaixa na preparação para migrações de banco de dados?

A Airbip gerencia implantações de aplicações baseadas em Docker e oferece backups configuráveis diários, semanais e mensais. A preparação para migrações continua sendo uma responsabilidade operacional compartilhada: a equipe deve verificar o que recebe backup, testar a restauração quando apropriado, entender o comportamento de migração da aplicação e aprovar decisões sobre dados e recuperação.

Fontes e leituras adicionais

  1. PostgreSQL transactions — PostgreSQL Global Development Group
  2. PostgreSQL ALTER TABLE — PostgreSQL Global Development Group
  3. PostgreSQL CREATE INDEX — PostgreSQL Global Development Group
  4. PostgreSQL backup and restore — PostgreSQL Global Development Group
  5. PostgreSQL SQL dump — PostgreSQL Global Development Group
  6. PostgreSQL write-ahead logging — PostgreSQL Global Development Group
  7. PostgreSQL MVCC caveats — PostgreSQL Global Development Group
  8. Django migration operations — Django Software Foundation
  9. Django migrations — Django Software Foundation
  10. Docker Compose volume reference — Docker