Pular para conteúdo

0028 — Native e jar no ar, com a troca feita instância a instância

Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-09-15 · Decidido em: 2026-09-15

Contexto

A casa deploya o server Micronaut como binário native por padrão, e o jar só com motivo registrado (decisão central 0033). O integrador compilava para native desde a 0020, mas rodava só jar nas seis instâncias, uma por cliente. A 0033 cita como motivo bloqueios de locale, FCM e Jackson no upload, além da falta de e2e native, e nenhum deles estava registrado aqui.

Medição de 2026-09-15: binário -Ob compilado no Windows, Postgres de dev e as mesmas 47 checagens contra o jar e contra o native (views, estáticos, ingest XADM, upload do PowerSync, heartbeat, export XLSX, /api/health, OpenAPI).

  • Locale: o push formata moeda em pt-BR, e a GraalVM embarca só en. O build ganhou -H:IncludeLocales=pt-BR.
  • Jackson no upload: o upload do PowerSync lia o corpo pelo ObjectMapper do Jackson, por reflexão. Passou ao JsonMapper do Micronaut Serde, gerado em compile-time. O ingest XADM (árvore do Jackson) e o registro do /api/health (um Map) passam no binário sem metadata extra.
  • Paridade: 45 de 47 nos dois. As duas divergências são as mesmas no jar e no native e não vêm do AOT: /favicon.ico fora da raiz dos estáticos, e /admin/mensageria sem cabeçalho Accept.
  • Custo: boot até o /health em 3,6 s (native) contra 8,0 s (jar); RSS de 137 MiB contra 365 MiB. O número absoluto muda no Linux; a proporção é o que se mediu.
  • Fora da medição: o envio FCM real (o push fica desligado em dev), a formatação pt-BR no binário (só o push formata), o build Linux do Dockerfile.native e uma suíte e2e native do integrador.

Decisão

Correção 2026-09-15: o reconcile do central-backend lê int-jar.<cli>.xadm.biz como a mesma instância (Coolify, dois recursos no mesmo domínio), e o jar movido para esse host não perde a instância. O jar de troca fica no int.<cli>, dividindo o tráfego com o native, até a instância provar o native; depois vai para o int-jar.<cli>.xadm.biz, e o native fica dono do int.<cli>. O jar só se move quando o reconcile no ar aceita esse host; antes disso, fica no int.<cli>. Procedimento no runbook de implantação.

O build.targets passa a ["native", "jar"], e a troca é feita instância a instância.

  • Cada cliente ganha o recurso integrador-server-<cli>-native-pull, que puxa a tag native-amd64, com o mesmo domínio da instância (int.<cli>.xadm.biz) como primeiro FQDN. O control-plane tira a instância só do primeiro FQDN e só na forma int.<cli>.xadm.biz: um jar movido para outro domínio perderia a instância e colidiria com os outros cinco no mapa de deploy.
  • Enquanto os dois estão no ar, o proxy divide o tráfego da instância entre jar e native. A divisão se mede pelo flavor do /health.
  • A troca começa por uma instância com o push ligado, que serve de piloto do FCM. As demais ganham o -native-pull uma a uma, cada uma só depois de smoke verde e GlitchTip sem exceção nova na anterior.
  • O jar só para quando sai do build.targets, numa release própria, depois que as seis instâncias têm native. Enquanto jar estiver no build.targets, cada release redeploya o -jar-pull, e um recurso parado voltaria a subir. Depois dessa release, o -jar-pull fica parado como fallback.

Consequências

  • A release builda as duas imagens, e o deploy dispara os dois alvos em todas as instâncias. Os seis recursos -native-pull existem antes da primeira release com native (implantação).
  • A release só sai depois que o xadm-commons publicar as versões de lib que este app declara: o Dockerfile.native builda num container limpo, que resolve dependências só pelo registro.
  • Durante a troca há duas cópias do app no mesmo banco. Elas não duplicam efeito: o relay da mensageria elege instância, a limpeza diária é idempotente, o backfill do manifesto é barrado pelo UNIQUE dos itens (uma corrida no boot sai como ERROR no log, sem dado duplicado), e a deduplicação do push passou da memória de cada cópia para uma reivindicação no banco.
  • O que ficou fora da medição é coberto pela ordem da troca: o FCM real se prova no piloto, e o build Linux e o binário no ar se provam no primeiro deploy, pelo smoke de cada instância. A suíte e2e native do integrador fica como pendência declarada.
  • Procedimento de build e diagnóstico: native-image.

Alternativas consideradas

  • Jar nesta release e native na seguinte, depois de um piloto: preterida pelo dono. A medição já cobre os bloqueios que a 0033 citava, e a troca por instância limita o risco do que não foi medido.
  • Só native, na próxima release: o FCM real e o build Linux iriam direto às seis instâncias, sem piloto e sem fallback no ar.