← На главную

Ubicloud нашла баг в Linux 6.5, убивающий PostgreSQL через OOM Killer

03.07.2026 13:00 · hackernews

PostgreSQL плохо переносит OOM Killer. Linux разрешает процессам выделять больше виртуальной памяти, чем есть физической — ядро надеется, что вся память не будет использована одновременно. Если это не так, OOM Killer убивает процесс, чтобы освободить память. Для большинства приложений это просто перезапуск, но не для PostgreSQL.

PostgreSQL использует общую память: postmaster создаёт бэкенд-процессы для каждого подключения, и они разделяют буферы, WAL-буферы, таблицы блокировок. OOM Killer не знает этой архитектуры — он убивает процесс по эвристике (обычно с наибольшим потреблением). Если убитый бэкенд в этот момент менял разделяемую память, она может остаться в несогласованном состоянии. Это ведёт к тихому повреждению данных. Когда postmaster видит, что его дочерний процесс убит, он предполагает худшее: завершает все остальные бэкенды, сбрасывает все соединения и отменяет транзакции. База данных уходит в crash recovery, который при высокой записи может длиться долго. Один OOM — и сервер ложится целиком.

Этого можно избежать с помощью строгого overcommit — vm.overcommit_memory = 2. В этом режиме ядро отслеживает общую зарезервированную память (Committed_AS) и отказывает в выделении, если превышен лимит. PostgreSQL обрабатывает ошибку ENOMEM корректно: бэкенд сообщает клиенту об ошибке, отменяет транзакцию и продолжает работу. Остальные соединения не страдают. Но на разделённых машинах с разными нагрузками предсказать использование commit budget сложно — какая-то посторонняя программа может съесть лимит, и PostgreSQL получит ENOMEM, даже если база в порядке.

Ubicloud (компания, построившая Ubicloud PostgreSQL) всегда использовала строгий overcommit для PostgreSQL, но на этот раз столкнулась с проблемой. Через несколько недель после включения на некоторых базах начали появляться ошибки нехватки памяти, хотя физической памяти было полно. Проверили /proc/meminfo — на 8-гигабайтной машине Committed_AS показывал 651 ГБ. Исследование показало, что hugetlb-отображения (PostgreSQL использует huge pages для shared_buffers) исключены из учёта, а сумма учётной памяти по всем VMA составила всего 2,43 ГБ. 648 ГБ были «фантомными».

Выяснили, что баг появился в Linux 6.5. Статистика по флоту: на 6.5 серверы были в 52 раза чаще подвержены завышению Committed_AS, и утечка росла примерно на 4,7% в неделю пропорционально uptime. На 6.8 — нет.

LLM нашёл виновника: коммит 408579c изменил возвращаемое значение do_vmi_align_munmap(). В mm/mremap.c в move_vma() условие с < 0 заменили на !, инвертировав логику. Функция сначала уменьшает Committed_AS для старой области, а затем вызывает do_vmi_unmap(). Если тот успешен (а не при ошибке, как должно было быть), повторно увеличивает счётчик. Так при каждом mremap Committed_AS рос. Линус Торвальдс исправил одной строкой, вернув < 0.

Теперь Ubicloud снова включает strict overcommit. Формула: overcommit_kbytes = total_memory_kb × 0.8 + 2 × 1048576. 20% holdback — на структуры ядра (page tables, slab, сетевые буферы). Page cache не считается, так как reclaimable. 2 ГБ — запас на sidecar-процессы (Prometheus, node_exporter, wal-g — Go-программы с большим VSZ). 2 ГБ покрывает >99% серверов. Для реализации используют vm.overcommit_kbytes, а не overcommit_ratio, потому что фиксированная прибавка в 2 ГБ не выражается одним процентом для машин разного размера.

Читать оригинал →