Dominando a Política de OOM: Ajustando a Resposta do Systemd a Eventos de Falta de Memória
Aprenda a controlar o comportamento do killer de Out-of-Memory (OOM) do Linux usando systemd. Este guia explora as diretivas `OOMScoreAdjust` e `OOMPolicy` para proteger serviços críticos, influenciando quais processos são encerrados durante condições de baixa memória. Domine o ajuste de OOM do systemd para maior estabilidade e resiliência do sistema.
Dominando a Política de OOM: Ajustando a Resposta do Systemd a Eventos de Falta de Memória
Falhas de falta de memória raramente acontecem em um momento conveniente. Uma importação em lote recebe um arquivo maior que o normal, um serviço vaza memória durante a noite, um backup coincide com um pico de tráfego ou um deploy dobra o número de processos workers. Quando o Linux não consegue liberar memória suficiente para uma alocação, o kernel pode invocar o killer OOM e encerrar um processo para que a máquina possa continuar funcionando.
A parte desconfortável é que a vítima padrão pode não ser o serviço que você teria escolhido. Em um host compartilhado, você pode preferir que um worker de fila retentável morra antes da API principal. Em um servidor de banco de dados, você pode querer que SSH e monitoramento permaneçam ativos para que você possa recuperar a máquina. O systemd oferece duas opções para esse tipo de decisão: OOMScoreAdjust= e OOMPolicy=.
OOMScoreAdjust= influencia qual processo é selecionado. OOMPolicy= controla o que o systemd faz após um processo no serviço ser morto. Eles resolvem problemas diferentes, e misturá-los leva a runbooks ruins.
O que o Kernel está Pontuando
Cada processo Linux tem uma pontuação OOM, visível em /proc/<pid>/oom_score. Uma pontuação mais alta significa que o processo é uma vítima OOM mais provável. O kernel deriva essa pontuação do uso de memória e outros contextos, e então aplica o valor de ajuste de /proc/<pid>/oom_score_adj.
O OOMScoreAdjust= do systemd escreve esse ajuste para os processos que ele inicia. O intervalo é de -1000 a 1000.
-1000fornece a proteção mais forte e efetivamente desabilita o killing OOM para esse processo.- Valores negativos tornam o processo menos provável de ser morto.
- Valores positivos tornam o processo mais provável de ser morto.
0deixa o ajuste neutro.
A abordagem mais segura geralmente não é "proteger tudo que é importante". Se todos os serviços estão protegidos, o kernel tem menos escolhas úteis quando o host já está com pouca memória. Proteja um pequeno número de serviços e torne o trabalho descartável mais fácil de matar.
Para um serviço de API primário, um ajuste moderado geralmente é suficiente:
[Service]
OOMScoreAdjust=-300
Para um worker de fila que pode tentar novamente os jobs:
[Service]
OOMScoreAdjust=500
Esse worker pode morrer primeiro durante a pressão de memória, mas esse é o objetivo. Um job falho pode voltar para a fila. Um banco de dados morto ou host inacessível é um incidente maior.
O que OOMPolicy Realmente Faz
OOMPolicy= não marca uma unidade como "crítica", e não escolhe o primeiro processo a ser morto. Os valores suportados são continue, stop e kill.
continue: o systemd registra o evento OOM e deixa a unidade em execução se algum processo permanecer.stop: o systemd registra o evento e para a unidade de forma limpa.kill: se um processo na unidade for morto pelo OOM, os processos restantes nessa unidade são mortos como um grupo.
Use essa configuração para evitar serviços meio vivos. Se um serviço web multiprocesso perde um worker e continua aceitando tráfego em um estado quebrado, continue pode esconder a falha. OOMPolicy=kill torna a falha óbvia e permite que Restart=on-failure traga o serviço de volta em um estado limpo.
[Service]
OOMPolicy=kill
Restart=on-failure
RestartSec=5s
Para um job em lote com processos auxiliares, stop pode ser menos abrupto para os processos restantes:
[Service]
OOMPolicy=stop
O processo escolhido pelo kernel já se foi. stop afeta apenas o que o systemd faz com o resto do serviço, então não confie nele como um ponto de salvamento gracioso. Jobs de longa duração devem fazer checkpoint do seu próprio trabalho.
Um Padrão Prático de Ajuste
Comece classificando os serviços em três grupos.
Primeiro, identifique os serviços que mantêm o host recuperável: SSH, rede, monitoramento e a carga de trabalho principal. Dê apenas aos mais importantes ajustes negativos modestos.
Segundo, identifique os serviços que podem ser tentados novamente: workers, importadores, geradores de relatórios, processadores de imagem, warmers de cache e ajudantes de desenvolvimento. Dê a esses ajustes positivos.
Terceiro, decida se cada serviço pode continuar funcionando com segurança após um processo ser morto. Se não, use OOMPolicy=kill e uma política de reinicialização.
Uma substituição realista de worker pode ser assim:
# /etc/systemd/system/image-worker.service.d/oom.conf
[Service]
OOMScoreAdjust=500
OOMPolicy=kill
Restart=on-failure
RestartSec=10s
Um serviço de aplicação primário pode ser assim:
# /etc/systemd/system/api.service.d/oom.conf
[Service]
OOMScoreAdjust=-300
OOMPolicy=kill
Restart=on-failure
RestartSec=5s
Eu evitaria OOMScoreAdjust=-1000 a menos que você tenha testado o modo de falha. Se esse serviço protegido é o que está vazando memória, a máquina ainda precisa de uma maneira de se recuperar.
Aplicando e Verificando a Mudança
Use drop-ins em vez de editar arquivos de unidade empacotados:
sudo systemctl edit api.service
Após salvar a substituição, recarregue o systemd e reinicie o serviço:
sudo systemctl daemon-reload
sudo systemctl restart api.service
Verifique a unidade mesclada e os valores que o systemd vê:
systemctl cat api.service
systemctl show api.service -p OOMPolicy -p OOMScoreAdjust
Em seguida, inspecione o processo em execução:
PID=$(systemctl show api.service -p MainPID --value)
cat /proc/$PID/oom_score_adj
cat /proc/$PID/oom_score
oom_score_adj deve corresponder ao seu ajuste configurado. oom_score pode mudar conforme o processo usa mais ou menos memória.
Após um incidente, verifique tanto os logs da unidade quanto o log do kernel:
journalctl -u api.service --since "1 hour ago"
journalctl -k --since "1 hour ago" | grep -i oom
Em sistemas que usam systemd-oomd, verifique também:
systemctl status systemd-oomd
oomctl
Política de OOM Não é Planejamento de Capacidade
O ajuste de OOM é uma última linha de defesa. Você ainda precisa de limites de memória, alertas e espaço suficiente para picos normais. Para serviços com limites previsíveis, considere os controles de memória do cgroup:
[Service]
MemoryHigh=1500M
MemoryMax=2G
MemoryHigh= aplica pressão antes do limite rígido. MemoryMax= é um teto. O comportamento exato depende da versão do systemd e da configuração do cgroup, mas a ideia operacional é simples: conter um serviço antes que ele consuma o host.
Swap merece o mesmo tipo de pensamento. Nenhum swap pode fazer picos curtos se transformarem em mortes OOM abruptas. Muito swap lento pode manter o host vivo enquanto a latência se torna inútil. Revise a política de OOM juntamente com swap, limites de memória, comportamento de reinicialização e alertas.
Exemplo: Um Host, Três Serviços
Suponha que um pequeno host de produção execute uma API, um cache Redis e um worker de relatórios em segundo plano. O worker de relatórios é útil, mas pode tentar novamente o trabalho. O Redis melhora a latência, mas a aplicação ainda pode atender algumas requisições indo ao banco de dados. A API é o serviço voltado para o cliente.
Uma primeira passagem razoável poderia ser:
# api.service
[Service]
OOMScoreAdjust=-300
OOMPolicy=kill
Restart=on-failure
# redis.service drop-in, se esta instância do Redis for apenas cache
[Service]
OOMScoreAdjust=0
OOMPolicy=kill
# report-worker.service
[Service]
OOMScoreAdjust=600
OOMPolicy=kill
Restart=on-failure
Isso não garante que o worker morra primeiro em todos os casos possíveis, mas torna sua intenção clara. Se o worker de relatórios crescer demais, ele é um alvo mais fácil. Se a API perder um de seus processos, o systemd mata o resto e reinicia-o de forma limpa. Se o Redis é apenas um cache, você pode optar por não protegê-lo fortemente; se o Redis é seu armazenamento de dados principal, você tomaria uma decisão diferente.
É por isso que a política de OOM deve estar ligada ao papel do serviço, não ao nome do produto. "Redis" não é automaticamente crítico ou descartável. "O cache que podemos reconstruir" e "a única cópia do estado da sessão" são objetos operacionais diferentes.
Testando Sem Criar um Desastre
Você não precisa derrubar um servidor de produção para aprender se as configurações estão aplicadas. Comece com a inspeção:
systemctl show report-worker.service -p OOMScoreAdjust -p OOMPolicy
systemctl status report-worker.service
Em seguida, verifique o processo em execução:
PID=$(systemctl show report-worker.service -p MainPID --value)
cat /proc/$PID/oom_score_adj
Para testes mais profundos, use um host de staging ou uma máquina virtual descartável com a mesma versão do systemd e modo cgroup. Execute uma ferramenta controlada de pressão de memória lá, não em uma caixa de produção compartilhada. O objetivo é confirmar o comportamento geral: o worker é mais fácil de matar, o serviço principal não permanece meio vivo, e o comportamento de reinicialização é visível no journal.
Se você usa contêineres, teste na mesma forma que você implanta. Um serviço rodando diretamente sob o systemd não se comporta exatamente como um processo dentro de um contêiner com seu próprio limite de memória. O kernel pode aplicar o limite do contêiner antes que o host esteja globalmente sem memória. Nesse caso, seu runtime de contêiner, Kubernetes ou configurações de cgroup podem ser a primeira camada que decide o que morre.
Lendo o Incidente Depois
Após um evento OOM, evite pular direto para "precisamos de mais RAM". Às vezes você precisa. Às vezes um cache esqueceu TTLs. Às vezes um deploy mudou a concorrência do worker. Às vezes a atividade de persistência ou backup causou um pico de memória copy-on-write.
Procure por três coisas:
journalctl -k --since "2026-05-24 01:00" | grep -i oom
journalctl -u api.service --since "2026-05-24 01:00"
systemctl show api.service -p Result -p NRestarts
O log do kernel geralmente informa qual processo foi morto. O log da unidade informa como o systemd reagiu. Os contadores de reinicialização informam se o serviço se recuperou de forma limpa ou oscilou.
Em seguida, compare o processo morto com sua prioridade pretendida. Se um serviço protegido morreu antes de um worker descartável, verifique se o worker estava realmente rodando sob a unidade que você ajustou, se a substituição foi carregada e se outro limite de memória foi acionado primeiro. Se a vítima escolhida corresponde à política, mas o incidente ainda prejudicou os usuários, sua classificação de serviço pode precisar mudar.
Documente o Motivo, Não Apenas o Valor
As configurações de OOM são fáceis de esquecer porque ficam quietas em drop-ins de unidade até um dia ruim. Deixe um breve comentário na substituição ou no repositório de infraestrutura explicando o motivo do ajuste.
[Service]
# Worker de fila retentável. Prefira matar este antes do api.service durante pressão do host.
OOMScoreAdjust=600
OOMPolicy=kill
Esse comentário economiza tempo durante uma revisão de incidente. Sem ele, alguém pode ver uma pontuação OOM positiva e "corrigi-la" de volta para zero sem perceber que era uma decisão de prioridade intencional.
Também registre quando você revisou a configuração pela última vez. Um serviço pode mudar de função ao longo do tempo. Um worker que antes lidava com miniaturas descartáveis pode depois processar pagamentos, exportações ou jobs visíveis ao cliente. A política de OOM deve seguir o risco atual, não o propósito original do serviço.
Configurações Ruins Comuns
Uma configuração ruim é proteger o banco de dados, a API, o worker, o cache, o shipper de logs e o agente de monitoramento todos de uma vez. Isso parece cuidadoso, mas dá ao kernel menos opções. Escolha prioridades.
Outra configuração ruim é definir OOMPolicy=continue em um serviço que não pode tolerar processos filhos faltantes. Um gerenciador de processos, servidor web ou daemon personalizado pode manter a unidade ativa mesmo após parte da carga de trabalho ter desaparecido. Se seu balanceador de carga apenas verifica se a porta está aberta, o tráfego pode continuar fluindo para um serviço degradado.
Uma terceira configuração ruim é ajuste positivo sem comportamento de retentativa. Se você torna um serviço fácil de matar, certifique-se de que matá-lo é aceitável. Para um worker de fila, isso significa que os jobs são confirmados apenas após o processamento bem-sucedido. Para um job em lote, isso significa checkpoints. Para um warmer de cache, isso significa que o cache pode ser reconstruído depois.
Finalmente, evite esconder eventos OOM apenas com reinicializações automáticas. Reiniciar um serviço com vazamento pode ganhar tempo, mas também pode criar um loop onde a memória sobe, o serviço morre e os usuários veem falhas periódicas. Adicione alertas sobre contagem de reinicializações e crescimento de memória, não apenas estado do processo.
Um Runbook Curto
Ao ajustar um servidor real, use uma lista de verificação repetível:
- Liste os serviços necessários para recuperação e tráfego de usuários.
- Liste os serviços retentáveis que podem ser mortos primeiro.
- Adicione valores positivos de
OOMScoreAdjustpara trabalho descartável. - Adicione valores negativos moderados apenas aos poucos serviços que merecem proteção.
- Use
OOMPolicy=killpara serviços que não devem rodar parcialmente. - Verifique os valores aplicados através de
systemctl showe/proc. - Alerte sobre pressão de memória antes que eventos OOM aconteçam.
O objetivo não é tornar os eventos OOM inofensivos. O objetivo é torná-los compreensíveis. OOMScoreAdjust= ajuda a escolher a vítima. OOMPolicy= ajuda a definir o que acontece com o resto da unidade. Juntos, eles fornecem uma ordem de falha mais previsível quando a memória já está esgotada.