Marcio Cunha

Migrações de Banco de Dados sem Tempo de Indisponibilidade Usando o Padrão Expand and Contract

Aprenda como modificar o esquema de bancos de dados relacionais em produção sem interromper o serviço, aplicando a técnica de expansão e contração de colunas e tabelas passo a passo.

Marcio Cunha12 min
Também disponível em:EnglishEspañol
Resumo
  • Alterações estruturais diretas em bases de dados grandes causam bloqueios que derrubam sistemas inteiros por horas.
  • O método de expandir e contrair desacopla a alteração do banco em etapas isoladas que convivem pacificamente com o código antigo e o novo.
  • A criação de colunas intermediárias permite que versões antigas e atualizadas da aplicação leiam e escrevam dados simultaneamente sem corrupção.
  • A remoção definitiva de colunas antigas ocorre apenas após ciclos completos de publicação e validação em ambiente produtivo.
  • A disciplina rigorosa de retrocompatibilidade elimina o risco de indisponibilidade e devolve a previsibilidade aos ciclos de entrega de software.

O desafio invisível das alterações estruturais em bases de dados

Quando precisamos alterar o formato dos dados em um sistema de grande porte, o primeiro instinto costuma ser simples: alterar a tabela e enviar o código novo. Na prática, sistemas em produção processam milhares de requisições por segundo, e qualquer comando que bloqueie o acesso à tabela principal causa falhas em cascata, irritando usuários e sobrecarregando equipes de suporte.

Em bancos de dados relacionais como PostgreSQL ou MySQL, alterar uma coluna ou adicionar restrições complexas muitas vezes exige que o sistema reescreva inteiramente o arquivo de dados em disco. Durante essa reescrita, o banco trava a tabela para evitar inconsistências, transformando uma simples atualização de rotina em uma pane generalizada.

Para resolver esse impasse sem interromper o tráfego de usuários, a engenharia de software adota o padrão conhecido como Expand and Contract, ou expansão e contração. Em termos práticos, essa abordagem divide uma mudança arriscada em várias etapas incrementais e seguras, permitindo que a aplicação evolua de forma contínua sem que ninguém perceba a transição nos bastidores.

Compreendendo o ciclo de expansão e contração

O conceito fundamental por trás do Expand and Contract é garantir que as alterações no banco de dados aconteçam sempre de maneira aditiva antes de se tornarem destrutivas. Em vez de modificar o que já existe, criamos estruturas novas ao lado das antigas, preenchendo ambas temporariamente até que o sistema inteiro dependa exclusivamente da novidade.

Esse ciclo costuma ser dividido em três fases distintas: a expansão, a migração e a contração. Na expansão, preparamos o banco para aceitar o novo formato sem quebrar o código legado que ainda está rodando nos servidores de produção. Na migração, movemos os dados antigos para o novo local e atualizamos gradualmente o código da aplicação.

Na última fase, a contração, removemos com segurança tudo o que se tornou obsoleto, como colunas antigas ou tabelas duplicadas. Esse cuidado cirúrgico evita o temido momento de inatividade planejada, conhecido no mercado como downtime, garantindo que o serviço permaneça acessível vinte e quatro horas por dia.

Cenário prático: dividindo o nome completo em primeiro e último nome

Imagine que temos uma tabela chamada clientes com uma única coluna chamada nome_completo, contendo o nome e o sobrenome juntos. A nova regra de negócio exige que o sistema armazene esses dados separadamente em duas colunas distintas: primeiro_nome e sobrenome.

Se alterássemos a tabela de uma só vez para apagar a coluna antiga e criar as duas novas, todas as consultas e cadastros em andamento falhariam instantaneamente. O código atual da aplicação tentará gravar dados na coluna antiga que não existe mais, gerando erros críticos de execução que derrubam o fluxo de pagamento ou cadastro.

Para evitar esse colapso, aplicamos a primeira etapa do padrão: adicionamos as novas colunas primeiro_nome e sobrenome na tabela, deixando a coluna antiga nome_completo intacta. Nesse exato momento, o banco de dados possui tanto a estrutura velha quanto a nova coexistindo em harmonia, abrindo caminho para a transição do código.

