Estou usando Linux 5.15 com Ubuntu 22.04.
Eu tenho um processo que usa muita memória. Requer mais memória do que tenho RAM na minha máquina. A primeira vez que o executei, foi morto pelo OOM Killer. Eu entendo isso: o sistema ficou sem memória, o OOM Killer foi acionado, meu processo foi encerrado. Isso faz sentido. Também tenho certeza de que foi isso que aconteceu: dei uma olhada dmesg
e está tudo lá.
Então eu adicionei algum espaço de troca. Não me importo se esse processo demorar muito para ser executado: não o executarei com frequência.
Executei o processo novamente. Desta vez, funcionou por mais tempo do que da primeira vez. Todo o sistema ficou muito lento, daquele jeito que os sistemas ficam quando estão trocando muito. Parecia estar funcionando... e então morreu. Não apenas o processo morreu, mas o processo shell que era seu pai também morreu, e o processo Tmux que era seu pai, e o processo shell que era pai do processo Tmux, e até mesmo o processo do terminal GNOME que era seu pai ! Mas então o processo de assassinato parou: nenhum outro pai morreu.
A princípio, pensei que o OOM Killer havia sido acionado novamente - embora ainda houvesse muito espaço de troca disponível - e que ele havia escolhido encerrar o processo do terminal GNOME. Mas eu verifiquei dmesg
e journalctl -k
não havia nada de novo lá. Não havia sinal de que o OOM Killer havia sido acionado.
Então, primeira pergunta: existe alguma circunstância em que o OOM Killer pode ser acionado sem registrar nada no buffer de anel do kernel?
Fiquei intrigado com o fato de que o kernel do Linux parecia ter começado a trocar, mas de alguma forma não havia trocado o suficiente... ou não havia trocado rápido o suficiente... ou algo assim.
Então eu aumentei vm.swappiness
. Isso realmente não deve afetar a estabilidade do sistema: é apenas um botão para girar para otimizar o desempenho. Mesmo com o kernel vm.swappiness
definido 0
, ainda deve iniciar a troca quando a memória livre em uma zona cair abaixo de um limite crítico.
Mas parecia que tinha começado a trocar, mas não havia trocado o suficiente ... então aumentei vm.swappiness
para 100
incentivá-lo a trocar um pouco mais.
Então eu executei o processo novamente. Todo o sistema ficou muito lento, daquela forma que os sistemas fazem quando estão trocando muito ... até que o processo seja executado com sucesso até a conclusão.
Então, segunda pergunta: por que o kernel não usou o espaço de troca disponível, mesmo quando a memória livre caiu abaixo do limite crítico e certamente havia muito espaço de troca disponível? Por que a mudança vm.swappiness
fez a diferença?
Atualizar:
Testes adicionais revelaram que a configuração vm.swappiness
não é uma solução confiável. Eu tive algumas falhas mesmo com vm.swappiness
set to 100
. Isso pode melhorar as chances de o processo ser concluído com sucesso, mas não tenho certeza.
Existem vários motivos para eventos OOM ocorrerem antes que o espaço de troca disponível seja totalmente usado e os eventos OOM podem acionar o thread OOM-killer ou pior… sinais desagradáveis:
A/ Generalidades sobre alocação de memória e eventos OOM
Porque os desenvolvedores do kernel estão cientes de que muitos programas malloc() enormes quantidades de memória “ just-in-case ” e não usam muito dela e, no mínimo, podem estaticamente esperar que todos os processos em execução no sistema não precisem simultaneamente da memória solicitada, o kernel na verdade não reserva a memória no ponto malloc (ou amigos).
Em vez disso, ele aguardará o primeiro acesso de gravação na memória (o que levará necessariamente a uma falha de página) para fazer o mapeamento real.
Se, neste ponto, não houver memória imediatamente disponível, o kernel aguardará por dias melhores (1) e, se esses dias melhores não vierem rápido o suficiente, disparará um evento OOM. Evento OOM que, dependendo de alguma configuração do sysctl (panic_on_oom) , acionará o OOM-killer ou gerará um kernel panic.
B/ Por que os eventos OOM podem ocorrer independentemente da quantidade de espaço livre na troca (2)
Como visto em §A, o kernel não esperará muito para que alguma memória fique disponível. Portanto, se nenhum processo em execução liberar alguma memória e o cache do sistema de arquivos já estiver reduzido ao seu mínimo estrito, fazer com que a troca seja a única maneira de liberar páginas de memória ... isso simplesmente não caberá no período de tolerância. O evento OOM será disparado mesmo que Gigs de memória possam ter sido trocados.
Os acessos aleatórios ao disco são lentos, o acesso à área de troca é ainda mais lento, pois o espaço de troca provavelmente está no mesmo disco que os sistemas de arquivos usados pelos processos em execução.
Existe, no entanto, uma maneira de tentar evitar que o sistema caia nessa situação. Lembre-se de Aquiles e a tartaruga: comece a trocar antes. Comece a mover as páginas no momento em que o sistema não precisar de memória física.
Isso é o que você indiretamente (3) conseguiu obter ao aumentar o swappiness . Mas, como isso é apenas um efeito colateral de sua configuração, a configuração "melhor" sofre de um alto stdev e é altamente dependente da carga de trabalho. Benchmarks necessários. (4)(5)
Processos usando a chamada de sistema mlock() podem obter páginas que são garantidas por design não trocáveis. Pior ?
mlockall()
(6)O que pode de fato resultar em uma boa quantidade de MB não substituíveis.
As páginas HugeTLB também não podem ser trocadas sob pressão de memória,
cat /proc/meminfo
relatarão a quantidade de memória reservada para atender a sua finalidade.C/ Por que os threads podem terminar quando a pressão da memória é alta sem que o OOM-killer registre nada . (7)
A decisão de superalocar é tomada pelo kernel no momento
malloc
da emissão. E apesar dos padrões do kernel em uma "estratégia otimista" , sempre pode acontecer que o kernel recuse o pedido de reserva, retornando um ponteiro NULL para omalloc()
thread de chamada.Nesse caso, dependendo de como o processo de chamada lida com essa exceção, ele aguardará melhores momentos para renovar sua solicitação ou simplesmente abortará graciosamente ou até mesmo ... simplesmente ignorará e segfault, encerrando ou causando a morte prematura dos pais em cascata, isso por sua vez, liberando uma boa quantidade de memória sem precisar da intervenção do OOM-killer. (e mais uma vez independentemente do espaço restante na troca)
1: Hmmm melhores milissegundos, na verdade, pois ele verificará até seis vezes no máximo, com alguns nanossegundos de espera entre eles. Observe que esses números pertencem à minha memória de kernels agora antigos, eles podem ter mudado desde então.
2 : Observe que, estritamente falando, o Linux não troca , pois a troca se refere à transferência de um espaço de endereço de processo inteiro para o disco. O Linux realmente implementa a paginação , pois, de fato, transfere páginas individuais. No entanto, documentos e discussões usando troca … que assim seja.
3: "indiretamente" porque começar a trocar mais cedo é apenas um efeito colateral dessa configuração que se destina principalmente a informar suas preferências de cache do sistema de arquivos em relação às páginas do processo.
Como o IO do sistema de arquivos é caro, o Linux usará o máximo de memória física possível para o cache.
Quanto maior o valor do swappiness, mais agressivo o sistema estará trocando as páginas do processo assim que o processo for iniciado, aumentando incidentalmente a quantidade de páginas de cache rapidamente recuperáveis sob pressão de memória.
4: Este BTW também explica o contrapositivo da sua pergunta: por que o sistema está trocando enquanto tem muita RAM livre disponível?
5 : Embora possamos ler as principais instituições (RHEL, ORACLE…) aconselhando a configuração do swappiness ao mínimo estrito… (e comprar mais RAM…) Morton (um desenvolvedor de kernel líder) recomenda fortemente um valor de 100. Com a disponibilidade de tecnologias
como como zswap , possivelmente tornando o custo de swap mais barato que o sistema de arquivos IO, valores de swappiness maiores que 100 nem seriam absurdos.
6:
7 : Lembre-se de que, mesmo se iniciado, o OOM-killer é bastante... preguiçoso, preferindo que as tarefas desagradáveis terminem sozinhas. Portanto, se houver sinais pendentes para o culpado… o OOM-killer aguardará que sua ação seja tomada… apenas por precaução…
Em primeiro lugar, gostaria de agradecer ao MC68020 por reservar um tempo para analisar isso para mim. Acontece que a resposta deles não incluiu o que realmente estava acontecendo nessa situação - mas eles receberam a recompensa de qualquer maneira, pois é uma ótima resposta e uma referência útil para o futuro.
Também gostaria de agradecer a Philip Couling por sua resposta, que também não estava certa, mas me apontou na direção certa.
O problema acabou sendo systemd-oomd .
O problema e sua solução são descritos aqui: Como desabilito o matador de processos OOM systemd no Ubuntu 22.04?
Resumidamente:
E agora posso executar meu processo de forma confiável até a conclusão todas as vezes, sem que algum serviço systemd elimine toda a árvore do processo sem aviso prévio.
Não estou ciente de nenhuma causa que possa resultar nos processos de morte do assassino OOM, mas não registrei o fato. Há um caso extremo em que o OOM Killer pode desativar o processo responsável por gravar os logs do kernel no disco. Isso parece improvável pela sua descrição.
Eu tomaria dois detalhes de sua descrição como importantes e relacionados:
É um palpite, mas parece que a própria GUI está matando.
É bem possível que a surra estivesse fazendo parecer que estava quebrado. Já vi exemplos em que, por exemplo, os navegadores travaram devido a uma intensa surra. Os detectores de falhas não podem ver nenhuma atividade e assumem que o próprio programa deu errado, sem entender que o programa estava simplesmente esperando a resposta do Kernel.
Eu tentaria trocar de console e executá-lo a partir de uma linha de comando sem a GUI. Isso pelo menos descartaria qualquer interferência do próprio GNOME.