Blog

Migração de fila sem perder mensagem: trocar o broker com o tráfego ligado

A migração estava marcada para as duas da manhã de um sábado, com trinta minutos de janela e um plano de três passos que cabia num bilhete: para o produtor, espera a fila esvaziar, aponta tudo para o broker novo. Às duas e dezoito a fila antiga ainda tinha quatro mil mensagens que não esvaziavam porque um consumidor lento continuava reprocessando, e às duas e trinta e um alguém apontou o produtor mesmo assim. Na segunda-feira o financeiro encontrou dezenove cobranças duplicadas e sete pedidos que nunca saíram do lugar. Este artigo mostra por que a janela de manutenção é a estratégia mais arriscada disponível, quais quatro garantias precisam ser inventariadas antes de escolher o método, como a fase de consumo duplo elimina a perda sem criar duplicidade, por que a ordem entre mensagens só sobrevive se você aceitar quebrar uma das duas propriedades, qual sequência de sete etapas migra com o tráfego ligado e reverte em qualquer ponto, e quais três indicadores dizem que a fila antiga pode ser desligada de verdade.

2026-09-07 / Arquitetura / 18 min

01

Por que a janela de manutenção é o plano mais arriscado

O plano de parar o produtor, drenar a fila e religar apontando para o broker novo parece o mais seguro porque elimina o período em que as duas filas existem ao mesmo tempo. Na prática ele concentra todo o risco num intervalo curto, sem ensaio e sem caminho de volta, e depende de uma premissa que quase nunca se sustenta: a de que a fila drena em tempo previsível.

A fila não drena em tempo previsível porque o tempo de drenagem não depende só do que está nela. Depende do consumidor mais lento, das mensagens que entram em nova tentativa e voltam para o fim, do lote que falha e é reprocessado inteiro, e da mensagem envenenada que ocupa uma partição indefinidamente. Uma fila com quatro mil mensagens e vazão de trezentas por segundo não leva treze segundos para esvaziar se dez por cento delas estão em ciclo de nova tentativa com espera exponencial de até cinco minutos.

O segundo problema é que a janela não tem reversão barata. Depois que o produtor foi apontado para o broker novo e um consumidor confirmou a primeira mensagem lá, voltar atrás significa ter duas fontes de verdade parciais, e nenhuma equipe toma essa decisão bem às três da manhã. Migração com tráfego ligado inverte essa relação: cada etapa é pequena, observável e reversível, e a decisão difícil nunca acontece sob pressão de relógio.

EstratégiaComo funcionaRisco principalReversão
Janela de manutençãoPara o produtor, drena, aponta para o broker novoA drenagem não termina dentro da janela e alguém corta mesmo assimCara, exige reprocessar ou reconciliar manualmente
Consumo duploConsumidor lê dos dois brokers, produtor migra depoisDuplicidade se o consumidor não for idempotenteImediata, basta parar de ler do broker novo
Ponte entre filasUm processo copia mensagem do broker antigo para o novoA ponte vira ponto único e pode duplicar em falha parcialImediata, basta desligar a ponte
Produção duplaProdutor publica nos dois, consumidor migra depoisDivergência quando uma publicação falha e a outra nãoImediata, mas exige decidir o que fazer com o já publicado
Roteamento por porcentagemFração do tráfego novo vai para o broker novoOrdem entre mensagens do mesmo agregado quebra entre brokersImediata, basta zerar a porcentagem

As duas linhas do meio são as que resolvem a maioria dos casos, e a escolha entre elas depende de quem você controla. Se o produtor é código seu, produção dupla e roteamento por porcentagem são possíveis. Se o produtor é um parceiro externo, um dispositivo em campo ou um serviço legado que ninguém quer tocar, a ponte entre filas é o único caminho, e ela precisa ser tratada com o cuidado de um componente de produção, não como script temporário.

02

As quatro garantias que precisam ser inventariadas antes

