Uma migração sem tempo de inatividade pode ser reversível?
Planeje uma migração sem tempo de inatividade com roteamento Strangler, gravações duplas, reconciliação e virada reversível.

Uma migração sem tempo de inatividade só é possível quando o sistema antigo continua sendo um destino válido até o novo provar que pode assumir. Parece óbvio, mas muitos planos eliminam essa opção. Eles copiam dados, implantam o substituto, marcam uma janela de manutenção e chamam a interrupção de "virada". A interrupção não é inevitável. Ela nasce ao juntar quatro ações distintas: mudar a rota, trocar quem grava, transferir a autoridade dos dados e remover o caminho antigo.
Um projeto mais seguro separa essas ações e torna cada uma observável e reversível. As solicitações passam por um ponto de roteamento. As gravações têm identidades estáveis e podem ser reproduzidas. A carga histórica ocupa uma posição definida no fluxo de mudanças. A reconciliação compara significado de negócio, não formatos de bytes. A mudança final de rota vira uma pequena alteração de configuração, não um salto sobre um vazio.
Isso não significa que o usuário nunca verá um erro. O sistema pode continuar disponível enquanto uma solicitação falha pelos mesmos motivos de antes. A promessa é mais restrita e útil: a migração não exige um período em que o serviço recuse todo o trabalho, e os operadores podem devolver o tráfego sem reconstruir o banco de dados de ontem.
Tempo de atividade depende do roteamento
A aplicação permanece disponível se toda solicitação tiver um destino válido durante a migração. Replicar dados ajuda, mas não garante disponibilidade. Se os clientes se conectam direto ao servidor que será substituído, a decisão de rota vive em centenas de clientes e a virada foge do controle.
Coloque um ponto de decisão controlado diante das duas implementações. Pode ser um gateway de API, proxy reverso, regra de balanceador, atribuição de consumidor ou adaptador no processo existente. A tecnologia importa menos que o contrato: os operadores precisam mudar o destino de uma parte específica sem reimplantar todos os chamadores.
Escolha essa parte por uma fronteira de negócio estável. Roteie consulta de conta separada da alteração, exportação de fatura separada da criação, ou um locatário separado dos demais. Não corte pelo arquivo de controlador mais fácil. Uma rota que mistura leituras, gravações, tarefas e callbacks pode mostrar um canário verde enquanto um caminho invisível ainda altera dados antigos.
Um registro mínimo de rota deve ser simples o bastante para revisão durante um incidente:
{
"capability": "invoice.read",
"cohort": "tenant-042",
"destination": "new",
"fallback": "old",
"revision": 17,
"changed_by": "change-1842"
}
capability nomeia o comportamento, não uma URL. cohort limita a exposição. fallback informa para onde o roteador pode enviar a solicitação se o lado novo falhar. A revisão expõe edições simultâneas, e a referência diz por que a rota mudou. Guarde o registro com histórico de auditoria e faça o roteador conservar a última configuração válida se o plano de controle desaparecer.
As verificações de saúde precisam testar a capacidade roteada. Um processo que responde a /health ainda pode não ter migração de banco, chave de descriptografia ou dependência disponível. Execute uma pequena leitura ou operação sintética pela mesma cadeia do tráfego real, com dados reservados. Seja conservador no fallback automático de gravações. Repetir uma leitura no lado antigo costuma ser inofensivo; repetir uma gravação aceita pode criar outro pedido, pagamento ou caso.
O ponto Strangler deve controlar todas as entradas
O roteamento Strangler só funciona quando seu ponto de interceptação cobre todas as entradas de uma capacidade. A descrição de Martin Fowler para o padrão Strangler Fig enfatiza a substituição gradual ao redor do sistema antigo. As equipes lembram de "gradual" e esquecem "ao redor". Se uma tarefa noturna, cliente desktop, arquivo ou fila contorna o ponto, duas implementações atuam sem uma política comum.
Inventarie entradas com evidências de execução, não com o diagrama. Examine logs de acesso, vínculos de filas, definições do agendador, fluxos de firewall, linguagem de controle de lotes, chamadores de procedimentos e callbacks que voltam como trabalho de entrada. Sistemas antigos costumam expor a mesma operação por HTTP, transação de terminal e arquivo importado à noite. Trate tudo como uma capacidade com vários adaptadores.
O ponto deve normalizar identidade e contexto antes do envio. Ambos os lados precisam do mesmo ID de solicitação, ator, locatário, decisão de autorização, prazo e chave de idempotência. Se cada implementação derivar esses campos sozinha, a reconciliação culpará a lógica de negócio por diferenças criadas na borda. Preserve o corpo original para auditoria quando permitido, mas passe aos dois caminhos um envelope canônico versionado.
Não comece com roteamento percentual para trabalho com estado. Um canário aleatório de dez por cento pode enviar a primeira etapa ao lado novo e a seguinte ao antigo. Prefira uma chave determinística como locatário, conta, caso ou ID de fluxo. O roteador deve dar a mesma resposta para essa chave até o operador mudar a regra.
Autenticação também é uma entrada. Se o sistema antigo cria sessões que o novo não valida, a primeira solicitação roteada força novo login. Valide a sessão no ponto comum e passe uma declaração de identidade assinada e curta, ou faça ambos aceitarem um formato compartilhado durante a coexistência. Não migre hashes de senha impondo redefinições sem aceitação consciente do negócio.
Torne a avaliação de rota visível em cada trace e log. Registre revisão, destino, decisão de fallback e chave da coorte. Se um usuário disser que a fatura de ontem difere da de hoje, você precisa saber qual implementação respondeu. Um painel apenas com percentuais agregados não responde.
Gravações duplas precisam de uma intenção durável
A forma segura de gravar duas vezes registra uma intenção de negócio durável e deixa workers independentes aplicá-la a cada modelo. A forma insegura faz a thread chamar o banco A e depois o B, esperando dois sucessos. Há uma lacuna inevitável: o primeiro commit funciona, o segundo expira, e o chamador não sabe se repetir duplicará o trabalho.
Faça a autoridade atual confirmar a mudança e um evento de outbox na mesma transação local. Um relay publica o evento, e um consumidor aplica uma projeção idempotente ao novo armazenamento. No início, o armazenamento antigo segue como autoridade mesmo com alguns segundos de atraso na projeção. Depois da mudança de rota, a direção pode se inverter para preservar o retorno.
O envelope de gravação precisa rejeitar duplicatas e detectar ordem incorreta:
{
"event_id": "01J7M6R2K8N4T3Q9V5X1",
"aggregate_type": "invoice",
"aggregate_id": "inv-90318",
"aggregate_version": 44,
"operation": "invoice.adjusted",
"occurred_at": "2026-08-14T10:42:31Z",
"payload": {"line_id": "ln-8", "amount_minor": 1250}
}
O consumidor salva event_id numa tabela de eventos processados com restrição única. Aplica a versão 44 só depois da 43, ou estaciona o evento até a versão ausente chegar. A carga usa unidades de negócio, como centavos, não strings formatadas. Uma entrega duplicada retorna o resultado anterior sem repetir a operação.
A documentação de repetição da Microsoft explica o risco: um serviço pode concluir o trabalho e perder a resposta, então uma tentativa repete uma operação não idempotente. Migrações criam isso com frequência quando relays reiniciam e caminhos de rede mudam. A chave de idempotência não é mero conforto de API. Ela prova que duas entregas representam uma intenção.
Algumas gravações não podem ser reproduzidas com segurança, sobretudo chamadas externas sem idempotência. Mantenha esse efeito atrás da autoridade existente durante a coexistência. Replique o estado resultante, não o comando. Enviar a mesma instrução de pagamento pelos dois sistemas gera dois pagamentos.
Observe a idade da outbox, não só o número de linhas. Uma fila pequena com um evento bloqueado há seis horas pode ser pior que uma grande escoando normalmente. Exponha o evento não publicado mais antigo, o não aplicado mais antigo por agregado, tentativas, motivo de descarte e lacuna de versão. Esses sinais mostram se os dados de retorno estão atuais.
A carga histórica deve encontrar o fluxo ativo
Uma carga histórica termina quando seu snapshot entra no fluxo de mudanças numa posição conhecida. Copiar linhas enquanto a produção continua persegue um alvo móvel. Se o copiador lê uma conta antes da atualização e grava depois que o consumidor a aplicou, o snapshot antigo pode sobrescrever estado recente.
Há dois padrões sólidos. Tire um snapshot consistente ligado a uma posição do log, carregue-o e aplique as mudanças posteriores. Ou condicione cada upsert à versão de origem para impedir que substitua uma projeção nova. A sintaxe de captura varia, mas a regra não: todo registro copiado e evento ativo precisam ter ordem comparável.
A replicação lógica do PostgreSQL mostra ajuda e limites. A documentação diz que as mudanças seguem o snapshot inicial na ordem do publicador dentro de uma assinatura. Também alerta que definições de esquema e DDL não são replicados em versões comuns, e o estado de sequências exigiu tratamento separado antes da virada. O manual da versão exata pertence ao runbook. "A replicação alcançou" não prova que o destino aceita novas gravações.
Controle a carga pela latência de produção, não por uma taxa fixa de linhas. Leia faixas de chave primária, registre faixa concluída e posição, e torne cada lote reiniciável. Transações grandes retêm logs, alongam bloqueios e escondem progresso. As minúsculas gastam overhead. Meça o impacto e escolha um lote que termine e repita dentro dos limites operacionais.
Transformações precisam de armazenamento explícito de falhas. Se um status antigo tem valor recusado pelo novo enum, não o converta silenciosamente em UNKNOWN. Guarde chave e versão de origem, versão do transformador, valor bruto e erro. O responsável pelo negócio decide se o valor mapeia para um estado existente, exige outro ou revela corrupção antiga.
Nunca carregue totais derivados sem definir quem os recalcula. Se o sistema novo deriva o saldo de lançamentos e o antigo mantém uma coluna mutável, compare lançamentos e saldo final, mas não copie a coluna para sempre. Caso contrário, duas autoridades sobrevivem no novo esquema.
A reconciliação compara invariantes
A reconciliação deve provar que os sistemas fazem as mesmas afirmações de negócio mesmo com esquemas diferentes. Contagens e checksums encontram dados ausentes, mas falham como principal teste depois de redesenhar a arquitetura. Um modelo Postgres normalizado não tem as mesmas linhas de um registro COBOL, e um cliente TypeScript não serializa campos como um programa desktop.
Comece pelas invariantes usadas pelo negócio: cada lançamento pertence a uma conta, débitos e créditos fecham dentro do limite contábil, a fatura equivale aos itens mais imposto, um caso fechado tem evento de encerramento e uma referência externa continua única. Execute cada invariante nos dois lados e compare por ID de negócio estável.
Para campos que devem coincidir, normalize apenas diferenças sem significado. Uniformize precisão de horários, forma Unicode, strings vazias e nulos conforme o comportamento antigo, e dinheiro em unidades menores inteiras. Não coloque identificadores em minúsculas nem arredonde só para deixar o relatório verde. Cada regra pode esconder um defeito, então deve ser versionada e revisada.
Uma consulta prática retorna discrepâncias, não um total aprovado:
SELECT account_id, old_balance_minor, new_balance_minor,
old_balance_minor - new_balance_minor AS delta_minor
FROM migration_account_balance
WHERE old_balance_minor <> new_balance_minor
ORDER BY ABS(old_balance_minor - new_balance_minor) DESC;
O formato importa: o operador precisa do ID, dos dois valores e da diferença. Guarde ID da execução, posições de origem e destino, versão da consulta, horários, quantidade de discrepâncias e amostra limitada. Sem posições, o relatório não pode ser reproduzido porque os bancos continuam mudando.
Classifique discrepâncias pela causa. Lacuna de transporte significa que o evento não chegou. Lacuna de ordem significa que chegou cedo ou tarde. Defeito de transformação produz destino errado a partir da origem certa. Diferenças esperadas vêm de mudanças deliberadas, como remover espaços finais. Diferenças inexplicadas bloqueiam a expansão mesmo em pequeno número.
Não exija zero diferença quando o tempo altera a resposta. Uma cotação expirando ou estoque ativo pode mudar entre leituras. Congele o relógio relevante, compare posições iguais ou valide tolerância aprovada pelo negócio. "Perto o bastante" escolhido pela equipe não é critério de aceitação.
Leituras sombra expõem o comportamento
Leituras sombra fazem a implementação nova processar solicitações reais enquanto a resposta antiga vai ao usuário. Elas acham defeitos semânticos ignorados por verificações de dados: ordem padrão, casos de autorização, arredondamento, formato local, registros ausentes e erros diferentes. Como o resultado novo é descartado, só são seguras sem efeitos colaterais.
Clone a solicitação normalizada no ponto comum e fixe prazo menor que o orçamento do usuário. Uma sombra lenta nunca deve atrasar a resposta oficial. Remova ou substitua segredos desnecessários e marque a solicitação para impedir que código posterior envie e-mail, altere caches, estenda sessões ou faça chamadas faturáveis.
Compare significado estruturado, não bytes. Ignore IDs de trace e horários gerados. Compare classe de status, coleções ordenadas conforme o contrato, autorização, campos, categoria de erro e totais. Guarde uma amostra removendo dados sensíveis para cada nova assinatura, não cada resposta. O armazenamento de comparação não pode virar outra cópia dos dados de produção.
Executar gravações sombra e reverter a transação costuma ser inseguro. Chamadas externas, alocação de sequência, publicação em fila e gatilhos podem escapar. Valide a gravação com tráfego registrado num ambiente isolado, ou execute a lógica pura sobre um snapshot suprimindo o adaptador de commit. Diga exatamente o que foi testado.
Comparações de desempenho exigem o mesmo cuidado. Uma sombra posterior pode aproveitar cache aquecido; execução paralela adiciona carga que nenhum lado vê sozinho. Meça latência e recursos, mas não declare vencedor sem controlar cache, mistura de solicitações e carga extra.
O critério de saída combina cobertura e comportamento correto. Acompanhe operações, papéis de autorização, formatos de dados e caminhos de erro exercitados. Dez milhões de consultas comuns não provam uma reversão rara. Roteie a coorte quando seu conjunto real for observado ou testado deliberadamente.
A virada é uma máquina de estados
Uma virada reversível move uma capacidade por estados nomeados com transições protegidas. Um item no calendário pode autorizar a transição, mas o relógio não decide se é segura. Operadores devem ver autoridade, rota, direção da replicação, atraso e ação de retorno numa página.
Use estados que descrevem fatos:
old_only: o lado antigo atende e grava; o novo pode estar vazio.old_authority: os dois recebem dados atuais; usuários ainda usam o antigo.new_canary: uma coorte determinística usa o novo; gravações antigas seguem atuais.new_primary: todo tráfego elegível usa o novo; o antigo fica pronto para retorno.new_only: a janela fechou e mutações antigas foram desativadas.
Cada transição precisa de condições legíveis por máquina. new_canary pode exigir zero discrepância inexplicada, nenhum evento acima do atraso permitido, cobertura sombra de operações críticas e retorno de rota testado. new_primary acrescenta capacidade livre, posse de tarefas, suporte preparado e confirmação de que entradas não HTTP seguem a mesma autoridade.
Mantenha o comando pequeno e idempotente. Ele atualiza uma rota versionada, não implanta código, muda esquema, esvazia filas e reinicia workers. Se cinco ações precisam ocorrer no mesmo minuto, uma atrasará e o retorno ficará ambíguo.
Separe parar de voltar. Parar congela a expansão mantendo os papéis. Voltar envia tráfego ao antigo porque uma condição falhou. Reparar dados corrige o estado após entender a falha. Copiar automaticamente destino para origem durante um alarme pode espalhar corrupção mais rápido que o diagnóstico.
Pratique a transição inversa em produção antes da virada ampla. Envie uma coorte ao novo, crie e atualize registros, devolva ao antigo e confirme que continuam corretos e acessíveis. Um documento que nunca moveu estado real é teoria.
O retorno expira quando o lado antigo para de aprender
O sistema antigo só continua apto ao retorno enquanto recebe toda mudança necessária para retomar a autoridade. Servidores ligados não ajudam se o banco ficou para trás após a primeira gravação nova. Defina a janela em termos de dados: operações replicadas de volta, atraso aceitável e mudanças que o modelo antigo não representa.
A replicação reversa fica difícil quando a arquitetura nova permite estados que o esquema antigo não expressa. Adie essas funções ou crie representação compatível antes da virada. Se o novo aceita vários ajustes onde o antigo tem um valor, não prometa retorno depois do segundo ajuste sem conseguir preservá-lo no caminho antigo.
Use uma sequência de expansão e contração do esquema. Adicione campos e leitores que tolerem as duas formas. Preencha a nova. Troque gravadores. Observe. Remova a antiga só ao fechar a janela. Mudanças destrutivas, enums reutilizados e campos encurtados apagam o retorno mesmo com roteamento reversível.
Trabalho agendado deve ter um dono. A rota pode mandar tráfego interativo ao novo enquanto dois agendadores geram extratos ou fecham casos. Dê a cada tarefa um lease ou sinal de autoridade governado pelo mesmo estado e registre quem assumiu cada execução. No retorno, transfira o lease antes da rota se a tarefa altera dados lidos pelo caminho antigo.
CodeHero trata esse contrato de coexistência como parte da reescrita: o novo sistema em Go, Rust ou TypeScript é comparado com tráfego de produção registrado por um harness de paridade, e o projeto é entregue em menos de 30 dias. Essa promessa curta não substitui condições de aceitação nem política de retorno do cliente; impede que as decisões sejam escondidas num programa longo.
Defina a condição que encerra a reversibilidade. Pode ser o primeiro estado exclusivo do novo, remoção da replicação reversa, mudança destrutiva ou fim do acordo de manter ambos os lados. O responsável deve aprová-la. Sem um ponto nomeado, a equipe o descobrirá no incidente quando o retorno já acabou.
A última mudança de rota deve ser rotineira
A virada final é segura quando muda apenas a rota padrão de uma capacidade cujas coortes já rodaram no novo lado. Código, dados, tarefas, controles, painéis, instruções de plantão e retorno já estão em produção. O risco restante é escala, então capacidade e filas merecem mais atenção que correção funcional.
Antes da mudança, registre revisão exata, posições, execução de reconciliação, exceções abertas, aprovador e limite de retorno. Confirme que o lado antigo recebe toda a carga imediatamente. Reduzi-lo antes do fim economiza pouco e transforma uma edição reversível em restauração de capacidade.
Mude a rota em etapas compatíveis com o domínio de falha. Um locatário expõe dados específicos. Uma região expõe dependências. Percentual só serve quando a afinidade do fluxo é garantida. Pause até observar a tarefa ou callback relevante mais lento, não um gráfico arbitrário de cinco minutos.
Observe sintomas percebidos: categoria de erro, latência de cauda, idade da fila, invariantes quebradas, recusas de autorização e contatos do suporte. CPU e memória podem parecer calmas enquanto faturas somem da busca porque um consumidor parou. Ligue cada limite a um sinal e uma janela nomeados.
Deixe um operador responsável pelo comando e outro pelas condições. O segundo precisa de autoridade para parar sem negociar no canal do incidente. Registre ambas as decisões. A separação detecta painéis vencidos, regras mal entendidas e o erro de considerar silêncio como aprovação.
Quando o limite disparar, execute o retorno testado e preserve evidências. Não improvise correção adiante enquanto erros crescem. Com o tráfego estável no antigo, congele gravações de destino se preciso, registre posições e diagnostique. A reversibilidade compra tempo apenas quando usada.
Após fechar a janela, remova o mecanismo deliberadamente. Desative gravadores antigos, revogue credenciais, pare consumidores reversos, arquive evidências e conserve o histórico. Um relay esquecido pode ressuscitar dados antigos meses depois. Um fallback esquecido pode mandar solicitações a uma aplicação sem monitoramento.
A migração termina quando o sistema antigo deixa de ser necessário, não quando a primeira solicitação chega ao novo. Até lá, trate estado de rota, posição de evento e evidência de reconciliação como dados de produção. Se algo faltar, a virada depende da memória, justamente quando o alerta a torna menos confiável.
Perguntas frequentes
Uma migração pode mesmo ocorrer sem inatividade?
Sim, se cada solicitação mantiver um destino válido enquanto rota e autoridade dos dados mudam separadamente. Isso significa ausência de parada geral causada pela migração, não sucesso garantido de cada solicitação.
O que é roteamento Strangler numa migração?
Ele coloca um ponto de decisão controlado ao redor das implementações e move uma capacidade ou coorte por vez. Falha quando tarefas, filas, clientes ou callbacks contornam esse ponto.
Gravações duplas são seguras durante a migração?
Duas gravações diretas na thread não são, pois uma pode confirmar e a outra falhar. Confirme mudança e outbox juntas, depois aplique um evento idempotente ao outro armazenamento.
Como carregar dados enquanto usuários gravam?
Ligue o snapshot a uma posição e aplique eventos posteriores em ordem. Outra opção é condicionar cada upsert à versão de origem para dados antigos não sobrescreverem eventos novos.
O que a reconciliação deve comparar?
Compare invariantes de negócio e identificadores estáveis, não linhas brutas. Inclua ambos os valores, diferença, posições, versão e evidências para reproduzir cada discrepância.
Quando leituras sombra são seguras?
Em operações sem efeitos, com prazo próprio que não atrase o usuário. Marque-as para impedir mensagens, mudanças de sessão, eventos ou chamadas faturáveis.
Quanto tráfego um canário deve receber?
Comece com uma coorte determinística, não um percentual aleatório. Ela deve ser fácil de reverter e rica para exercitar fluxos, papéis, tarefas e callbacks.
O que torna a virada reversível?
O lado antigo deve ficar atual, capaz e pronto para toda a carga. Isso exige fluxo reverso, esquemas compatíveis, dono único de tarefas e retorno testado com estado real.
Quando fechar a janela de retorno?
Numa fronteira explícita e aprovada, como o primeiro estado exclusivo do novo ou uma mudança destrutiva. Algumas horas tranquilas não bastam.
O que fazer depois da virada final?
Desative gravadores antigos, revogue credenciais, pare consumidores reversos, preserve evidências e remova fallbacks. A aplicação antiga sai quando nenhum caminho suportado consegue gravar ou devolver trabalho a ela.