Atualizando o código da aplicação com suporte duplo

Com as novas colunas criadas no banco, o próximo passo é atualizar a aplicação para suportar um estado de transição conhecido como escrita dupla. Durante esse período, o código da aplicação é ajustado para continuar gravando o nome completo na coluna antiga, mas também preenchendo as colunas novas recém-criadas.

Para consultas e leituras, o sistema pode continuar utilizando a coluna antiga enquanto o processo de cópia em segundo plano não for concluído. Essa redundância momentânea é o segredo para evitar qualquer interrupção, pois assegura que, caso ocorra um erro na nova lógica, o fluxo principal continue operando sem interrupções.

Abaixo temos um exemplo ilustrativo em código que demonstra como a aplicação lida com essa escrita dupla durante o período de transição estrutural:

def atualizar_cliente(conexao, cliente_id, nome_completo):
partes = nome_completo.split(' ', 1)
primeiro = partes[0]
sobrenome = partes[1] if len(partes) > 1 else ''

# Escrita dupla: mantemos a coluna antiga e preenchemos as novas
cursor = conexao.cursor()
cursor.execute(
"UPDATE clientes SET nome_completo = %s, primeiro_nome = %s, sobrenome = %s WHERE id = %s",
(nome_completo, primeiro, sobrenome, cliente_id)
)
conexao.commit()

Esse trecho simples de código ilustra perfeitamente como o sistema atua como uma ponte entre o passado e o futuro, garantindo que nenhum dado seja perdido ou interpretado incorretamente pelas rotinas de negócio.

Sincronizando o histórico de dados existentes

Adicionar as colunas e atualizar o código para novos cadastros resolve apenas metade do problema, pois os registros antigos que já estavam no banco continuam com as colunas novas vazias. Para preencher essa lacuna, precisamos executar um processo auxiliar em lotes conhecido como migração de dados históricos.

Esse processo lê os registros antigos em pequenos blocos, por exemplo, mil linhas por vez, e atualiza as colunas novas correspondentes sem sobrecarregar a memória do servidor de banco de dados. Fazer isso em lotes controlados evita picos repentinos de uso de CPU e disco que poderiam deixar o sistema lento para os usuários finais.

Após a conclusão dessa varredura, todas as linhas da tabela passam a ter os dados duplicados corretamente entre a estrutura antiga e a nova. Nesse ponto, a aplicação já está pronta para mudar sua fonte principal de leitura para as colunas recém-criadas.

Ajustando as consultas e preparando a contração

Com os dados históricos totalmente sincronizados e o código adaptado para gravar em ambos os lugares, o passo seguinte consiste em alterar as consultas da aplicação para lerem exclusivamente das colunas novas. Isso garante que a lógica de negócio esteja validada e operando com o novo esquema estrutural.

Após implantar essa alteração e monitorar o sistema por alguns dias para garantir que não há erros ocultos, entramos na fase final do padrão conhecida como contração. Nessa etapa, removemos o código que escrevia na coluna antiga e, finalmente, excluímos a coluna nome_completo da tabela do banco de dados.

A exclusão de uma coluna antiga em sistemas modernos deve ser feita com cautela, mas como a aplicação já parou totalmente de utilizá-la, essa operação ocorre sem nenhum risco de quebra ou perda de dados em produção.

Considerações finais sobre a evolução segura de esquemas

Implementar mudanças estruturais sem interromper o funcionamento do sistema exige disciplina, planejamento e a quebra de tarefas complexas em etapas menores e reversíveis. O padrão Expand and Contract transforma uma operação estressante de banco de dados em uma rotina previsível e segura de engenharia.

Embora essa abordagem exija mais linhas de código temporário e um esforço maior de planejamento inicial, o retorno sobre o investimento é inestimável. Garantir alta disponibilidade e confiança operacional consolida a maturidade técnica da equipe e preserva a melhor experiência possível para quem utiliza o sistema todos os dias.