Назад в блог

Bitrix на выделенных ядрах: что стало видно в prod-графиках после переезда с shared-хостинга

Клиент пишет: «Мы всё перенастроили по вашим рекомендациям. OPcache включён, Redis-сессии работают. SQL-индексы тоже — проверяли отдельно. Вечерний пик всё равно роняет сайт».

Смотрю в метрики. OPcache hit rate — 97%. MySQL slow log пустой. PHP-FPM workers — все живые. Но в логах в промежутке с 18:30 до 21:00 — паузы по 2-4 секунды там, где быть их не должно.

Открываю метрики хостинга. CPU-steal — 28-42%.

Вот и ответ.


Что такое CPU-steal и почему его нет в стандартном мониторинге Bitrix

CPU-steal — это процент времени, которое виртуальная машина провела в очереди на получение процессорного времени у гипервизора. Проще: ваши PHP-воркеры готовы работать, но гипервизор занят соседними VM на том же физическом хосте.

В Bitrix нет дашборда с CPU-steal. Его нет в стандартном мониторинге ISPmanager. Его не видно в MySQL slow log, потому что запросы сами по себе быстрые. Единственное место — системные метрики: vmstat, iostat -x, или облачная панель провайдера, если они её показывают.

Когда CPU-steal стабильно выше 10-15%, это уже проблема. 28-42% в пики — это значит, что каждый четвёртый-третий такт процессора ваш сервер ждёт, пока сосед освободит физическое ядро.

В нашем кейсе — интернет-магазин, 28k SKU, Bitrix на shared VPS — всё это было невидимо до тех пор, пока нагрузка не достигала порога. Это тот самый проект, где мы до этого прошли все code-level оптимизации и упёрлись в стену. Днём, при 20-30 одновременных запросах, всё работало. В пик, при 80-100, начиналось.


Как shared-среда обнуляет code-level оптимизации

PHP-FPM worker получает запрос и начинает выполнять PHP-код. Без OPcache каждый раз парсит файл заново. С OPcache берёт скомпилированный opcode из памяти. Это быстро.

Но «быстро взять opcode из памяти» не поможет, если CPU-планировщик гипервизора не даёт процессу процессорное время. Worker не ждёт диск. Не ждёт MySQL. Он ждёт CPU.

В нашем случае:

  • Время выполнения PHP-скрипта в одиночном тесте: 180ms
  • Время того же запроса в пик нагрузки (80+ concurrent): 1.8-3.2s
  • MySQL запросы в slow log: 0 (всё укладывалось в 50ms)
  • OPcache hit rate: 97%
  • CPU-steal в этот момент: 35-40%

Оптимизация кода имеет потолок. Если CPU физически занят другими VM, никакой тюнинг PHP не даст прироста сверх этого потолка. Мы его достигли.

Ещё один эффект: PHP-FPM pm.max_children мы уже поправили с 5 до 38 (как это делается). Но при высоком CPU-steal больше воркеров не помогает — каждый воркер просто дольше стоит в очереди планировщика. Но при высоком CPU-steal больше воркеров не помогает — каждый воркер просто дольше стоит в очереди планировщика.


Что изменилось в prod-графиках после переезда

Переехали на выделенный сервер у того же облачного провайдера. Не добавляли RAM. Не меняли конфигурацию PHP. Не трогали код.

Результаты через 72 часа после переезда:

  • CPU-steal: 35-40% в пики → 0-1% (выделенные ядра, нет конкуренции)
  • TTFB p95 в вечерний пик: 3.1s → 0.85s
  • TTFB p50 в обычное время: 0.41s → 0.38s (почти без разницы — днём и раньше было нормально)
  • Timeout'ы и 504-ошибки в пик: 180-220/час → 0
  • PHP-FPM: workers перестали «толпиться» в состоянии ожидания CPU

p50 почти не изменился. Это нормально — днём shared CPU справлялся. Вся боль была в p95 под нагрузкой, и именно там переезд дал результат.

Один момент, который меня удивил: после переезда обнаружилось, что на старом хосте мы немного переплачивали за OPcache-оптимизации, которые давали меньше 20% прироста в условиях CPU-steal. На выделенном те же настройки дали полный эффект.


Три признака, что проблема в хостинге, а не в коде

Перед тем как бежать к хостингу — проверьте, что это действительно его вина. Три признака, которые я теперь смотрю в первую очередь:

Первое — деградация под нагрузкой, а не линейная. Если при 20 запросах сайт работает нормально, а при 80 — ломается, и MySQL при этом живой — смотрите CPU-steal. Линейная деградация обычно означает память или диск.

Второе — время выполнения PHP в профайлере не объясняет TTFB. Если xhprof показывает 200ms, а TTFB в браузере — 2 секунды, и это воспроизводится стабильно только под нагрузкой — вы, скорее всего, видите scheduling delay.

Третье — MySQL slow log пустой, но пользователи жалуются на медленные страницы. Именно этот случай. Slow log отлавливает медленные запросы, но не видит, что PHP-процесс 800ms ждал CPU перед тем, как вообще начать этот запрос.

Как проверить: vmstat 1 10 — колонка st в секции cpu. Больше 10-15% в обычное время — повод для разговора с провайдером или переезда.


Когда выделенный сервер оправдан, а когда нет

Переезд на dedicated CPU не бесплатный. У нас вышло +$60/месяц. Это оправдано, если:

  • Есть выраженные пиковые нагрузки (вечер, акции, рассылки)
  • Код уже оптимизирован на уровне PHP-FPM, OPcache, сессий, SQL
  • CPU-steal стабильно выше 15% в эти пики

Не оправдано, если:

  • Slow log показывает запросы по 2-5 секунд — это код, не хостинг
  • OPcache вообще не настроен или validate_timestamps=1 в проде (базовые настройки)
  • PHP-FPM pm.max_children всё ещё дефолтный (5-10)

Короче: сначала закройте базовые PHP-оптимизации. Если после этого пики всё ещё роняют сайт — смотрите CPU-steal. Выделенный сервер не первый шаг. Это то, что делаешь, когда всё остальное уже сделано.


Для нашего проекта всё сошлось: код был оптимизирован, инфраструктура не была. Три месяца тюнили PHP — и упёрлись в потолок, который был не в PHP.

CPU-steal — это метрика, которую я теперь проверяю в числе первых, когда клиент говорит «всё правильно сделали, но медленно». Иногда ответ в коде. Иногда — в том, что физическое ядро занято кем-то другим.