Файловые сессии валят Битрикс в пик при спокойном процессоре
Как отличить очередь на блокировке файла сессии от нехватки CPU и когда переносить сессии Битрикса в Redis отдельно от кеша.

В пик акции сайт на Битриксе отдаёт 504, PHP-FPM весь в Running, а CPU и память ещё спокойные. Админ уже смотрит MySQL и железо — хотя воркеры часто стоят не на расчёте, а на блокировке файла сессии одного пользователя с кучей параллельных AJAX.
Ниже — как отличить «нет CPU» от «воркеры заняты file lock», что проверить в PHP и bitrix/.settings.php, зачем Redis (или ранний session_write_close) и почему сессии нельзя держать в той же Redis-БД, что и кеш.
Почему FPM красный, а процессор зелёный

Дефолт PHP: session.save_handler = files, файлы вида sess_<id> в session.save_path (часто /tmp). При session_start() обработчик берёт exclusive lock на файл сессии и держит его до session_write_close() или конца скрипта — с одной сессией в момент времени работает только один скрипт.
Параллельные запросы с одним session ID — вкладки, AJAX каталога, корзины, виджетов — выстраиваются в очередь на flock, а не на CPU. В strace на зависшем FPM-воркере типично видно ожидание flock(... LOCK_EX). Процессор свободен не потому, что «нагрузки нет», а потому что воркеры ждут файл.
В разобранном пике маркетинговой акции (~23:00): CPU около 40%, память в норме, RPS вырос примерно на 30%, Nginx отвечает, а PHP-FPM «молчит» — новые запросы висят. По pm.status_path: все 128 воркеров Busy, listen queue около 340. Оценка конкуренции: сотни одновременных пользователей × несколько AJAX дают тысячи параллельных PHP-запросов, часть из которых блокируется на одном session file.
Порог «тихого» дня: до примерно 100–150 concurrent ещё терпимо; около 200+ с SPA/AJAX-фронтом file lock становится мультипликативным — retry удлиняет очередь. После перехода на Redis-сессии в том же кейсе: p95 каталога 3.8 s → 0.7 s; занятые воркеры 127/128 → 38–42/128; 504 за час акции ~1100 → 0. Сервер «лёг» не от «Meltdown нагрузки», а от очередей на LOCK_EX.
Как подтвердить, что виноваты файловые сессии

Симптом простой: пул Busy при умеренном CPU. Сначала смотрите handler и блокировку файла — не «добавить ядра» и не путать с просто малым pm.max_children.
- php -i | grep -E 'session.save_handler|session.save_path' (или phpinfo / pool conf) — ожидаемо ли ещё files.
- FPM status: все active при CPU <50% → искать блокировки.
- strace -p <pid> -e trace=file,futex на busy-воркер → flock на sess_*.
- lsof | grep sess_ — много дескрипторов на session files (в кейсе — тысячи).
- В Битриксе: grep -A20 "'session'" bitrix/.settings.php — не переопределён ли handler (file / redis / memcache / database).
Смежный отказ файловых сессий (не lock, а запись): полный диск или inode на session.save_path даёт «плавающий» bitrix_sessid и сбои AJAX CSRF — другая картина, но тот же дефолтный files-path.
Свежий community-кейс (07.08.2026): после restore.php на новую BitrixVM сессии падают с «Could not start session by PHP», потому что в .settings.php остался старый host memcache. Инфраструктура сессий живёт отдельно от факта «сайт открылся».
Быстрый обход без Redis и нормальный переход на Redis

