Ой, ничего не найдено!

К сожалению, по вашему запросу пока ничего нет (но это только пока!), зато вы можете подписаться на нашу замечательную email-рассылку, чтобы не пропустить самое интересное в будущем.

  • 39

Тормозит сайт — виноват не хостинг: тюним nginx и PHP-FPM

  • 3 минуты на чтение

Часть 9 из 14 · Серия «Сервер в бою»

«Сайт тормозит — пора переезжать на хостинг помощнее» — самый дорогой рефлекс в мире маленьких серверов. Дорогой дважды: сначала вы платите за тариф в два раза толще, а потом обнаруживаете, что сайт тормозит и там. Потому что в большинстве случаев тормозил не хостинг — тормозило то, что на нём крутится, а лишние ядра не лечат медленный SQL-запрос и невключённый opcache.

Вторая крайность не лучше: «потюнить сервер» по первой попавшейся статье «50 настроек nginx для максимальной производительности». Половина таких настроек — карго-культ, переписываемый из блога в блог с 2012 года: ритуальные строчки, которые не вредят, не помогают и создают ощущение проделанной работы.

Эта статья — против обеих крайностей. Метод всё тот же, серийный: сначала измеряем, где именно теряется время, потом крутим ровно ту ручку, которая на это время влияет. По дороге разберём четыре ручки из подзаголовка — pm.max_children, opcache, gzip/brotli, keepalive — честно, с ожидаемым эффектом в цифрах. А в конце — галерея карго-культа, чтобы вы узнавали его в лицо.

Шаг ноль: где тормозит? Одна команда и одна строчка в конфиге

Слово «тормозит» описывает минимум четыре разные болезни, и лечатся они в разных местах. Разложить время ответа по полочкам умеет обычный curl:

curl -o /dev/null -s -w "DNS: %{time_namelookup}s\nTCP+TLS: %{time_appconnect}s\nTTFB: %{time_starttransfer}s\nВсего: %{time_total}s\n" https://ваш-сайт/
DNS: 0.021s
TCP+TLS: 0.183s
TTFB: 1.914s
Всего: 2.187s

Читаем: до сервера долетели быстро, а потом почти две секунды ждали первого байта (TTFB — time to first byte). Это время сервер думал: исполнял PHP и ходил в базу. Вот вам и диагноз в первом приближении:

  • Большой TTFB — тормозит бэкенд: PHP, база, — 90% реальных случаев. Наша сегодняшняя (и следующая) статья.
  • TTFB маленький, а грузится долго — тяжёлая страница: мегабайты картинок и скриптов. Это лечится сжатием (ниже) и работой над фронтендом, сервер тут почти ни при чём.
  • Долгий TCP+TLS — сеть/география или проблемы с TLS-сессиями; редкий зверь.
Запуск за 2 минуты
VPN, бот или пет-проект — поднимем за пару минут
VPN, Telegram-бот, своё облако или пет-проект — арендуйте сервер за пару минут и проверьте идею в деле уже сегодня.
Создать сервер

Чтобы видеть эту картину постоянно, а не разово, научим nginx записывать в access.log, сколько думал бэкенд. В nginx.conf:

log_format timed '$remote_addr [$time_local] "$request" $status '
                 'rt=$request_time urt=$upstream_response_time';

и access_log ... timed; в нужном хосте. Теперь у каждого запроса видно rt (всё время) и urt (сколько из него занял PHP-FPM). Если rt=2.1 urt=2.0 — nginx невиновен, всё время съел бэкенд; тюнить nginx в этой ситуации — красить забор горящего дома. Это, кстати, главный вывод статьи, вынесу его жирным: на типичном PHP-сайте почти всё время ответа живёт в PHP и базе. Nginx отдаёт статику и проксирует за микросекунды — там почти нечего ускорять.

Ручка 1: opcache — самый большой выигрыш за пять минут

PHP — интерпретатор: на каждый запрос он читает исходники, парсит и компилирует в байткод. Для приложения на фреймворке это сотни файлов на каждый клик. Opcache кеширует байткод в памяти — компиляция происходит один раз, а не тысячу раз в минуту. Эффект на реальных приложениях — ускорение в разы; это самая недооценённая галочка в PHP просто потому, что все уверены, что она уже включена. Проверяем:

php -i | grep opcache.enable

В современных пакетах он обычно включён, но со скромными лимитами. Рабочий набор в /etc/php/8.3/fpm/conf.d/10-opcache.ini:

opcache.enable = 1
opcache.memory_consumption = 192      ; МБ под байткод; фреймворку 128 мало
opcache.max_accelerated_files = 20000 ; файлов в кэше; у Laravel их тысячи
opcache.validate_timestamps = 1       ; проверять, не изменился ли файл
opcache.revalidate_freq = 60          ; ...не чаще раза в 60 секунд