Migrar broker é migrar as garantias que o sistema assumia sem escrever em lugar nenhum. Antes de escolher o método, cada uma das quatro precisa de resposta explícita, porque o método correto muda conforme a resposta.

  1. Entrega. O broker antigo garantia pelo menos uma entrega, no máximo uma, ou exatamente uma dentro de um escopo limitado. Se o consumidor foi escrito assumindo no máximo uma e o broker novo entrega pelo menos uma, cada nova tentativa vira efeito duplicado. Essa é a origem das dezenove cobranças duplicadas do exemplo de abertura.
  2. Ordem. A ordem era global, por partição, por chave, ou não existia. Ordem global é a mais cara de preservar numa migração e a menos frequentemente necessária de verdade. Ordem por chave é a que o negócio quase sempre precisa e a que quebra silenciosamente quando duas filas coexistem.
  3. Persistência e confirmação. Quando o produtor recebe a confirmação de publicação, a mensagem já está em disco e replicada, ou apenas aceita em memória. Brokers diferentes têm padrões diferentes aqui, e uma migração que troca uma configuração conservadora por uma permissiva perde mensagens só em falha de nó, que é justamente o caso que ninguém testa.
  4. Retenção e reprocessamento. A mensagem some depois de confirmada, ou fica retida por um período e pode ser relida do começo. Essa diferença define se a reversão consiste em reapontar um cursor ou em reconstruir estado a partir de outra fonte, e é o que separa uma migração reversível de uma migração de mão única.

A garantia de entrega merece um destaque prático. Nenhum broker entrega exatamente uma vez de ponta a ponta na presença de falha de rede, porque o produtor que não recebe a confirmação não sabe se a publicação aconteceu. O que existe é deduplicação dentro de uma janela, do lado do broker, e idempotência do lado do consumidor. A migração é o momento em que essa diferença deixa de ser teórica: durante a coexistência dos dois brokers, a mesma mensagem pode chegar por dois caminhos, e a única defesa que funciona em todos os casos é a chave de idempotência do lado de quem aplica o efeito.

// Consumidor idempotente por chave de negocio, nao por identificador
// gerado pelo broker: durante a migracao a mesma mensagem chega com
// identificadores diferentes pelos dois caminhos.
const JANELA_DEDUP_SEGUNDOS = 60 * 60 * 24 * 7;

async function processarMensagem(mensagem) {
  // A chave vem do payload e e estavel entre brokers. Usar o offset,
  // o messageId do broker ou o deliveryTag quebra na coexistencia.
  const chave = `efeito:${mensagem.tipo}:${mensagem.agregadoId}:${mensagem.eventoId}`;

  // SET com NX e a operacao atomica que decide quem processa. Sem NX,
  // dois consumidores leem "nao existe" ao mesmo tempo e ambos aplicam.
  const primeiro = await redis.set(chave, 'processando', {
    NX: true,
    EX: JANELA_DEDUP_SEGUNDOS,
  });

  if (!primeiro) {
    const estado = await redis.get(chave);
    if (estado === 'concluido') return { status: 'duplicada_ignorada' };
    // Outro consumidor pegou e ainda nao terminou: devolve para nova
    // tentativa em vez de confirmar, senao a mensagem some se o outro falhar.
    throw new ErroTentarDepois('efeito_em_andamento');
  }

  try {
    await aplicarEfeito(mensagem);
    await redis.set(chave, 'concluido', { EX: JANELA_DEDUP_SEGUNDOS });
    return { status: 'processada' };
  } catch (erro) {
    // Libera a chave para que a nova tentativa possa reprocessar.
    await redis.del(chave);
    throw erro;
  }
}

O detalhe que mais falha em implementações reais é o bloco de erro. Sem o descarte da chave, uma falha transitória no efeito deixa a marca de processamento no lugar e a nova tentativa é descartada como duplicata, o que transforma um erro recuperável em mensagem perdida. É o modo de falha mais difícil de detectar depois, porque não gera erro nem alerta: a mensagem simplesmente não produziu efeito e ninguém percebe até a conciliação.

03

Consumo duplo: a fase que elimina a perda

