Почему при исчерпании памяти не срабатывает OOM Killer!?
Потому что по умолчанию параметр ядра vm.oom_kill_allocating_task=0 и вкупе с другими относящимися к нему параметрами в конфигурации по умолчанию он не срабатывает. Вернее срабатывает, но можно прождать целый час (или даже больше) когда дело наконец дойдёт до SIGKILL процесса с утечкой памяти. Всё это время сервер будет в состоянии глубокого фриза и не в состоянии отвечать ни на что кроме hard reset (т.е. физического рубильника). Для систем высокой доступности с высоким аптаймом такое не подходит - уничтожение процесса (чаще контейнера) или перезагрузка сервера должна происходить сразу, с автоматической маршрутизацией трафика, балансировкой и подхватом нагрузки резервными нодами кластера.
oom_kill_allocating_task
This enables or disables killing the OOM-triggering task in
out-of-memory situations.
If this is set to zero, the OOM killer will scan through the entire
tasklist and select a task based on heuristics to kill. This normally
selects a rogue memory-hogging task that frees up a large amount of
memory when killed.
If this is set to non-zero, the OOM killer simply kills the task that
triggered the out-of-memory condition. This avoids the expensive
tasklist scan.
If panic_on_oom is selected, it takes precedence over whatever value
is used in oom_kill_allocating_task.
The default value is 0.
Описание всех параметров и их возможные значения можно найти в документации:
https://www.kernel.org/doc/Documentation/sysctl/vm.txt
https://www.kernel.org/doc/Documentation/sysctl/kernel.txt
По умолчанию в ванильных и кастомных сборках ядер (mainline, stable и longterm) для дистрибутивов Debian, Gentoo, Arch (остальные давно не использовал) параметры ядра установлены по умолчанию следующим образом:
vm.oom_kill_allocating_task=0
vm.oom_dump_tasks=1
vm.panic_on_oom=0
kernel.panic=0
vm.overcommit_kbytes=0
vm.overcommit_memory=0
vm.overcommit_ratio=50
Таким образом Out Of Memory (OOM) Killer по умолчанию настроен не оптимально и не срабатывает ожидаемо и гарантировано в нужный критический для системы момент. Также на механизм его работы было много жалоб на форумах Linux, после чего с версии 4.6 ядра он был несколько переработан.
https://github.com/torvalds/linux/blob/master/mm/oom_kill.c
https://www.kernel.org/doc/gorman/html/understand/understand016.html
Дело в том, что oom_kill() вызывается ядром в самую последнюю очередь, в самой крайней и самой критической ситуации нехватки системной памяти, когда не остаётся пространства для манёвра и ничего другого предпринять уже невозможно.
Для более раннего вызова OOM Killer в user space даже был создан внешний по отношению к ядру инструмент, резидентный демон (что конечно же не является абсолютным решением проблемы):
https://github.com/rfjakob/earlyoom
Системный вызов oom_kill() вызывается при частых запросах программы на выделение памяти при вызове функции malloc() стандартной библиотеки Си.
При vm.oom_kill_allocating_task=0 происходит постоянное сканирование списка процессов при системном вызове oom_kill(), что крайне неэффективно и дорого по накладным расходам.
Сработать успешно oom_kill() может только если ещё осталась свободная память, но вызывается в самый последний критический момент, когда память уже закончилась, в результате чего вызов oom_kill() при исчерпании свободной памяти, пытаясь выполнить сканирование процессов с перерасходом памяти для их завершения, зависает вместе с ядром и всей системой, происходит тотальный системный фриз, работа вызова продолжается крайне медленно, фактически в однозадачном режиме исполняется только oom_kill() и сопутствующие вызовы, с активным дисковым вводом-выводом в swap, начинается меденный дамп страниц в область подкачки (если она есть) и/или пометка страниц памяти на удаление из ОЗУ и вычищение страниц из swap раздела страничной подкачки памяти, очистка всех буферов ядра и только после, спустя достаточно длительное время, происходит вызов сигнала SIGKILL и уничтожение процесса израсходовавшего всю память.
Потому что по умолчанию параметр ядра vm.oom_kill_allocating_task=0 и вкупе с другими относящимися к нему параметрами в конфигурации по умолчанию он не срабатывает. Вернее срабатывает, но можно прождать целый час (или даже больше) когда дело наконец дойдёт до SIGKILL процесса с утечкой памяти. Всё это время сервер будет в состоянии глубокого фриза и не в состоянии отвечать ни на что кроме hard reset (т.е. физического рубильника). Для систем высокой доступности с высоким аптаймом такое не подходит - уничтожение процесса (чаще контейнера) или перезагрузка сервера должна происходить сразу, с автоматической маршрутизацией трафика, балансировкой и подхватом нагрузки резервными нодами кластера.
oom_kill_allocating_task
This enables or disables killing the OOM-triggering task in
out-of-memory situations.
If this is set to zero, the OOM killer will scan through the entire
tasklist and select a task based on heuristics to kill. This normally
selects a rogue memory-hogging task that frees up a large amount of
memory when killed.
If this is set to non-zero, the OOM killer simply kills the task that
triggered the out-of-memory condition. This avoids the expensive
tasklist scan.
If panic_on_oom is selected, it takes precedence over whatever value
is used in oom_kill_allocating_task.
The default value is 0.
Описание всех параметров и их возможные значения можно найти в документации:
https://www.kernel.org/doc/Documentation/sysctl/vm.txt
https://www.kernel.org/doc/Documentation/sysctl/kernel.txt
По умолчанию в ванильных и кастомных сборках ядер (mainline, stable и longterm) для дистрибутивов Debian, Gentoo, Arch (остальные давно не использовал) параметры ядра установлены по умолчанию следующим образом:
vm.oom_kill_allocating_task=0
vm.oom_dump_tasks=1
vm.panic_on_oom=0
kernel.panic=0
vm.overcommit_kbytes=0
vm.overcommit_memory=0
vm.overcommit_ratio=50
Таким образом Out Of Memory (OOM) Killer по умолчанию настроен не оптимально и не срабатывает ожидаемо и гарантировано в нужный критический для системы момент. Также на механизм его работы было много жалоб на форумах Linux, после чего с версии 4.6 ядра он был несколько переработан.
https://github.com/torvalds/linux/blob/master/mm/oom_kill.c
https://www.kernel.org/doc/gorman/html/understand/understand016.html
Дело в том, что oom_kill() вызывается ядром в самую последнюю очередь, в самой крайней и самой критической ситуации нехватки системной памяти, когда не остаётся пространства для манёвра и ничего другого предпринять уже невозможно.
Для более раннего вызова OOM Killer в user space даже был создан внешний по отношению к ядру инструмент, резидентный демон (что конечно же не является абсолютным решением проблемы):
https://github.com/rfjakob/earlyoom
Системный вызов oom_kill() вызывается при частых запросах программы на выделение памяти при вызове функции malloc() стандартной библиотеки Си.
При vm.oom_kill_allocating_task=0 происходит постоянное сканирование списка процессов при системном вызове oom_kill(), что крайне неэффективно и дорого по накладным расходам.
Сработать успешно oom_kill() может только если ещё осталась свободная память, но вызывается в самый последний критический момент, когда память уже закончилась, в результате чего вызов oom_kill() при исчерпании свободной памяти, пытаясь выполнить сканирование процессов с перерасходом памяти для их завершения, зависает вместе с ядром и всей системой, происходит тотальный системный фриз, работа вызова продолжается крайне медленно, фактически в однозадачном режиме исполняется только oom_kill() и сопутствующие вызовы, с активным дисковым вводом-выводом в swap, начинается меденный дамп страниц в область подкачки (если она есть) и/или пометка страниц памяти на удаление из ОЗУ и вычищение страниц из swap раздела страничной подкачки памяти, очистка всех буферов ядра и только после, спустя достаточно длительное время, происходит вызов сигнала SIGKILL и уничтожение процесса израсходовавшего всю память.