Про последнюю пару — важная развилка. validate_timestamps = 0 — режим максимальной скорости: PHP вообще не проверяет файлы на изменения, кэш живёт до перезапуска. Минус: после деплоя изменения «не применяются», пока вы не сбросите кэш (systemctl reload php8.3-fpm) — источник многочасовых недоумений «я же исправил, почему не работает?!». Честная рекомендация: держите 1 с revalidate_freq = 60, а на 0 переходите, когда у вас появится автоматический деплой со сбросом кэша в конце — как раз тема тринадцатой статьи.

Ручка 2: pm.max_children — не «побольше», а по расчёту

В прошлой статье мы раздали пулам лимиты воркеров на глазок; пришло время науки. Симптом голодания по воркерам ищется в логе PHP-FPM одной строкой:

grep max_children /var/log/php8.3-fpm.log
SSD/NVMe в комплекте
Пока вы это читаете, чей-то сайт уже летает
NVMe-диски, свежее железо и честные ресурсы без «соседей», которые съедают всю мощность. Разница чувствуется с первого запроса.
Разогнать проект
WARNING: [pool site1] server reached pm.max_children setting (8), consider raising it

Вот она — очередь у кассы: посетителей больше, чем воркеров, лишние ждут (растёт TTFB) или получают 502/504. Рефлекс «поставлю 50 и забуду» — ловушка: 50 воркеров по 60 МБ — это 3 ГБ только на один пул, и здравствуй, OOM killer из первой статьи. Расчёт на салфетке:

# сколько реально ест один воркер этого пула (RSS, в МБ):
ps -o rss= -C php-fpm8.3 | awk '{sum+=$1; n++} END {print sum/n/1024 " MB"}'

Дальше арифметика: (RAM всего − MySQL − nginx − система, ~25% про запас) / размер воркера = потолок суммы max_children по всем пулам. VPS на 4 ГБ, MySQL просит полтора, воркер весит 60 МБ: (4096 − 1536 − 512) / 60 ≈ 34 воркера на всех. Распределяете по важности сайтов — и это число обосновано, а не нагугленно.

И второй уровень той же ручки — режим менеджера. pm = dynamic (дефолт) держит запас тёплых воркеров — хорош для основного сайта. pm = ondemand поднимает воркеры только под запросы и убивает после простоя — идеален для staging и сайтов-визиток из нашей «коммуналки»: не занимают память впустую. pm = static — все воркеры всегда — имеет смысл на выделенном под одно приложение сервере, на общем VPS это расточительство.

Но вот важное: если воркеры упираются в потолок постоянно, правильный вопрос не «как поднять лимит», а «почему воркеры заняты так долго?» Воркер, отвечающий за 100 мс, обслужит 10 запросов в секунду; воркер, висящий 3 секунды, — 0,3. Достаём свидетеля обвинения, заготовленного в прошлой статье, — slowlog:

tail -50 /var/log/php/site1.slow.log

Там — трейсы запросов дольше 5 секунд, и в подавляющем большинстве трейс замрёт на строчке с mysqli_query/PDO->execute. Воркеры не работают — они ждут базу. Поднимать их число в этой ситуации — ставить больше касс, в которых кассиры звонят на склад. Лечить надо склад — и это целиком следующая, десятая статья.

Ручка 3: gzip и brotli — честный, но скромный выигрыш

Теперь про «TTFB маленький, грузится долго». Сжатие текстовых ответов (HTML, CSS, JS, JSON) режет их в 3–5 раз — для посетителя с мобильным интернетом это ощутимо. Nginx из коробки обычно сжимает только HTML; полноценный конфиг:

gzip on;
gzip_comp_level 5;          # выше 6 — рост CPU без заметного выигрыша
gzip_min_length 1024;       # мелочь сжимать дороже, чем отдать
gzip_types text/css application/javascript application/json
           image/svg+xml application/xml;
gzip_vary on;
от 0 ₽ на старте
Свой VPS дешевле, чем кажется
Прозрачные тарифы без сюрпризов в чеке. Платите за то, что реально используете, и масштабируйтесь, когда вырастете.
Смотреть тарифы

Три оговорки, отделяющие пользу от культа. Первая: не сжимайте картинки и видео — jpeg/png/webp уже сжаты, гонять их через gzip — греть CPU ради +0%. Вторая: brotli жмёт на 15–20% лучше gzip, но модуля нет в стандартном пакете nginx — придётся ставить сторонний. Стоит ли? Если ставится легко — да; если ради него надо пересобирать nginx — окупаемость сомнительна, лучше включите режим brotli_static/gzip_static: сжать файлы заранее при деплое и отдавать готовые, ноль CPU в рантайме. Третья: сжатие ускоряет доставку, но никак не лечит медленный бэкенд — если TTFB две секунды, gzip сэкономит вам полсекунды на передаче и оставит все две на ожидании.