A ordem correta de migração é contraintuitiva: o consumidor migra primeiro, o produtor migra depois. Quem começa apontando o produtor para o broker novo cria imediatamente uma janela em que existem mensagens do lado novo sem ninguém para lê-las, e mensagens do lado antigo que ainda precisam ser drenadas. Quem começa pelo consumidor cria uma janela em que existe capacidade de leitura sobrando dos dois lados, que é inofensiva.

Fase 1 - so o broker antigo
  produtor --> [FILA ANTIGA] --> consumidor

Fase 2 - consumo duplo (consumidor le dos dois, so o antigo tem trafego)
  produtor --> [FILA ANTIGA] --\
                                >--> consumidor (idempotente)
               [FILA NOVA]   --/     nenhuma mensagem chega pela nova ainda
  ponto de verificacao: consumidor conectado, com 0 mensagens lidas da nova

Fase 3 - producao percentual (5% -> 25% -> 50% -> 100%)
  produtor --5%--> [FILA NOVA]   --\
           -95%--> [FILA ANTIGA] --/--> consumidor
  ponto de verificacao: taxa de erro igual dos dois lados,
  latencia de ponta a ponta comparavel, 0 duplicatas aplicadas

Fase 4 - drenagem da fila antiga (producao 100% na nova)
  produtor -100%-> [FILA NOVA]   --\
                                    >--> consumidor
               [FILA ANTIGA] ------/     drenando o residual
  ponto de verificacao: profundidade da antiga em 0 por
  tempo maior que o backoff maximo da nova tentativa

Fase 5 - desligar
  produtor --> [FILA NOVA] --> consumidor
  a fila antiga sai do consumidor so depois da fase 4 confirmada

A fase dois é a que dá segurança ao resto e a que mais gente pula. Ela não move tráfego nenhum: serve para provar que o consumidor consegue se conectar ao broker novo, autenticar, desserializar o formato de mensagem, respeitar o limite de mensagens em voo e confirmar corretamente. Todos esses são pontos de falha reais numa troca de broker, e descobri-los com zero mensagens em jogo custa uma tarde, enquanto descobri-los com cinquenta por cento do tráfego custa um incidente.

A fase quatro tem uma armadilha de tempo. A fila antiga chegar a zero uma vez não significa que ela esvaziou: mensagens em ciclo de nova tentativa reaparecem depois do tempo de espera, e se a espera exponencial chega a quinze minutos, a fila pode ficar em zero por dez minutos e voltar a ter conteúdo. O critério correto é profundidade zero por um período maior que o maior tempo de espera configurado, somado ao tempo de visibilidade da mensagem, e não a primeira leitura de zero no painel.

04

Ordem entre mensagens: escolher qual propriedade quebrar

Durante a coexistência, mensagens do mesmo agregado podem estar nas duas filas ao mesmo tempo, e não existe forma de ordená-las entre si sem coordenação externa. Essa é a restrição dura da migração com tráfego ligado, e ela não tem solução gratuita: você escolhe qual propriedade quebrar durante a transição.

AbordagemO que preservaO que sacrificaQuando usar
Migrar por chave de agregadoOrdem dentro de cada agregado, sempreMigração deixa de ser percentual e vira por fatia de chaveOrdem por chave é requisito de negócio
Drenar antes de mover a chaveOrdem total dentro do agregado migradoLatência da última mensagem do agregado durante a trocaAgregados com volume baixo e picos raros
Aceitar reordenação e versionarDisponibilidade e simplicidade da migraçãoOrdem, que passa a ser resolvida pelo consumidorO consumidor já descarta versão antiga por número
Pausar o agregado por segundosOrdem, com custo previsível e limitadoDisponibilidade daquele agregado durante a pausaPoucos agregados críticos e pausa tolerável
Ignorar o problemaNadaConsistência, de forma silenciosa e difícil de detectarNunca, e é o que mais acontece na prática