Временный обход без смены handler: на read-only роутах сразу после чтения сессии вызвать session_write_close() (или session_start(['read_and_close' => true]), если дальше сессию не пишут). В одном разборе среднее ожидание параллельных Ajax упало с 820 мс до 40 мс. Ограничение: на роутах, которые ещё пишут в $_SESSION, раннее закрытие потеряет изменения; при output_buffering закрытие может откладываться.
Через php.ini / FPM pool (phpredis):
- session.save_handler = redis
- session.save_path = "tcp://127.0.0.1:6379?weight=1&timeout=2&database=1"
- Параметр database в save_path — отдельная logical DB от кеша: иначе FLUSHDB на кеше разлогинит всех.
- TTL сессий завязан на session.gc_maxlifetime (дефолт часто 1440 с); у phpredis lifetime берётся из этой INI.
- Lock в Redis — не file flock; INI redis.session.locking_enabled, redis.session.lock_expire (default = max_execution_time), lock_wait_time, lock_retries. В кейсе пика ставили redis.session.lock_expire = 30.
- read_timeout в save_path в проде лучше задать явно (default 0 — можно зависнуть навсегда при мёртвом Redis).
- Persistence: без AOF/RDB рестарт Redis = потеря всех сессий. Для сессий в кейсе советуют appendonly yes; совет «save "" для сессий» ускоряет, но сознательно жертвует переживаниями рестарта.
Через ядро Битрикс (bitrix/.settings.php, training.bitrix24): с главного модуля 20.5.0+ handlers file, redis, memcache, database (b_user_session). Ключ: 'session' → 'value' → 'handlers' → 'general' → 'type'. Для Redis: type => redis, host/port или servers[]. Если кастомный блок 'session' не задан — обычно идёт нативный PHP session_start() и достаточно php.ini.
Ограничения: что не лечит и что ломает прод
Рост pm.max_children не лечит очередь на flock — только маскирует числом воркеров, пока хватает RAM. Это другой инцидент, чем «мало children и сразу 502».
- Redis не отключает семантику сессии: при включённом locking длинный запрос всё ещё сериализует доступ; слишком короткий lock_expire даёт гонки.
- Общая Redis DB с Bitrix cache = риск массового logout при flush кеша.
- Без persistence рестарт Redis = массовый разлогин (prod ≠ dev).
- Файловые сессии на нескольких веб-нодах требуют sticky session или общий FS; NFS + file lock — известный источник зависаний.
- Memcache как session store без persistence: рестарт = logout; после миграции VM проверить host/port.
Метрики, которые отличают этот инцидент: active FPM ≈ max_children при CPU <50%; рост latency у авторизованных / AJAX-heavy URL; session.save_handler ещё files; после смены handler — падение listen queue без апгрейда железа.
Частые вопросы
Почему при акции сайт лежит, а CPU около 40%?
Параллельные AJAX с одним session ID ждут exclusive lock на файл сессии (flock). Воркеры PHP-FPM заняты ожиданием, а не счётом — поэтому процессор и память могут оставаться спокойными при полной listen queue и 504.
Как быстро проверить, что handler ещё files?
Смотрите session.save_handler / session.save_path через php -i или phpinfo, на busy-воркере — strace с flock на sess_*, в Битриксе — секцию 'session' в bitrix/.settings.php. Картина «все FPM Busy при умеренном CPU» указывает на блокировки, а не на нехватку ядер.
Можно ли обойтись без Redis?
Да, как временный шаг: на read-only роутах сразу после чтения сессии вызвать session_write_close() или стартовать сессию с read_and_close, если дальше её не пишут. В разборе ожидание параллельных Ajax сократилось с сотен миллисекунд до десятков. На роутах с записью в $_SESSION раннее закрытие нельзя применять вслепую.
Почему сессии нельзя класть в ту же Redis-БД, что и кеш?
Параметр database в session.save_path должен быть отдельной logical DB. Иначе FLUSHDB (или аналог) на кеше разлогинит всех пользователей. Плюс без AOF/RDB рестарт Redis сам по себе обнуляет сессии.
Поможет ли просто поднять pm.max_children?
Нет как лечение причины: больше воркеров лишь маскирует очередь на flock, пока хватает RAM. Нужна смена handler (Redis / handlers ядра) или снятие лишней блокировки на read-only путях — иначе пик снова соберёт очередь на тех же session files.
Как BX Pulse помогает поймать такой пик
Ситуация «FPM весь Busy при спокойном CPU» — слепой угол, если смотрят только железо и SQL, а сессии годами остаются на files. Имеет смысл заранее видеть занятость пула и очередь до жалоб с витрины и после смены handler — убедиться, что listen queue падает без апгрейда железа.
BX Pulse следит за доступностью и техническим состоянием проектов на 1С-Битрикс и помогает замечать сбои до обращения клиента. Для этой ситуации настройте контроль пула PHP-FPM и уведомление при росте Busy / listen queue при спокойном CPU — добавьте первый сайт.


