Vi várias postagens na web de pessoas aparentemente reclamando de um VPS hospedado que matava processos inesperadamente porque eles usavam muita RAM.
Como isso é possível? Eu pensei que todos os sistemas operacionais modernos fornecem "RAM infinita" apenas usando a troca de disco para o que for que passa pela RAM física. Isso está correto?
O que pode estar acontecendo se um processo é "interrompido devido à pouca RAM"?
Respostas:
Às vezes é dito que o Linux, por padrão, nunca nega solicitações de mais memória do código do aplicativo - por exemplo
malloc()
. 1 Isso não é de fato verdade; o padrão usa uma heurística pela qualDe
[linux_src]/Documentation/vm/overcommit-accounting
(todas as cotações são da árvore 3.11). Exatamente o que conta como uma "alocação seriamente selvagem" não é explicitado; portanto, teríamos que passar pela fonte para determinar os detalhes. Também poderíamos usar o método experimental na nota de rodapé 2 (abaixo) para tentar obter alguma reflexão sobre a heurística - com base nisso, minha observação empírica inicial é que, em circunstâncias ideais (== o sistema está ocioso), se você não Se não houver troca, você poderá alocar cerca de metade da sua RAM e, se houver, receberá cerca da metade da RAM mais todas as trocas. Isso é mais ou menos por processo (mas observe que esse limite é dinâmico e está sujeito a alterações devido ao estado, consulte algumas observações na nota de rodapé 5).Metade da sua RAM mais swap é explicitamente o padrão para o campo "CommitLimit" em
/proc/meminfo
. Aqui está o que isso significa - e observe que ele realmente não tem nada a ver com o limite discutido (a partir de[src]/Documentation/filesystems/proc.txt
):O documento de contabilidade de supercomissão anteriormente citado declara que o padrão
vm.overcommit_ratio
é 50. Portanto, sesysctl vm.overcommit_memory=2
você puder ajustar vm.covercommit_ratio (withsysctl
) e ver as conseqüências. 3 O modo padrão, quandoCommitLimit
não é imposto e apenas "confirmações excessivas no espaço de endereço óbvio são recusadas", é quandovm.overcommit_memory=0
.Embora a estratégia padrão tenha um limite heurístico por processo que evite a "alocação seriamente selvagem", ela deixa o sistema como um todo livre para ficar seriamente selvagem, em termos de alocação. 4 Isso significa que em algum momento ele pode ficar sem memória e precisar declarar falência em alguns processos por meio do killer do OOM .
O que o assassino da OOM mata? Não necessariamente o processo que solicitou memória quando não havia, pois esse não é necessariamente o processo verdadeiramente culpado e, o que é mais importante, não é necessariamente o que mais rapidamente tirará o sistema do problema em que ele se encontra.
Isso é citado a partir daqui, que provavelmente cita uma fonte 2.6.x:
O que parece ser uma lógica decente. No entanto, sem ser forense, o # 5 (que é redundante de # 1) parece uma implementação de venda difícil e o # 3 é redundante de # 2. Portanto, pode fazer sentido considerar isso reduzido para # 2/3 e # 4.
Pesquisei através de uma fonte recente (3.11) e notei que este comentário mudou nesse meio tempo:
Isso é um pouco mais explícito sobre o item 2: "O objetivo é [matar] a tarefa que consome mais memória para evitar falhas subseqüentes do oom" e pela implicação no 4 ( "queremos matar a quantidade mínima de processos ( um ) ) .
Se você deseja ver o assassino do OOM em ação, consulte a nota de rodapé 5.
1 Uma ilusão de que Gilles me livrou, ver comentários.
2 Aqui está um pouco simples de C que solicita pedaços de memória cada vez maiores para determinar quando uma solicitação de mais falhará:
Se você não conhece C, pode compilar isso
gcc virtlimitcheck.c -o virtlimitcheck
e executar./virtlimitcheck
. É completamente inofensivo, pois o processo não usa o espaço que ele solicita - ou seja, nunca usa realmente nenhuma RAM.Em um sistema de 3,11 x86_64 com sistema de 4 GB e 6 GB de troca, falhei em ~ 7400000 kB; o número flutua, então talvez o estado seja um fator. Isso é coincidentemente próximo ao
CommitLimit
in/proc/meminfo
, mas a modificação dessa viavm.overcommit_ratio
não faz nenhuma diferença. No entanto, em um sistema ARM de 448 MB 3.6.11 de 32 bits e 64 MB de troca, porém, falho em ~ 230 MB. Isso é interessante, pois no primeiro caso a quantidade é quase o dobro da quantidade de RAM, enquanto no segundo é cerca de 1/4 disso - implicando fortemente que a quantidade de troca é um fator. Isso foi confirmado desativando a troca no primeiro sistema, quando o limite de falhas caiu para ~ 1,95 GB, uma proporção muito semelhante à pequena caixa ARM.Mas isso é realmente por processo? Parece ser. O pequeno programa abaixo solicita um pedaço de memória definido pelo usuário e, se for bem-sucedido, espera que você retorne - dessa forma, você pode tentar várias instâncias simultâneas:
Cuidado, no entanto, que não se trata estritamente da quantidade de RAM e troca, independentemente do uso - consulte a nota de rodapé 5 para observações sobre os efeitos do estado do sistema.
3
CommitLimit
refere-se à quantidade de espaço de endereço permitido para o sistema quando vm.overcommit_memory = 2. Presumivelmente, a quantidade que você pode alocar deve ser aquela menos o que já foi confirmado, que aparentemente é oCommitted_AS
campo.Um experimento potencialmente interessante demonstrando isso é adicionar
#include <unistd.h>
à parte superior do virtlimitcheck.c (consulte a nota de rodapé 2) e um poucofork()
antes dowhile()
loop. Não é garantido que funcione como descrito aqui sem alguma sincronização tediosa, mas há uma chance decente de que isso funcione, YMMV:Isso faz sentido - olhando tmp.txt em detalhes, você pode ver os processos alternando suas alocações cada vez maiores (isso é mais fácil se você jogar o pid na saída) até que um, evidentemente, tenha reivindicado o suficiente para que o outro falhe. O vencedor fica livre para pegar tudo até
CommitLimit
menosCommitted_AS
.4 Nesse ponto, vale a pena mencionar, se você ainda não entende o endereçamento virtual e exige a paginação, que o que torna possível o comprometimento em primeiro lugar é que o que o kernel aloca para os processos da terra do usuário não é memória física - é espaço de endereço virtual . Por exemplo, se um processo reserva 10 MB para algo, isso é organizado como uma sequência de endereços (virtuais), mas esses endereços ainda não correspondem à memória física. Quando esse endereço é acessado, isso resulta em uma falha de páginae então o kernel tenta mapeá-lo na memória real para que ele possa armazenar um valor real. Os processos geralmente reservam muito mais espaço virtual do que realmente acessam, o que permite que o kernel faça o uso mais eficiente da RAM. No entanto, a memória física ainda é um recurso finito e, quando tudo foi mapeado para o espaço de endereço virtual, é necessário eliminar algum espaço de endereço virtual para liberar alguma RAM.
5 Primeiro um aviso : se você tentar fazer isso
vm.overcommit_memory=0
, salve o seu trabalho primeiro e feche os aplicativos críticos, pois o sistema ficará congelado por ~ 90 segundos e algum processo terminará!A idéia é rodar uma bomba de garfo que atinge o tempo limite após 90 segundos, com os garfos alocando espaço e alguns deles gravando grandes quantidades de dados na RAM, enquanto relatam ao stderr.
Compile isso
gcc forkbomb.c -o forkbomb
. Primeiro, tente comsysctl vm.overcommit_memory=2
- você provavelmente obterá algo como:Nesse ambiente, esse tipo de bomba de forquilha não chega muito longe. Observe que o número em "diz N forks" não é o número total de processos, é o número de processos na cadeia / filial que antecederam aquele.
Agora tente com
vm.overcommit_memory=0
. Se você redirecionar o stderr para um arquivo, poderá fazer algumas análises brutas depois, por exemplo:Apenas 15 processos falharam ao alocar 1 GB - demonstrando que a heurística para overcommit_memory = 0 é afetada pelo estado. Quantos processos havia? Olhando para o final do tmp.txt, provavelmente> 100.000. Agora, como pode realmente usar o 1 GB?
Oito - o que novamente faz sentido, já que na época eu tinha ~ 3 GB de RAM livre e 6 GB de swap.
Dê uma olhada nos logs do sistema depois de fazer isso. Você deve ver as pontuações dos relatórios do OOM killer (entre outras coisas); presumivelmente isso se refere a
oom_badness
.fonte
Isso não acontecerá se você carregar apenas 1G de dados na memória. E se você carregar muito, muito mais? Por exemplo, muitas vezes trabalho com arquivos enormes, contendo milhões de probabilidades que precisam ser carregadas no R. Isso leva cerca de 16 GB de RAM.
A execução do processo acima no meu laptop fará com que ele comece a trocar de maluco assim que meus 8 GB de RAM forem preenchidos. Isso, por sua vez, atrasará tudo, porque a leitura do disco é muito mais lenta que a leitura da RAM. E se eu tiver um laptop com 2 GB de RAM e apenas 10 GB de espaço livre? Uma vez que o processo ocupe toda a RAM, ele também preencherá o disco porque ele está gravando para trocar e eu não tenho mais RAM e espaço para trocar (as pessoas tendem a limitar a troca para uma partição dedicada em vez de uma swapfile exatamente por esse motivo). É aí que o assassino da OOM entra e começa a matar os processos.
Portanto, o sistema pode realmente ficar sem memória. Além disso, os sistemas de troca pesada podem se tornar inutilizáveis muito antes que isso aconteça, simplesmente devido às operações lentas de E / S devido à troca. Geralmente, alguém quer evitar trocar o máximo possível. Mesmo em servidores de ponta com SSDs rápidos, há uma clara diminuição no desempenho. No meu laptop, que possui uma unidade clássica de 7200RPM, qualquer troca significativa essencialmente torna o sistema inutilizável. Quanto mais troca, mais lento fica. Se eu não matar o processo ofensivo rapidamente, tudo ficará travado até que o assassino da OOM intervenha.
fonte
Os processos não são mortos quando não há mais RAM, eles são mortos quando foram enganados desta maneira:
Isso pode acontecer mesmo enquanto o sistema não estiver trocando ativamente, por exemplo, se a área de troca estiver preenchida com páginas de memória de daemons adormecidos.
Isso nunca acontece em sistemas operacionais que não comprometem demais a memória. Com eles, nenhum processo aleatório é eliminado, mas o primeiro processo que solicita memória virtual enquanto está esgotado faz com que o malloc (ou similar) retorne com erro. Assim, é dada a chance de lidar adequadamente com a situação. No entanto, nesses sistemas operacionais, também pode acontecer que o sistema fique sem memória virtual enquanto ainda houver RAM livre, o que é bastante confuso e geralmente mal compreendido.
fonte
Quando a RAM disponível está esgotada, o kernel começa a trocar bits de processamento no disco. Na verdade, o kernel começa a trocar a quando a RAM está quase esgotada: começa a trocar proativamente quando há um momento de inatividade, para ser mais responsivo se um aplicativo de repente exigir mais memória.
Observe que a RAM não é usada apenas para armazenar a memória dos processos. Em um sistema íntegro típico, apenas metade da RAM é usada pelos processos e a outra metade é usada para cache e buffers de disco. Isso fornece um bom equilíbrio entre processos em execução e entrada / saída de arquivo.
O espaço de troca não é infinito. Em algum momento, se os processos continuarem alocando mais e mais memória, os dados de spillover da RAM preencherão a troca. Quando isso acontece, os processos que tentam solicitar mais memória veem sua solicitação negada.
Por padrão, o Linux supercomprime a memória. Isso significa que, às vezes, permite que um processo seja executado com a memória reservada, mas não usada. A principal razão para o comprometimento excessivo é a maneira como o garfo funciona. Quando um processo inicia um subprocesso, o processo filho opera conceitualmente em uma réplica da memória do pai - os dois processos inicialmente têm memória com o mesmo conteúdo, mas esse conteúdo diverge à medida que os processos fazem alterações cada um em seu próprio espaço. Para implementar isso completamente, o kernel precisaria copiar toda a memória do pai. Isso tornaria a bifurcação lenta, então o kernel pratica a cópia na gravação: inicialmente, o filho compartilha toda a sua memória com os pais; sempre que um dos processos grava em uma página compartilhada, o kernel cria uma cópia dessa página para interromper o compartilhamento.
Muitas vezes, uma criança deixa muitas páginas intocadas. Se o kernel alocasse memória suficiente para replicar o espaço de memória do pai em cada fork, muita memória seria desperdiçada em reservas que os processos filho nunca usarão. Daí o comprometimento excessivo: o kernel reserva apenas parte dessa memória, com base em uma estimativa de quantas páginas o filho precisará.
Se um processo tentar alocar alguma memória e não houver memória suficiente, o processo recebe uma resposta de erro e lida com ela da maneira que achar melhor. Se um processo solicitar indiretamente memória, gravando em uma página compartilhada que deve ser compartilhada, é uma história diferente. Não há como relatar essa situação ao aplicativo: ele acredita que possui dados graváveis e pode até lê-los - é que a escrita envolve alguma operação um pouco mais elaborada. Se o kernel não puder fornecer uma nova página de memória, tudo o que você pode fazer é eliminar o processo de solicitação ou matar outro processo para preencher a memória.
Você pode pensar neste ponto que matar o processo de solicitação é a solução óbvia. Mas, na prática, isso não é tão bom. O processo pode ser importante e, por acaso, é necessário acessar apenas uma de suas páginas agora, enquanto outros processos menos importantes estão em execução. Portanto, o kernel inclui heurísticas complexas para escolher quais processos matar - o (in) famoso assassino do OOM .
fonte
Apenas para adicionar outro ponto de vista a partir das outras respostas, muitos VPS hospedam várias máquinas virtuais em qualquer servidor. Qualquer VM única terá uma quantidade especificada de RAM para uso próprio. Muitos provedores oferecem "RAM estourada", na qual eles podem usar a RAM além da quantidade designada. Isso é destinado apenas para uso a curto prazo, e aqueles que excederem esse período de tempo prolongado podem ser penalizados pelo host que interrompe os processos para reduzir a quantidade de RAM em uso, para que outros não sofram a máquina host está sendo sobrecarregada.
fonte
Algum tempo o linux ocupa espaço virtual externo. Essa é a partição swap. Quando o Ram é preenchido, o Linux leva essa área de troca para executar o processo de baixa prioridade.
fonte