A primeira linha é a que mais vale conhecer, porque transforma a migração percentual em algo compatível com ordem. Em vez de sortear cinco por cento das mensagens, você usa um hash estável do identificador do agregado e migra a fatia inteira de uma vez: todas as mensagens do pedido 4711 vão para o broker novo, ou nenhuma vai. Isso preserva ordem por chave por construção, e o percentual continua controlável, só que em degraus de fatia em vez de mensagem a mensagem.

// Roteamento por fatia estavel de chave: preserva ordem por agregado
// durante a coexistencia dos dois brokers.
import { createHash } from 'node:crypto';

const TOTAL_FATIAS = 128;

const fatiaDe = (chaveAgregado) => {
  const digest = createHash('sha256').update(chaveAgregado).digest();
  return digest.readUInt32BE(0) % TOTAL_FATIAS;
};

// Vem de configuracao dinamica, nao de variavel de ambiente: a mudanca
// precisa valer sem reimplantacao para que a reversao seja imediata.
const fatiasMigradas = () => configuracao.get('fila.fatiasMigradas', 0);

async function publicar(mensagem) {
  const fatia = fatiaDe(mensagem.agregadoId);
  const destino = fatia < fatiasMigradas() ? brokerNovo : brokerAntigo;

  await destino.publicar({
    ...mensagem,
    // Carimbo de rota no proprio payload: sem isso e impossivel
    // reconstruir depois por onde cada mensagem passou.
    rota: { broker: destino.nome, fatia, migradoEm: Date.now() },
  });
}

// Aumento de degrau: 0 -> 6 -> 32 -> 64 -> 128 fatias.
// Cada degrau so avanca depois de uma janela de observacao completa,
// e voltar um degrau nao gera reordenacao porque a fatia inteira volta.

A reversão nesse desenho tem uma propriedade que a migração aleatória não tem: reduzir o número de fatias migradas devolve o agregado inteiro para o broker antigo, e como todas as mensagens dele estavam do mesmo lado, não existe intercalação entre os dois. A única mensagem que pode ficar fora de ordem é a que estava em voo no instante exato da mudança de degrau, e isso é resolvido drenando a fatia antes de mover, ou aceitando que o consumidor descarte versão antiga por número de versão.

05

A sequência de sete etapas com o tráfego ligado

A sequência abaixo funciona para os dois casos, produtor sob seu controle ou não, e cada etapa tem um critério de saída objetivo. A regra que sustenta o método é simples: nenhuma etapa avança por horário, todas avançam por indicador.

  1. Torne o consumidor idempotente antes de tocar em qualquer broker. Chave de idempotência derivada do payload, não do identificador do broker, e verificação de que reprocessar a mesma mensagem duas vezes não muda o resultado. Critério de saída: teste que republica a mesma mensagem cinco vezes e confirma um único efeito.
  2. Suba o broker novo e conecte o consumidor a ele sem tráfego. Autenticação, formato, limite de mensagens em voo, confirmação, tratamento da fila morta. Critério de saída: consumidor conectado por vinte e quatro horas com zero mensagens lidas e zero erro de conexão.
  3. Publique tráfego sintético no broker novo. Mensagens marcadas que percorrem o caminho completo e produzem efeito verificável em ambiente controlado. Critério de saída: latência de ponta a ponta medida e comparável à do broker antigo, e comportamento de nova tentativa igual ao esperado.
  4. Migre a primeira fatia de chaves, algo entre três e cinco por cento. Observe por pelo menos uma janela que contenha um pico de tráfego, não apenas trinta minutos de horário calmo. Critério de saída: taxa de erro, latência e contagem de duplicatas aplicadas iguais entre as duas rotas.
  5. Aumente em degraus com observação entre eles. Vinte e cinco, cinquenta, cem por cento das fatias. Cada degrau precisa passar por um ciclo completo de operação, incluindo uma implantação da aplicação, para provar que a reconexão funciona sob o broker novo.
  6. Drene a fila antiga e confirme a drenagem pelo critério certo. Profundidade zero por período maior que o maior tempo de espera de nova tentativa somado ao tempo de visibilidade. Verifique também a fila morta antiga, que costuma ser esquecida e contém exatamente as mensagens que mais precisam de tratamento.
  7. Desconecte o consumidor do broker antigo e só então desprovisione. Mantenha o broker antigo ligado, sem tráfego, por pelo menos um ciclo de retenção completo. Ele é o caminho de volta e o registro de auditoria do período de transição, e desligá-lo cedo troca economia pequena por risco grande.