Ручка 4: keepalive — почти всегда уже в порядке

Включаю в разбор, потому что он в каждом «топ-10 настроек», — и именно тут больше всего культа. Keepalive — это переиспользование TCP-соединения для нескольких запросов, экономия на рукопожатиях (особенно TLS). Правда состоит из двух частей:

Клиентский keepalive (браузер ↔ nginx) в nginx включён по умолчанию (keepalive_timeout 65). Ритуальное прописывание этой строчки в конфиг ничего не меняет — она уже там. Единственный осмысленный тюнинг тут — HTTP/2 (listen 443 ssl http2;), который мультиплексирует запросы в одном соединении: вот это реально ускоряет загрузку страниц с кучей ресурсов, одна строчка — включите, если ещё нет.

Апстримный keepalive (nginx ↔ бэкенд) имеет смысл, когда nginx проксирует по TCP на другой сервис. Наш PHP-FPM подключён через unix-сокет на той же машине — рукопожатий там и так нет, экономить нечего. Если же вы проксируете на node/python-бэкенд по proxy_pass http://... — вот там upstream с keepalive 16; даёт измеримый эффект.

Галерея карго-культа

Настройки, кочующие из статьи в статью. Общее свойство: на маленьком VPS их эффект — от нуля до отрицательного, но выглядят они внушительно.

Ритуал Реальность
worker_processes 8; руками Дефолт auto уже ставит по числу ядер. Больше ядер — не быстрее, а конкуренция
worker_connections 65535; Дефолтных 768×ядра хватает на тысячи одновременных клиентов. Ваша проблема не здесь
Простыня *_buffer_size из чужого блога Подобраны под чужой сайт 2014 года. Дефолты nginx разумны; менять — только от конкретной ошибки в error.log
access_log off; «для скорости» Экономит микросекунды, отнимает диагностику. После второй статьи серии — просто кощунство
multi_accept on; use epoll; Epoll и так используется; multi_accept на малой нагрузке неотличим от плацебо
Отключить swap «чтобы не тормозил» Swap — страховка от OOM. Без него вместо замедления получите убийство процессов
tcp_nopush/tcp_nodelay «магические пары» Включены/уместны по умолчанию с sendfile; переписывание их — чистый ритуал
«Поднять memory_limit PHP до 2G» Лечит симптом кривого кода и приближает OOM. Лимит — предохранитель, а не педаль газа

Как отличать культ от пользы в будущем, без меня: у осмысленной настройки всегда есть измеримый до/после и симптом, который она лечит. «Все так делают» симптомом не является.

от 0 ₽ на старте
Свой сервер по цене чашки кофе в неделю
Прозрачные тарифы без сюрпризов в чеке. Платите за то, что реально используете, и масштабируйтесь, когда вырастете.
Смотреть тарифы

Итоговая иерархия: куда смотреть по порядку

Если выстроить всё по убыванию эффекта на типичном PHP-сайте, получится честный список приоритетов:

  1. База данных — медленные запросы, отсутствие индексов: секунды. (Статья 10.)
  2. Кэширование — не считать заново то, что не изменилось: превращает секунды в миллисекунды. (Статья 11.)
  3. Opcache — разы, пять минут работы.
  4. pm.max_children по расчёту — лечит очереди и 502 под нагрузкой.
  5. HTTP/2 + gzip — десятки процентов на доставке.
  6. Всё остальное из «топ-50 настроек» — проценты или плацебо.

Заметьте: два первых места занимают темы, которых мы ещё не касались, — и обе впереди. Сервер, где включён opcache, посчитаны воркеры и настроено сжатие, честно отдаёт всё, что позволяет приложение. Дальше ускорять надо само приложение — и начинается это со склада, на который звонят все кассиры. В следующей статье идём в MySQL: почему база на маленьком VPS съедает всю память, что такое innodb_buffer_pool и как slow query log находит те самые запросы, на которых висели наши воркеры.

Хостинг Siteko

VDS с первого дня

Свой сервер Siteko под полным контролем

NVMe-диски, root-доступ и честные ресурсы без «соседей». Поднимите VPN, бота или прод-проект и масштабируйтесь без тесных рамок.

  • Root-доступ полная свобода настройки под ваш стек.
  • NVMe и свежее железо скорость заметна с первого запроса.
  • Перенос бесплатно поможем переехать без простоя.
Выбрать VDS