Comments 16
Я не понимаю, как технология, не только потворствующая заметанию мусора под ковер, но и препятствующая нормальной обработке OOM в том коде, в котором она таки есть и таки уместна, могла появиться на свет и получить такую популярность.
Потому что оверкоммит - это эффективно. Более того, oom killer ведёт себя адекватнее, чем ENOMEM (если вы когда-то жили на системе с выключенным оверкоммитом, можно получить сегфолт баша просто по нажатию кнопки - "ну нет у аллокатора памяти"). Когда у вас есть микро процесс на 100кб, а рядом взбесившийся сервис, который жрёт память o(t), то вам будет очень обидно обнаружить, что ваш запрос на 4кб удовлетворить нельзя, потому что сервис сбоку съел последние двадцать тысяч свободных страниц памяти за 1мкс до вашего запроса. Съел и не использует (virt).
Вообще, компьютер, в котором закончилась оперативная память, это уже не совсем область CS, это уже область "подфигачить хоть как-то".
Или для специфических задач (разреженные массивы), или для тех условий, когда уже на этот оверкоммит заложились. Это как в анекдоте — «смотри, сколько я успел! записался на смену масла, купил новые колёса и помыл машину! как бы я успел это всё без машины?»
> можно получить сегфолт баша просто по нажатию кнопки — «ну нет у аллокатора памяти»)
Именно! Он уже написан так, что отказ в выделении может вызвать только сегфолт, потому что написан для систем, где отказ в выделении это конец света.
> Когда у вас есть микро процесс на 100кб, а рядом взбесившийся сервис, который жрёт память o(t), то вам будет очень обидно обнаружить, что ваш запрос на 4кб удовлетворить нельзя, потому что сервис сбоку съел последние двадцать тысяч свободных страниц памяти за 1мкс до вашего запроса.
На это есть система квот. Она есть даже сейчас в виде rlimits, хоть и примитивная. А как по-вашему это бы лечилось сейчас?
Убийство того прожорливого процесса не предлагать — это уже регулярно проходили и неизвестно в общем случае, насколько это лучше.
> Вообще, компьютер, в котором закончилась оперативная память, это уже не совсем область CS, это уже область «подфигачить хоть как-то».
И это опять самосбывающееся пророчество.
можно получить сегфолт баша просто по нажатию кнопки — «ну нет у аллокатора памяти»)На системе с выключенным оверкоммитом можно получить сегфолт, только если результат malloc не проверять. Что, формально, г-нокод. Если в баше так — соболезную.
А вот с включенным вам дают нечто, что выглядит/ходит/крякает как память, но при обращении к нему иногда случается сегфолт. Очень удобно и очень надежно.
Приходите вы в банк за кредитом, вам без малейших вопросов и проверок выдают карту на сколько угодно, но при попытке ей воспользоваться она взрывается. Вместе с вами. Иногда. Если место людное или звезды так сложились. Предсказать нельзя, но можно молиться и надеяться, что взорвется не у вас.
До тех пор, пока у вас потребление памяти ниже доступной - всё ок. Если выше - какой-то софт идёт нафиг.
Аналогия с картой совсем не подходит, потому что компьютер устройство не надёжное и в продакшене в любом случае требует резервирования.
До тех пор, пока у вас потребление памяти ниже доступной — всё ок.Пока потребление ниже — и без оверкоммита все ок.
Если выше — какой-то софт идёт нафиг.Вот это «какой-то» и напрягает. Без него можно хотя бы останавливаться, некоторое время ждать и пробовать снова, пока память не появится, а так только смириться и перезапускать.
Большая простыня, которая даже не упоминает про существование oomd, cgroups и понятия pressure?
Очень многие люди об этой проблеме задумались, и там есть комплекты решений, от уведомлений о предстоящем разгоне митинга, до userspace разгонятелей митинга и системы приоритетов для oom'а (oom_adj_score).
Статья описывает состояние дел в линуксе circa 2005...
И даже из состояния 2005 года не описывается радикальное решение - отключение оверкоммита.
Упомянутые вами oomd и прочие меры это лечение симптомов, а не проблемы.
Вы говорите, предупреждения. Пусть есть предупреждения, а что в результате? Вот, например, мы генерируем исключение в C++ коде. На создание объекта требуется память. В результате, когда памяти и так мало, мы просим ещё. Вы считаете, это поможет решению?
Cgroups — я там в конце дал ссылку, когда в такой cgroup считалась не только явная память процесса, но и то, что попало в неуправляемые им затраты — кэш диска, и OOM получался от этого. Как это поможет?
> И даже из состояния 2005 года не описывается радикальное решение — отключение оверкоммита.
Описывается, читайте внимательнее. Со всеми его недостатками: начало чудовищного прожора свопа, который скорее всего будет недоступен в облачном сетапе (и не только).
Собственно, переформулирую главный вопрос: почему вместо принципиального решения основной проблемы начинают лечиться симптомы, или даже симптомы симптомов?
> Статья описывает состояние дел в линуксе circa 2005…
И да, не с 2005. Считайте, что я описываю состояние, например, с 1995, и не только Linux (как минимум, все BSD), и утверждаю, что его никто не правит.
Объясните — может, я неправ, например, потому что
1) никто не будет этим пользоваться (с доказательством, почему так)
2) описанный метод не сработает технически (опять же, с причинами)
3) он недоступен (патент, запрет от рептилоидов)
?
Он совершенно тривиален, и ещё в старинные сишные времена до линуксов так делали.
Однако, он же совершенно никак не защищает от oom'а, потому что если вы allocate память, но её не используете, то она virtual. А если хапнули и просто не отдаёте - кандидат на своп. А если хапнули много - кандидат на oom.
Более того, наличие "нычки" с оперативной памятью совершенно вас не спасает от её нехватки в момент выполнения IO, например, причём какой именно памяти, вы даже понятия не имеете (jumbo frames >4k включен или нет?).
Хотите детерминированного поведения? Отключайте оверкоммит. Хотите реакцию на нехватку памяти? Смотрите pressure. Хотите иметь микронычку и надеяться, что она поможет? Ну кто же вам мешает-то? Вон, java себе микронычку на 128Гб делает и ей хорошо.
Свопить нечего, память будет закоммичена, но без реального содержимого.
На OOM — да, но только после того, как уже точно резерв выеден.
> Более того, наличие «нычки» с оперативной памятью совершенно вас не спасает от её нехватки в момент выполнения IO
Имеется в виду, что ядро захотело памяти для какой-то операции, которая вообще не привязана ни к какому процессу?
Тогда аналогичное резервирование надо делать и для самого ядра.
> Хотите детерминированного поведения? Отключайте оверкоммит. Хотите реакцию на нехватку памяти? Смотрите pressure.
Спасибо, но я объяснил, почему считаю этот подход обструктивным.
> Вон, java себе микронычку на 128Гб делает и ей хорошо.
Что за JVM и как именно это реализуется?
> Он совершенно тривиален, и ещё в старинные сишные времена до линуксов так делали.
Именно резерв, который перекидывается по потребности? Ссылочку можно?
Т.е. вы хотите оверкоммит, но не хотите oom? Окай... Покупайте больше памяти.
У jvm это реализуется просто - Xms - Xmx и всё.
Если в старых системах, где malloc() при исчерпании памяти возвращает NULL, программа могла продолжить работу, хотя бы воздержавшись от дальнейшего выделения памяти — то в современных системах этого недостаточно. Надо не только прекратить запрашивать память, но активно и срочно освобождать память, выделенную ранее.
Допустим, у вас многопоточное приложение. Один поток получает уведомление, что в системе заканчивается память. Что он может с этим сделать? Самому потоку, ждущему уведомлений, обычно память не нужна, и освобождать ему нечего. Нужно послать сигнал другим потокам, которые используют в данный момент память и могут ее освободить. Если потоки заняты в данный момент вычислениями, архивацией, шифрованием и т.д. — то во все эти алгоритмы необходимо вставить опрос флага, сигнализирующего о необходимости срочно прекратить работу.
Если опрашивать этот флаг редко — то за время между опросами поток может потребить больше памяти, чем имеется в «сейфе на 1000 талеров». Если опрашивать часто — то придется глубоко лезть в сложные алгоритмы расчетов, архивации, шифрования, видеокодеки. Возможно, придется лезть в сторонние библиотеки. Придется разбивать большие операции чтения файлов на маленькие части, чтобы проверять между ними флаг. Риски и трудозатраты, связанные с такой доработкой алгоритмов и библиотек, представляются мне гигантскими.
И самое печальное, что все эти ухищрения нужны только для того, чтобы снизить (но не устранить) последствия порочного подхода, принятого в конкретной операционной системе.
Это зависит от реализации… и тут всплывает вопрос об ещё одном свойстве Unix подхода — уже не явной диверсии, но просто устарелости. В Windows есть structured exception handling, в юниксы — не завезли.
Чем SEH тут было бы хорошо — оно штатно позволяет послать другой нити (как минимум) асинхронный сигнал что-то сделать, та его обработает, и это интегрировано в общий механизм обработки любых исключительных ситуаций (включая внутриязыковые). Если код той сложной обработки рассчитан на это, то он будет аккуратно производить все свёртки, включая освобождение памяти.
Разумеется, код, который на это не рассчитан, типа стандартного сишного, зачистки не сделает. Может, поэтому его в Unix и не стали портировать. Ну и ещё его надо как-то сочетать со своими сигналами, это это ещё надо продумать (и решить универсально на нескольких поставщиков, лучше даже в POSIX стандарт). С setjmp/longjmp согласовать.
Или просто форсировать всех на C++ уровня «C с классами + инкапсуляция + умные указатели»… Подождать лет 15, пока все библиотеки обновят…
(Или есть альтернатива SEH, которая даёт то же, но лучше?)
> Риски и трудозатраты, связанные с такой доработкой алгоритмов и библиотек, представляются мне гигантскими.
Да.
Но если будет 1) осознание проблем, 2) доступный работающий механизм, то предполагаю, что начнётся постепенная миграция в эту сторону.
> И самое печальное, что все эти ухищрения нужны только для того, чтобы снизить (но не устранить) последствия порочного подхода, принятого в конкретной операционной системе.
Слишком ярким оказался конкретный соблазн.
В Windows есть structured exception handling, в юниксы — не завезли.
Создать exception другому потоку не так просто. В юниксах есть сигналы — один поток может послать другому что-то типа аппаратного прерывания. В рамках потока-«жертвы» исполнение кода приостанавливается, поток исполняет процедуру обработки прерывания и возвращается к прерванному коду (если обработчик этому не помешает). Насчет межпоточных сигналов в юниксе не знаю, но межпроцессовые сигналы точно прерывают код.
А вот в Windows этого нет. В User-mode нет никаких средств, с помощью которых один поток может прервать исполнение другого в произвольном месте. Можно прервать код с помощью Kernel-mode APC, но этот механизм доступен только из ядра и не документирован официально. User-mode APC код не прерывают (см. доку на QueueUserAPC). Они срабатывают только когда поток-жертва войдет в специальный режим ожидания.
Кстати, интересно, как вы себе представляете всякие lock-free алгоритмы, адаптированные к тому, что в них в любой момент может произойти исключение? Не может ли от этого существенно снизиться производительность?
Если код той сложной обработки рассчитан на это, то он будет аккуратно производить все свёртки
Отладка численных методов, шифрования, компрессии и т.д. — чрезвычайно сложный процесс. И чтобы начать перепахивать код таких алгоритмов — должны быть очень веские основания и очень квалифицированные разработчики.
Я бы все же предпочел иметь систему, где malloc возвращает NULL в случае нехватки памяти. Пусть даже придется проверять результат — это значительно проще, чем переделать все алгоритмы так, как вы предлагаете. Пусть даже программу, которой не хватило памяти, прибивают сразу. Но не та система, что есть сейчас — когда вместо одной программы может быть прибита другая, и не в момент malloc, а в произвольном месте.
Слишком ярким оказался конкретный соблазн.
Вот тут не понял. Кто чем соблазнился?
Я помню, читал, что люди это делали. Средства уже не помню; может быть, и что-то очень кастомное. В любом случае, если уже сделан общий механизм (SEH), доработка такого оповещения уже мелкая проблема.
> Кстати, интересно, как вы себе представляете всякие lock-free алгоритмы, адаптированные к тому, что в них в любой момент может произойти исключение? Не может ли от этого существенно снизиться производительность?
1. Можно сделать что-то вроде EnterUninterruptible/ExitUninterruptible (такое уже давно делается в ядре). Где можно, сопровождать его через RAII. Забота автора кода — сделать время такой блокировки поменьше. В пределах процесса у нас всё равно, увы, зона полного взаимного доверия кода.
2. Если и не делать, то предел затрат, мне кажется, одна переменная состояния (причём может быть, что и стек ей не нужен — если поместится в регистр; код обработки исключения, который таблично определён для данного участка основного кода, будет просто рассчитан на конкретное место хранения значения). Если в стеке — возможно, да, какой-нибудь write-release будет вытеснять и это значение в пространство памяти-кэша, и это будет чуть дороже. Но не думаю, что удорожание будет критичным.
> Отладка численных методов, шифрования, компрессии и т.д. — чрезвычайно сложный процесс. И чтобы начать перепахивать код таких алгоритмов — должны быть очень веские основания и очень квалифицированные разработчики.
Предложенный в статье метод позволяет начать процесс перехода и делать его постепенно. Если на это уйдёт 10-20 лет — ничего страшного, лишь бы процесс пошёл.
Ну а в достаточно современных средах и так можно будет пользоваться уже готовыми средствами с RAII (как в C++ векторы, умные указатели и прочее). Возможное усложнение кода (небольшое) будет в языках, где таких средств нет.
> Я бы все же предпочел иметь систему, где malloc возвращает NULL в случае нехватки памяти.
Я тоже склоняюсь к примерно такому. Но, как сказал в статье, чтобы это всегда полностью гарантировать, нужно отказаться от пачки вкусных преимуществ нынешней организации, и это может быть очень дорого.
Потому и идеи, как максимально сократить вред, не отказываясь от этих преимуществ.
> Вот тут не понял. Кто чем соблазнился?
Авторы Unix (как минимум) — вкусностями системы с объединением хранения данных памяти и copy-on-write, и, как следствие, lazy commit.
Виртуальная память как критический ресурс, или Как справиться с расстрелом из-за угла