Blog

Quando 40 aplicações dividem destino: por que saímos do gerenciador de processos

11 de agosto de 2026 · Time Orion

Durante meses, o ERP de um cliente caía sozinho. Não com padrão de vazamento de memória, não sob carga, não depois de deploy: caía em horários aleatórios, várias vezes por dia, com 502 para o usuário e volta em segundos.

O log da aplicação não mostrava nada. Ela não travava — ela era terminada.

A causa

O servidor rodava dezenas de aplicações sob o mesmo gerenciador de processos. Entre elas, uma de terceiros com um watchdog próprio que, ao detectar um problema nela mesma, executava um comando de manutenção do gerenciador como remediação.

Esse comando recicla o daemon supervisor inteiro — e, com ele, todos os processos gerenciados.

Num dia, isso aconteceu 43 vezes. O ERP caiu 43 vezes por um problema que não era dele, disparado por um watchdog que estava fazendo exatamente o que tinha sido programado para fazer.

O nome disso é destino compartilhado: processos independentes que caem juntos porque dependem do mesmo supervisor.

A correção

Duas frentes, e as duas eram necessárias.

Curto prazo: corrigimos os watchdogs alheios para reiniciar apenas o próprio serviço, em vez de reciclar o daemon.

Estrutural: tiramos o ERP do gerenciador compartilhado e o colocamos numa unidade de serviço própria do sistema operacional. O init do sistema já é um supervisor — com isolamento por serviço, política de reinício e captura de log, sem intermediário que possa ser reciclado por engano.

As três linhas que aprendemos a não mexer

A unidade de serviço tem três configurações que parecem detalhe e são estruturais:

Environment=HOME=/root. Sem isso, o processo não encontra as credenciais de git da máquina — e o mecanismo de auto-atualização quebra de um jeito que não aparece em teste, só no dia da atualização.

KillMode=process. Esta é a mais sutil. O modo padrão mata o grupo de controle inteiro no restart. O problema: a própria aplicação dispara trabalhos de longa duração destacados — inclusive o que executa a atualização e reinicia o serviço. Com o modo padrão, o restart matava o processo que tinha pedido o restart, no meio do trabalho. Destacar o processo do grupo não basta: o grupo de controle pega mesmo assim.

TimeoutStopSec maior que o tempo de drenagem. A aplicação leva alguns segundos para encerrar conexões com elegância. Timeout menor que isso transforma todo restart em encerramento forçado — e encerramento forçado no meio de uma escrita é como se cria dado inconsistente que ninguém explica depois.

O que isso ensinou sobre supervisão

Supervisor é infraestrutura crítica, e destino compartilhado é uma decisão — não um detalhe. Colocar N aplicações sob o mesmo supervisor significa aceitar que uma pode derrubar as outras. Para um servidor de desenvolvimento, tudo bem. Para produção de coisas que não podem cair, não.

"Não é bug nosso" não conforta ninguém. O usuário via 502. A causa era um watchdog de outro produto. A responsabilidade de não compartilhar destino era nossa.

Log de aplicação não conta a história inteira. Nada nos nossos logs indicava a causa; o que resolveu foi olhar quem enviou o sinal e quando. Quando um processo é terminado de fora, a evidência está no supervisor, não na aplicação.

O deploy que veio junto

Aproveitamos a mudança para tornar o deploy atômico: a aplicação é construída num diretório novo e, só se a construção terminar bem, o diretório antigo é trocado pelo novo. Se falhar, nada é substituído e a versão no ar continua sendo a que funcionava.

Um detalhe que custou um incidente: num pipeline com saída redirecionada, o código de saída observado pode ser o do último comando da cadeia, e não o da construção. O resultado foi trocar uma versão boa por uma quebrada. Hoje a troca só acontece depois de conferir que o artefato de build existe de verdade.

O resumo

Se uma aplicação sua não pode cair, ela não deve dividir supervisor com dezenas de outras que você não controla. O init do sistema faz esse trabalho com isolamento real — e as poucas linhas de configuração que parecem opinativas são justamente as que sustentam atualização e encerramento limpo.

É a mesma família de decisão de preferir indisponibilidade a estado corrompido: escolher qual falha você aceita, antes de ela escolher por você.