B3x.by Связаться
14 мая 2026 г. · BitrixVM · MySQL · post-mortem

OOM-killer убивает MySQL на BitrixVM: 23 GB RAM, 10 GB innodb и Apache, которые не помещаются

Разбор инцидента на интернет-магазине под BitrixVM: ночью система убивает mysqld из-за нехватки памяти. Виноваты три вещи разом — слишком жирный innodb_buffer_pool под маленькую базу, утечка mod_php в Apache prefork и крошечный swap.

Симптом

Клиент пишет утром: «Ночью сайт лагал, в 00:54 пропадал секунд на 15». Алерт в Telegram подтверждает: mysql_uptime метрика обнулилась в 00:54 — то есть MySQL рестартанулся. Сам себя? Под нагрузкой? Что-то непонятное.

Разведка

Лезем в journalctl за нужный момент:

May 14 00:54:05 server1 kernel: httpd invoked oom-killer: gfp_mask=0x201da
May 14 00:54:05 server1 kernel: Out of memory: Kill process 43812 (mysqld) score 145
May 14 00:54:05 server1 kernel: Killed process 43812 (mysqld), UID 27,
                                total-vm:16238064kB, anon-rss:3633208kB
May 14 00:54:05 server1 systemd[1]: mysqld.service: main process exited, code=killed, status=9/KILL
May 14 00:54:18 server1 systemd[1]: Started MySQL Server.

Картина в трёх строках:

  1. Apache (httpd) попросил у системы памяти, которой не было.
  2. Ядро запустило OOM-killer и убило mysqld (у него был самый высокий oom_score).
  3. systemd через 13 секунд перезапустил mysqld автоматически.

Клиент в этот момент видел: 13 секунд ошибок 5xx, потом ещё 2–3 часа «штормило» — это MySQL прогревал innodb_buffer_pool после холодного старта.

Самое неприятное: за последние 30 дней OOM срабатывал 12 раз. Не разовое везение — системная проблема.

Откуда дефицит памяти

Считаем по факту, что было запущено в момент срабатывания:

80 Apache prefork воркеров × ~200 MB private RSS  ≈ 16 GB
mysqld (innodb_buffer_pool 10G + остальное)        ≈ 10 GB
PHP opcache, система, прочее                       ≈  2 GB
────────────────────────────────────────────────────────
Итого                                              ≈ 28 GB
Имеется RAM                                        = 23 GB
Имеется swap                                       = 0.5 GB (и весь занят)

Дефицит около 5–6 GB. Когда последний Apache-воркер вырастал на критические сотни мегабайт — ядро не могло выделить страницу, шёл OOM-killer, выбирал самый «жирный» процесс, и это был mysqld.

Корни проблемы — их три

1. innodb_buffer_pool_size = 10G под базу 2.3 GB

Проверяем, сколько реально занято данными:

SELECT
  (SELECT VARIABLE_VALUE FROM performance_schema.global_status
     WHERE VARIABLE_NAME='Innodb_buffer_pool_pages_data')  AS pages_data,
  (SELECT VARIABLE_VALUE FROM performance_schema.global_status
     WHERE VARIABLE_NAME='Innodb_buffer_pool_pages_free')  AS pages_free;

Результат:

pages_data = 67 265  →  ~1.03 GB реально занято
pages_free = 584 144 →  ~8.9 GB просто пустует

Из 10 GB пула реально используется ~1 GB. Остальные 9 GB — мёртвый резерв, который ничему не помогает (Innodb_buffer_pool_reads за 16 часов uptime: 51 тыс. промахов на 52 млрд запросов — это уже 99.9999% hit ratio, кэш и так идеален). Эти 9 GB напрямую вкладываются в OOM.

2. Apache MaxRequestsPerChild = 5000

Стандартное значение в BitrixVM. Звучит безобидно, но при 80 воркерах и десятках тысяч запросов в день каждый воркер живёт ~12 часов. За это время mod_php накапливает утечку — медленную, но устойчивую.

ТОП воркеров на момент диагностики:

pid=106155  rss=1020 MB  age=12ч21м
pid=106167  rss= 908 MB  age=12ч20м
pid=105811  rss= 759 MB  age=12ч29м
... (топ-10 от 600 MB до 1 GB, всем 12+ часов)

То есть воркеры не сидят на 50–100 MB как при старте — они раздуваются до гигабайта за полусутки. И самая жадная пачка из них накладывается на «случайный» нерабочий час, когда вместе с этим прокатывается ночной cron-агент Bitrix — и OOM.

3. Swap 511 MB (и весь забит)

На 23 GB RAM — это даже не страховка. При нехватке памяти системе деваться некуда: либо OOM, либо OOM. Нормальный размер swap’а на таком сервере — 2–4 GB: не для постоянной работы, а как буфер для пиков.

Что делаем

В порядке «сильно/слабо вмешиваемся в работающую систему»:

1. innodb_buffer_pool_size 10 GB → 4 GB, instances 8 → 4.

Освобождает 6 GB RAM в моменте, без потерь по производительности (вся база 2.3 GB всё равно умещается в 4 GB кэша с гигабайтом запаса). На BitrixVM правится через custom drop-in:

# /etc/mysql/conf.d/z_bx_custom.cnf
[mysqld]
innodb_buffer_pool_size = 4G
innodb_buffer_pool_instances = 4

Требуется рестарт mysqld (instances динамически не меняется). 10–30 секунд простоя, после рестарта на холодных кэшах ~5 минут «небыстрых» запросов — это нормально.

2. MaxRequestsPerChild 5000 → 300.

Воркеры пересоздаются в 16 раз чаще и не успевают накопить утечку. На BitrixVM — через z_bx_custom.conf:

# /etc/httpd/bx/custom/z_bx_custom.conf
<IfModule prefork.c>
    MaxRequestsPerChild 300
</IfModule>

Применяется через apachectl graceful — без даунтайма.

3. Swap 512 MB → 4 GB. Создаём swapfile:

fallocate -l 4G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
# в конце — убедиться что старый 512M swap отключён или объединён

Без даунтайма и рестартов.

Эффект сразу после применения

ДоПосле
RAM used~7.6 GB1.9 GB
RAM available12 GB19 GB
Swap used511M / 511M (full)367M / 511M → дальше 0/4G
wsd.by response250–800 ms120–270 ms

В первые часы освободилось ~6 GB RAM. Дальше, когда воркеры стали пересоздаваться чаще, профиль стабилизировался — больше OOM на этом проекте мы не видели.

Что взять домой, если у вас Bitrix

  1. Проверьте pages_data в InnoDB через performance_schema.global_status. Если меньше 30% от пула — вы напрасно держите RAM. Хорошее правило: пул должен быть в 1.5–2 раза больше реально занятых страниц.
  2. На BitrixVM по умолчанию MaxRequestsPerChild = 5000 — для сайтов с серьёзным трафиком это слишком много. 300–500 безопаснее и почти не нагружает CPU дополнительными форками.
  3. Никогда не оставляйте swap меньше 1 GB даже на серверах с десятками гигабайт RAM. Это страховка от пиковых перерасходов, ничего более.
  4. OOM-killer один раз в месяц — это уже паттерн. Один раз — может быть случайность. Два — совпадение. Три — точно что-то системное.

Если хотите, чтобы похожее не повторилось у вас — запишитесь на бесплатный аудит. Прогоним 30-минутный диагностический срез по вашему серверу и пришлём отчёт с приоритетами «что чинить».