A etapa cinco tem um detalhe que costuma ser descoberto tarde: incluir uma implantação da aplicação dentro de cada degrau. Reconexão a broker é um dos comportamentos menos exercitados de um serviço, e uma configuração de reconexão que não funciona só aparece quando o processo reinicia. Descobrir isso com cinco por cento do tráfego é um bilhete de tarefa, descobrir com cem por cento é um incidente com mensagens paradas em fila.

Quando o produtor não é seu, as etapas quatro e cinco mudam de forma mas não de lógica. A ponte entre filas assume o papel do roteamento: ela lê do broker antigo e publica no novo, e o percentual passa a ser aplicado dentro dela pela mesma função de fatia. A ponte precisa confirmar no broker antigo somente depois de receber a confirmação de publicação no novo, nessa ordem, porque a ordem inversa perde mensagem em qualquer falha entre as duas operações, e ela precisa da mesma chave de idempotência, porque confirmar depois de publicar significa que uma falha no meio republica a mensagem.

06

Os três indicadores que autorizam desligar a fila antiga

O desligamento é a decisão que mais costuma ser tomada por sensação, e é a que tem consequência mais permanente, porque depois dela não há caminho de volta. Os três indicadores abaixo transformam essa decisão em verificação.

IndicadorO que medeCritério para desligarO que ele pega
Profundidade residual sustentadaMensagens na fila antiga, incluindo a fila mortaZero por período maior que o maior tempo de espera mais a visibilidadeMensagem em ciclo de nova tentativa que ressurge depois
Cobertura de produtoresProdutores distintos que publicaram no broker antigo na janelaZero por um ciclo de negócio completo, incluindo rotinas mensaisTrabalho agendado raro que ninguém lembrou de migrar
Duplicatas aplicadasEfeitos bloqueados pela chave de idempotência por origemEstável e explicável, sem crescimento durante a coexistênciaPonte ou produtor duplo publicando o mesmo evento duas vezes

O segundo indicador é o que mais evita incidente pós-migração. Sistemas reais têm produtores que publicam uma vez por mês, no fechamento, e que ninguém inventariou porque não aparecem no gráfico de tráfego do dia. Medir produtores distintos por janela, em vez de volume de mensagens, revela esses casos: um produtor que publicou uma única mensagem em trinta dias tem o mesmo peso de um que publicou um milhão, e é exatamente ele que quebra depois do desligamento.

O terceiro indicador precisa ser lido pela origem, não pelo total. Um número absoluto de duplicatas bloqueadas não diz nada sozinho, porque nova tentativa legítima produz duplicata bloqueada e isso é o sistema funcionando. O que importa é a quebra por rota: se as duplicatas vindas da ponte crescem enquanto as vindas do consumo normal ficam estáveis, a ponte está republicando, e esse é o defeito que produz cobrança duplicada mesmo com consumidor idempotente, quando a janela de deduplicação é menor que o intervalo entre as duas publicações.

FAQ

Perguntas frequentes

Vale a pena manter a ponte entre filas depois da migração, como camada de compatibilidade permanente?

Quase nunca, e o motivo é que a ponte tem um custo que só aparece meses depois. Enquanto ela existe, o sistema tem dois brokers em produção, duas configurações de retenção, duas políticas de fila morta, dois conjuntos de credenciais para rotacionar e dois lugares onde procurar uma mensagem durante um incidente. Nada disso é dramático isoladamente, mas junto significa que toda investigação começa com a pergunta de por qual caminho a mensagem veio, e essa pergunta custa minutos em cada incidente pelo resto da vida do sistema. Existe um caso legítimo, que é o produtor externo que você não controla e que tem um cronograma próprio de migração, tipicamente um parceiro com contrato. Nesse caso a ponte deixa de ser transitória e vira componente de produção, e a consequência prática é que ela precisa ser tratada como tal: alerta próprio de atraso e de erro, teste automatizado, dono definido, documentação de comportamento em falha parcial e revisão periódica. O padrão que dá errado é o intermediário, em que a ponte fica ligada por conveniência sem dono e sem alerta, e alguém descobre seis meses depois que ela parou há três semanas quando o parceiro reclama de dados faltando. Se a decisão for manter, escreva a data de revisão junto com a decisão, porque uma ponte sem data de fim nunca é removida.

