0035 — Eleição de instância no relay M2M¶
Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-09-12 · Decidido em: 2026-09-09
Complementa a 0021, que fixa a entrega at-least-once e o
filtro da SAIDA por tipo.
Contexto¶
O filtro por tipo da 0021 particiona a fila entre apps diferentes. Ele não separa réplicas
do mesmo app: elas registram os mesmos tipo, casam o mesmo predicado e drenam a mesma linha. E
duas instâncias do mesmo app coexistem na casa por motivos normais — sobreposição durante o deploy,
réplica, religamento do fallback JVM. Nessa janela o efeito externo (o PUT no destino) sai em
dobro, não por corrida rara, mas toda vez que as duas estão no ar.
Decisão¶
O tick do relay roda sob advisory lock do Postgres chaveado pelo nome do app
(pg_try_advisory_lock(hashtext('<micronaut.application.name>_relay_m2m'))). A réplica que não
adquire o lock desiste do tick — não roda, não enfileira — e o próximo tick reencaixa.
- A chave é por app, não global à tabela. Dois apps que dividem a mesma
mensagem_m2mdrenamtipodisjuntos e não têm por que bloquear um ao outro. O lock compõe com o filtro portipo, não o substitui. - O lock é por sessão (
pg_try_advisory_lock), não por transação: o relay seleciona fora de transação e despacha cada mensagem na transação curta dela, então um lock transacional soltaria antes do envio, que é justamente o trecho a proteger. try, não bloqueante: a réplica perdedora desiste em vez de enfileirar trabalho duplicado.- Duplicação evitável × inerente. Evitável é duas réplicas fazendo o mesmo trabalho ao mesmo tempo — a lib tem como não emitir. Inerente é o crash entre o efeito externo e o commit — o destino já viu a chamada e o remetente não sabe, e a reentrega é a única saída correta. Remover a evitável não é exactly-once: a garantia segue at-least-once e a idempotência do receptor por chave natural segue precondição.
Alternativas descartadas¶
- Reivindicar a linha da fila (
FOR UPDATE SKIP LOCKEDou claim persistido). O lock de linha seria solto antes doPUT, pelo mesmo motivo do lock transacional. Reivindicar de verdade exigiria estado persistido (em voo, marca de reivindicação, reaper de órfão), que é o estado que a 0021 descartou ao deferir exactly-once. - Chave global à tabela. Faria um app coalescer por causa do outro, sem ganho.
- Desligar o relay por config em uma das réplicas. Não compõe com deploy, réplica nem fallback, e some no dia em que alguém esquece a env.
Consequências¶
- Sob N réplicas, a adoção não pede nada do app: sem DDL, sem coluna, sem config obrigatória; o nome
do lock deriva do
micronaut.application.name. A escotilhamensageria.relay.cluster-lock(defaulttrue) desliga a guarda sem release da lib, e desligar devolve a duplicação evitável. - A mecânica geral — qualquer job agendado sob N réplicas elege instância — mora em
java-micronaut §Banco, com a guarda
que avisa no
pipeline.yml.