Como testar a migração antes de fazer, se o ambiente de homologação não tem o volume nem os produtores reais de produção?

Homologação não vai reproduzir o volume, e insistir nisso costuma consumir mais tempo do que a migração inteira. O que ela reproduz bem é comportamento, e comportamento é onde a maioria das migrações falha: formato de mensagem, autenticação, semântica de confirmação, política de nova tentativa, tratamento de fila morta e reconexão após queda. Testar essas seis coisas em homologação já elimina a maior parte dos incidentes, e um teste específico vale mais que todos os outros: derrubar o broker novo no meio do consumo e verificar que nenhuma mensagem foi perdida nem duplicada. Ele é fácil de executar e falha com frequência surpreendente. Para o que homologação não cobre, existem duas técnicas melhores do que tentar simular volume. A primeira é o espelhamento de tráfego real: copiar mensagens de produção para o broker novo e processá-las com um consumidor que aplica efeito em ambiente separado. Isso exercita o formato real, incluindo aquele campo que só um produtor manda e que ninguém documentou. A segunda é a própria migração por fatia, que é o teste em produção com dano limitado por construção: cinco por cento das chaves atravessam o caminho completo com efeito real, e se algo quebrar, o alcance é conhecido de antemão e a reversão é uma mudança de configuração. Migração por degraus não é uma alternativa ao teste, é a forma de teste que produção aceita.

A ordem das mensagens realmente importa no meu caso, ou estou complicando uma migração que poderia ser simples?

Na maioria dos sistemas a ordem global não importa e a ordem por chave importa em poucos fluxos específicos, então vale medir em vez de assumir qualquer um dos extremos. O teste mental que resolve rápido é este: para cada tipo de mensagem, pergunte o que acontece se duas mensagens do mesmo agregado forem aplicadas na ordem inversa. Em atualização de cadastro com sobrescrita de campo, o resultado é dado antigo vencendo dado novo, o que é uma falha silenciosa e real. Em incremento de contador, a ordem não muda nada. Em máquina de estados com transição válida declarada, a mensagem fora de ordem é rejeitada e vira nova tentativa, o que é comportamento correto e não perda. Essa separação costuma mostrar que ordem importa em dois ou três fluxos, não em todos, e isso muda a estratégia: migre esses fluxos por fatia de chave e o resto por percentual simples, em vez de submeter o sistema inteiro à restrição mais cara. Vale registrar que a proteção mais durável não é a ordem e sim o número de versão no payload, com o consumidor descartando aplicação de versão menor que a já aplicada. Quem tem isso pode migrar sem se preocupar com ordenação entre brokers, porque a reordenação deixa de produzir efeito errado e passa a produzir apenas descarte. Se o sistema não tem número de versão, adicioná-lo antes da migração costuma ser mais barato que preservar ordem durante ela, e o benefício permanece depois que a migração termina.

Trocar o broker é migrar garantias, não endereços de conexão

A troca de fila raramente falha por causa do broker novo: falha porque a garantia de entrega mudou sem ninguém notar, porque a ordem por chave quebrou durante a coexistência, ou porque a fila antiga foi desligada antes da última mensagem em nova tentativa reaparecer. Posso revisar as garantias que a sua fila entrega hoje e definir o inventário de entrega, ordem, persistência e retenção, a chave de idempotência que sobrevive à coexistência dos dois brokers, o roteamento por fatia de chave que preserva ordem durante os degraus, a sequência de migração reversível em qualquer ponto e os indicadores que autorizam desligar a fila antiga.