Високий TTFB — це такий тип проблеми з продуктивністю, який одночасно робить усіх упевненими та невірними. Маркетинг каже «сайт повільний», продавець плагінів радить «встановіть це», а ваш хостинг каже «це WordPress». Тим часом користувачі дивляться на порожню сторінку в очікуванні першого байта, ніби це 2006 рік і ми всі знову на модемі.
Це не огляд плагінів. Це виробничий playbook для приведення часу відповіді сервера під контроль: виміряйте, на якому етапі застряг перший байт (мережа, TLS, вебсервер, PHP, база даних, кеш, сховище), усуньте вузьке місце й уникайте змін, що виглядають швидкими на графіку, але тануть під реальним трафіком.
TTFB: реальність — що це (і чим не є)
Time To First Byte — це час від початку запиту клієнтом до моменту отримання клієнтом першого байта тіла відповіді. Це включає:
- резолюцію DNS (іноді, залежно від інструмента)
- TCP-з’єднання
- TLS-обмін
- маршрутизацію запиту (балансувальник навантаження, зворотний проксі)
- обробку вебсервером та очікування апстріму
- роботу застосунку (PHP + WordPress)
- запити до бази даних
- промахи кешу, cache stampede та моменти «чому Redis на 100% CPU?»
- затримку сховища (особливо на шарованих або burst-томах)
TTFB — не чисто «бекенд-час». Це метрика скрізь між клієнтом і сервером, яку бачить клієнт. Ось чому вона корисна й її легко неправильно інтерпретувати. Якщо ви вимірюєте TTFB з ноутбука в готелі — ви дізнаєтесь про готель. Якщо з вимірюєте всередині того самого датацентру — ви дізнаєтесь про свій стек.
Також: низький TTFB не гарантує швидку сторінку. Це лише гарантує, що сервер почав відповідати швидко. Ви все одно можете відправити 7 МБ JavaScript і підпалити браузер. Але коли TTFB високий, зазвичай це не фронтенд-проблема. Це система, яка не доходить до «першого байта» вчасно — зазвичай через очікування чогось.
Правило з позицією: не вважайте високий TTFB за «WordPress повільний». Вважайте це за «запит заблокований». Знайдіть, на чому він заблокований.
Цікаві факти та трохи історії
- TTFB існує довше за Core Web Vitals. Інженери використовують його десятиліттями, бо він добре відображає серверну латентність і вартість мережевих рукостискань.
- WordPress почався в 2003 році як форк b2/cafelog. Ранні шаровані хостинги сформували багато дефолтів: PHP, MySQL і припущення «робіть простіше» щодо кешування.
- PHP opcode-кешування раніше було опційним і хаотичним. До того, як OPcache став стандартом, запуск WordPress означав парсити PHP при кожному запиті — біль TTFB був нормою.
- HTTP/1.1 keep-alive (кінець 1990-х) був тихою революцією в продуктивності. Без нього повторні підключення суттєво збільшували відчутну затримку.
- TLS-обміни колись були дорогими. Сучасний TLS 1.3 зменшив кількість кругів, а відновлення сесії важливе для повторних відвідувачів.
- Кеш запитів MySQL було видалено. (фактично застаріло та видалено), бо він викликав блокування та непередбачувану продуктивність у середовищах з великою кількістю записів — часто в адмінці WP та в WooCommerce.
- «Кеш сторінок вирішує все» був правдою частково коли більшість трафіку була анонімною. Залога вхідних користувачів, персоналізація та кошики змінили правила гри.
- Постійний кеш об’єктів для WordPress доріс пізніше. Persistent object cache (Redis/Memcached) став мейнстрімом, коли сайти WP стали більш динамічними та насиченими плагінами.
- CDN не вбили проблеми з TTFB. Вони можуть приховати їх для закешованих сторінок, але динамічні запити та промахи кешу все одно йдуть до вашого origin, ніби він їм винен гроші.
Швидкий план діагностики
Коли TTFB стрибає, не починайте з встановлення плагінів, зміни тем чи суперечок у Slack. Почніть з трьох швидких питань:
1) Це мережа/TLS чи сервер/застосунок?
- Виміряйте TTFB з зовні та всередині сервера/VPC.
- Якщо внутрішній TTFB нормальний, а зовнішній поганий: думайте про DNS, TLS handshake, балансувальник, маршрутизацію, втрату пакетів або WAF.
- Якщо і внутрішній поганий: це ваш стек застосунку (PHP, БД, кеш, сховище, CPU-контенція).
2) Це один endpoint чи все підряд?
- Тільки головна сторінка? Може бути повільний запит або тема/шаблон робить забагато.
- Тільки wp-admin? Можуть бути аутентифікація, зовнішні виклики або блокування БД.
- Все (включно зі статикою)? Думайте про насичення вебсерверу, CPU steal або обмеження апстріму мережі.
3) Це «повільно завжди» чи «повільно під навантаженням»?
- Повільно навіть при 1 запиті: шукайте вузькі місця для одиничного запиту (DNS-запити з PHP, повільний запит до БД, затримка сховища, запуск PHP/помилки OPcache).
- Швидко при 1 запиті, але повільно при 20: дивіться на розміри пулу, підключення до БД, блокування, cache stampede, обмеження швидкості, насичення CPU.
Умова зупинки: Як тільки ви зможете сказати «ми чекаємо на X Y мс», ви завершили діагностику й можете починати виправляти. Якщо ви не можете цього сказати, ви все ще здогадуєтесь.
Спочатку вимірюйте: побудуйте хронологію одного запиту
TTFB — це одне число. Вам потрібна хронологія. Мета — розбити запит на фази та прикріпити числа до кожної фази:
- Пошук імені (DNS)
- Підключення (TCP)
- Підключення застосунку (TLS handshake)
- Pre-transfer (запит відправлено, очікування відповіді)
- Початок передачі (безпосередньо TTFB)
- Усього (повна відповідь)
З боку сервера ви хочете:
- таймінги Nginx/Apache: request time, upstream response time
- таймінги PHP-FPM: тривалість запиту, стек-трейси slowlog
- таймінги БД: slow query log, очікування блокувань, показник попадань у buffer pool
- системні таймінги: тиск на CPU, черга виконання, латентність I/O, повторні передачі в мережі
Жарт №1: Якщо ваш моніторинг — «перезавантажити сторінку і приглядатись», у вас немає моніторингу; у вас є ритуал.
Цитата, бо вона вічна: Надія — не план.
— Джеймс Кемерон.
Практичні завдання з командами: що запускати, що це означає, яке рішення приймати
Це не «спробуйте, може допоможе». Це кроки, які ви можете виконати на Linux-хості з WordPress, Nginx + PHP-FPM + MariaDB/MySQL (подібні ідеї застосовні до Apache). Кожне завдання містить команду, приклад виводу, що означає вивід і яке рішення приймати.
Завдання 1: Виміряйте TTFB з клієнта за допомогою curl (базова лінія)
cr0x@server:~$ curl -o /dev/null -s -w 'dns=%{time_namelookup} connect=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total}\n' https://example.com/
dns=0.012 connect=0.031 tls=0.082 ttfb=1.247 total=1.392
Значення: DNS/connect/TLS невеликі; TTFB великий. Сервер/застосунок повільно генерує перший байт.
Рішення: Зосередьтеся на обробці на origin (Nginx upstream waits, PHP-FPM, БД, кеші), а не на мережі або TLS.
Завдання 2: Виміряйте TTFB зсередини сервера/VPC (відокремлена мережа від застосунку)
cr0x@server:~$ curl -o /dev/null -s -w 'connect=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total}\n' https://127.0.0.1/
connect=0.000 tls=0.000 ttfb=1.106 total=1.219
Значення: Все ще повільно внутрішньо. Це не ваш ISP, не DNS, не CDN.
Рішення: Інструментуйте сервер: логи вебсерверу, статус/slowlog PHP-FPM, повільні запити БД, перевірки ресурсів системи.
Завдання 3: Швидко розділіть статичне та динамічне
cr0x@server:~$ curl -o /dev/null -s -w 'ttfb=%{time_starttransfer} total=%{time_total}\n' https://example.com/wp-content/uploads/2025/01/logo.png
ttfb=0.045 total=0.046
Значення: Статика в порядку; динаміка — проблема.
Рішення: Не марнуйте час на тонке налаштування sendfile або gzip зараз. Ідіть прямо до PHP/БД/кешування.
Завдання 4: Увімкніть таймінги запитів у логах Nginx (дивіться upstream time)
cr0x@server:~$ sudo grep -R "log_format" /etc/nginx/nginx.conf /etc/nginx/conf.d 2>/dev/null
/etc/nginx/nginx.conf:log_format main '$remote_addr - $host [$time_local] "$request" $status $body_bytes_sent '
/etc/nginx/nginx.conf:'"$http_referer" "$http_user_agent" rt=$request_time urt=$upstream_response_time uct=$upstream_connect_time';
Значення: У вас вже є змінні request_time та upstream timings. Добре.
Рішення: Переконайтеся, що активний server block використовує цей log_format, і потім аналізуйте повільні запити.
Завдання 5: Знайдіть найповільніші запити у логах Nginx (реальні URL, реальні таймінги)
cr0x@server:~$ sudo awk '{for(i=1;i<=NF;i++) if($i ~ /^rt=/){sub("rt=","",$i); print $i, $0}}' /var/log/nginx/access.log | sort -nr | head -n 5
2.914 203.0.113.10 - example.com [27/Dec/2025:10:11:09 +0000] "GET /product/widget/ HTTP/2.0" 200 84512 "-" "Mozilla/5.0" rt=2.914 urt=2.902 uct=0.000
2.501 203.0.113.11 - example.com [27/Dec/2025:10:10:58 +0000] "GET / HTTP/2.0" 200 112340 "-" "Mozilla/5.0" rt=2.501 urt=2.489 uct=0.000
Значення: request_time (rt) приблизно відповідає upstream_response_time (urt). Nginx не є вузьким місцем; він чекає на upstream (PHP-FPM).
Рішення: Перейдіть до PHP-FPM: насичення пулу, повільні скрипти, зовнішні виклики, OPcache.
Завдання 6: Перевірте стан пулу PHP-FPM і насичення
cr0x@server:~$ curl -s http://127.0.0.1/fpm-status?full | sed -n '1,25p'
pool: www
process manager: dynamic
start time: 27/Dec/2025:09:31:02 +0000
start since: 2430
accepted conn: 18291
listen queue: 17
max listen queue: 94
listen queue len: 128
idle processes: 0
active processes: 24
total processes: 24
max active processes: 24
max children reached: 63
slow requests: 211
Значення: Є listen queue і max children reached не нульове. PHP-FPM насичений: запити чекають, перш ніж розпочати виконання PHP.
Рішення: Налаштуйте PHP-FPM: збільште pm.max_children, якщо CPU/RAM дозволяють, і зменшіть витрати на запит (OPcache, БД, кешування). Також переконайтеся, що ви не ховаєте повільну БД за «більше дітей».
Завдання 7: Перевірте PHP-FPM slowlog, щоб зрозуміти, куди йде час
cr0x@server:~$ sudo tail -n 30 /var/log/php8.2-fpm/www-slow.log
[27-Dec-2025 10:11:09] [pool www] pid 22107
script_filename = /var/www/html/index.php
[0x00007f2a2c1a3f40] curl_exec() /var/www/html/wp-includes/Requests/src/Transport/Curl.php:205
[0x00007f2a2c19b8a0] request() /var/www/html/wp-includes/Requests/src/Session.php:214
[0x00007f2a2c193250] wp_remote_get() /var/www/html/wp-includes/http.php:197
[0x00007f2a2c18aa10] some_plugin_check_updates() /var/www/html/wp-content/plugins/some-plugin/plugin.php:812
Значення: Плагін робить вихідний HTTP-виклик під час генерації сторінки. Це вб’є TTFB, коли віддалений сервіс повільний або заблокований.
Рішення: Видаліть/замініть таку поведінку плагіна, перемістіть її в cron/фон або агресивно кешуйте результат. Також перевірте egress DNS і правила фаєрволу.
Завдання 8: Перевірте, чи увімкнений OPcache і чи він не відчуває дефіциту пам’яті
cr0x@server:~$ php -i | grep -E 'opcache.enable|opcache.memory_consumption|opcache.interned_strings_buffer|opcache.max_accelerated_files|opcache.validate_timestamps'
opcache.enable => On => On
opcache.memory_consumption => 128 => 128
opcache.interned_strings_buffer => 8 => 8
opcache.max_accelerated_files => 10000 => 10000
opcache.validate_timestamps => On => On
Значення: OPcache увімкнений, але 128 МБ і 10k файлів можуть бути замало для сайтів з великою кількістю плагінів. Перевірка часових міток у продакшні додає перевірки файлової системи.
Рішення: Збільште пам’ять OPcache і max файлів; розгляньте встановлення opcache.validate_timestamps=0 для незмінних деплоїв (з рестартом при деплої).
Завдання 9: Перевірте повільні запити бази даних (ви не можете кешувати блокування)
cr0x@server:~$ sudo tail -n 20 /var/log/mysql/mysql-slow.log
# Time: 2025-12-27T10:10:58.114221Z
# Query_time: 1.873 Lock_time: 0.001 Rows_sent: 20 Rows_examined: 945321
SET timestamp=1766830258;
SELECT option_name, option_value FROM wp_options WHERE autoload = 'yes';
Значення: Запит autoload option сканує занадто багато рядків. Зазвичай це через забиті autoloaded options (плагіни люблять класти сміття) або відсутні/неефективні індекси разом із роздутою таблицею.
Рішення: Зменшіть блоату autoload, перегляньте розмір wp_options і підтвердьте індекси. Розгляньте persistent object cache, щоб уникнути повторних звернень до опцій, але виправте й основний блоат.
Завдання 10: Перевірте здоров’я InnoDB buffer pool (чи читаєте з диска?)
cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';"
+---------------------------------------+----------+
| Variable_name | Value |
+---------------------------------------+----------+
| Innodb_buffer_pool_read_requests | 98765432 |
| Innodb_buffer_pool_reads | 1234567 |
+---------------------------------------+----------+
Значення: Багато читань ідуть з диска (Innodb_buffer_pool_reads не маленький). Це додає латентності і підвищує TTFB під навантаженням.
Рішення: Збільшіть innodb_buffer_pool_size (в межах доступної RAM), зменшіть розмір датасету (очищення) і перевірте латентність сховища.
Завдання 11: Виявляйте очікування блокувань БД в реальному часі (режим «все застрягло»)
cr0x@server:~$ mysql -e "SHOW PROCESSLIST;" | head -n 12
Id User Host db Command Time State Info
291 wpuser localhost wp Query 12 Sending data SELECT * FROM wp_posts WHERE post_status='publish' ORDER BY post_date DESC LIMIT 10
305 wpuser localhost wp Query 9 Locked UPDATE wp_options SET option_value='...' WHERE option_name='woocommerce_sessions'
Значення: Запити чекають на блокування. Це часто виглядає як «випадкові стрибки TTFB», бо деякі запити застрягають за довгим записом чи запитом.
Рішення: Ідентифікуйте блокуючий запит, виправте патерн (індекси, зменшіть записи сесій, уникайте штормів автозапису), і розгляньте ізоляцію важких адмінських робіт від фронтенд-трафіку.
Завдання 12: Перевірте тиск на CPU і чергу виконання (чи чекають запити на CPU?)
cr0x@server:~$ uptime
10:12:33 up 12 days, 3:41, 2 users, load average: 18.42, 17.90, 16.85
Значення: Середнє завантаження дуже високе. Якщо у вас, скажімо, 8 vCPU, ви перевищили потужність і PHP-воркери стоятимуть у черзі.
Рішення: Зменшіть витрати CPU на запит (кеш, OPcache, менше плагінів), обмежте конкуренцію або додайте CPU. Не збільшуйте сліпо кількість PHP-FPM children на машині, що насичена CPU.
Завдання 13: Знайдіть сплески I/O-латентності (сховище може домінувати в TTFB)
cr0x@server:~$ iostat -xz 1 3
Linux 6.1.0 (server) 12/27/2025 _x86_64_ (8 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
22.10 0.00 6.30 9.80 0.00 61.80
Device r/s rkB/s rrqm/s %rrqm r_await rareq-sz w/s wkB/s w_await aqu-sz %util
nvme0n1 210.0 18560.0 0.0 0.00 18.40 88.38 95.0 9120.0 42.10 5.12 98.20
Значення: Високий await і майже 100% завантаження: сховище насичене. Читання/записи БД і filesystem-операції PHP будуть гальмувати.
Рішення: Перейдіть на швидше сховище, зменшіть I/O (buffer pool, кешування), розслідуйте шумних сусідів (shared disks) і перевірте наявність резервних робіт або логу-штормів.
Завдання 14: Перевірте повторні передачі та втрати в мережі (тихі вбивці латентності)
cr0x@server:~$ netstat -s | egrep -i 'retrans|segments retransmited|listen|overflow' | head -n 12
14572 segments retransmited
0 listen queue overflows
0 listen queue drops
Значення: Є retransmits. Якщо вони швидко зростають, у вас втрата пакетів або перевантаження.
Рішення: Перевірте помилки інтерфейсу, мережу апстріму і здоров’я балансувальника. Високі retransmits можуть збільшити TTFB, навіть якщо застосунок в порядку.
Завдання 15: Переконайтеся, що Nginx не буферизує вас у латентність (рідко, але реально)
cr0x@server:~$ sudo nginx -T 2>/dev/null | egrep -n 'proxy_buffering|fastcgi_buffering|fastcgi_read_timeout|keepalive_timeout' | head -n 20
42: keepalive_timeout 65;
117: fastcgi_read_timeout 60s;
118: fastcgi_buffering on;
Значення: Буферизація увімкнена. Зазвичай це допомагає пропускній здатності, але може приховувати стрімінгові відповіді і ускладнювати діагностику. Таймаути — нормальні.
Рішення: Не вимикайте буферизацію з вірою в диво. Використовуйте її осмислено (здебільшого залишайте увімкненою). Якщо у вас є довгі запити, виправте причину, а не таймаут.
Завдання 16: Підтвердіть HTTP keep-alive і повторне використання з’єднань (зменшення вартості рукостискань)
cr0x@server:~$ curl -I -s https://example.com/ | egrep -i 'server:|connection:|keep-alive|alt-svc|via'
server: nginx
connection: keep-alive
Значення: Keep-alive увімкнено. Добре. Якби ви бачили connection: close всюди, ви платили б за додаткові рукостискання повторно.
Рішення: Залишайте так. Налаштуйте таймаути розумно. Не «оптимізуйте», закриваючи з’єднання раніше, якщо ви не любите самостійно спричиняти затримки.
Завдання 17: Шукайте зовнішні виклики з WordPress на рівні мережі
cr0x@server:~$ sudo ss -tpn | grep php-fpm | head -n 10
ESTAB 0 0 10.0.0.10:48722 93.184.216.34:443 users:(("php-fpm8.2",pid=22107,fd=15))
ESTAB 0 0 10.0.0.10:48724 151.101.2.132:443 users:(("php-fpm8.2",pid=22111,fd=17))
Значення: PHP-FPM воркери роблять вихідні HTTPS-з’єднання під час обслуговування запитів. Це бомба уповільнення TTFB.
Рішення: Ідентифікуйте, який плагін/тема це робить; перемістіть у фон; кешуйте результати; встановіть жорсткі таймаути; блокуйте небажаний egress, якщо можете.
Завдання 18: Швидка перевірка «що змінилося?» (оновлення пакунків, деплої, рестарти)
cr0x@server:~$ sudo journalctl -p warning -S "2 hours ago" | head -n 12
Dec 27 09:02:11 server php-fpm8.2[1022]: WARNING: [pool www] server reached pm.max_children setting (24), consider raising it
Dec 27 09:05:44 server mariadbd[881]: Aborted connection 305 to db: 'wp' user: 'wpuser' host: 'localhost' (Got timeout reading communication packets)
Значення: У вас є підтвердження: насичення PHP-FPM і таймаути комунікації з БД (можливо через перевантаження).
Рішення: Трактуйте це як питання потужності/латентності, а не як «таємничу повільність WordPress». Виправте налаштування пулу і продуктивність БД; розгляньте масштабування.
Виправлення, що дійсно зменшують TTFB
1) Перестаньте робити роботу на кожен запит: кеш сторінок там, де це має сенс
Для анонімного трафіку повне кешування сторінок — найсильніша важіль. Не через модний тренд, а тому що воно прибирає PHP і БД з шляху запиту. Ви можете зробити це на:
- Nginx fastcgi_cache
- Reverse proxy cache (Varnish або керований edge cache)
- CDN кеш для HTML (уважно з cookie і логікою vary)
Але треба бути чесним щодо сайту:
- Якщо у вас багато WooCommerce, цінний трафік здебільшого для користувачів під логіном або з cookie. Хітрейт кешу сторінок може бути низьким.
- Якщо багато персоналізації, кешування стає задачею маршрутизації: що відрізняється і чому?
Порада для прийняття рішень: якщо головна сторінка піддається кешуванню, але сторінки продуктів — ні, кешування все одно вигідне. Почніть з того, що можна закешувати.
2) Постійний кеш об’єктів: Redis (або Memcached) для часто повторюваних частин WordPress
WordPress часто виконує одні й ті ж пошуки: опції, post meta, зв’язки термінів, transients. Persistent object cache зменшує звернення до БД і покращує TTFB для динамічних сторінок.
На що дивитись:
- Object cache не виправить повільні зовнішні HTTP-виклики.
- Object cache не виправить блокування БД, спричинені патернами записів.
- Object cache може нашкодити, якщо перетвориться на спільний смітник без гігієни ключів і стратегії евікшну.
3) PHP-FPM: налаштуйте під ваше залізо, а не під надії
Параметри PHP-FPM легко змінити і ще легше зіпсувати. Ваша мета — уникнути чергування, зберігаючи CPU та RAM в межах.
- Симптом: зростання
listen queueіmax children reachedпід час сплесків. - Виправлення: Збільште
pm.max_children, якщо є запас, і зменшіть час на запит, щоб діти швидше звільнялися. - Анти-виправлення: подвоєння children на машині з дефіцитом RAM. Ви почнете свопитись, а своп — це просто латентність з додатковими кроками.
Обчисліть грубий верх: виміряйте пам’ять на процес PHP-FPM під навантаженням, потім встановіть children так, щоб сумарно не перевищити доступну RAM мінус БД і системний кеш.
4) OPcache: дайте PHP «мозок» і достатньо місця для нього
Без OPcache PHP повторно парсить скрипти і витрачає CPU та файлові виклики. З OPcache, але занадто малою пам’яттю, починається thrashing і ви повертаєтесь до повільних стартів.
Робіть таке:
- Збільште
opcache.memory_consumptionдля сайтів з великою кількістю плагінів. - Збільште
opcache.max_accelerated_filesдостатньо, щоб покрити тему + плагіни. - Віддавайте перевагу незмінним схемам деплою; вимикайте перевірку часових міток, коли це безпечно.
5) База даних: зробіть гарячі запити дешевими і рідкісними блокування
Більшість болю БД у WordPress — не екзотика. Це базові речі, які ви вже знаєте, що потрібно робити:
- Контролюйте autoload у
wp_options. Плагіни його роздувають; ви платите податок на кожний запит. - Індексируйте те, що запитуєте. Багато плагінів постачають сумнівні запити і вважають, що БД «розбереться». БД розбереться — як бути повільною.
- Дайте InnoDB достатньо buffer pool. Якщо читання йдуть із диска, TTFB перетворюється на бенчмарк сховища.
- Слідкуйте за таблицями з великою кількістю записів під навантаженням записи (sessions, options, postmeta).
6) Сховище: усуньте хвостову латентність, а не лише середнє
TTFB страждає через хвостову латентність. 99-й перцентиль важливий, бо це те, що запам’ятають користувачі і що спрацює в моніторингу.
Поширені вбивці TTFB у сховищі:
- Спільні мережеві томи з кредитами на сплески (швидко, поки не вичерпано)
- Резервні копії, що працюють на тому ж диску, що й MySQL
- Лог-шторм (debug, access, slow логи на одному томі)
- Часті виклики метаданих файлової системи (багато
stat()якщо OPcache з перевіркою часових міток і без налаштування realpath cache)
7) Зовнішні виклики: зробіть їх асинхронними або змусьте зникнути
Якщо WordPress чекає на сторонні API під час обробки запиту, ви перетворили своє SLA на їхнє хобі. Встановіть жорсткі таймаути для вихідних викликів, кешуйте результати і переносіть не критичну роботу в cron/чергу.
Жарт №2: Сторонні API як ліфти: коли ви спізнюєтесь, кожен з них саме зайнятий.
8) CDN і edge: використовуйте їх, щоб зменшити навантаження на origin, а не щоб приховати пожежу на origin
CDN корисний, коли ви можете кешувати HTML і статичні файли. Він також зменшує вартість TLS і TCP для глобальних користувачів. Але якщо ваш origin має TTFB 1.5 с для некешованих динамічних сторінок, CDN просто ввічливо доставляє погану новину швидше.
Три корпоративні міні-історії (і уроки, за які вони заплатили)
Історія 1: Інцидент, спричинений хибним припущенням
Команда мала WordPress-сайт, який був «нормальний» в офісі і «жахливий» для користувачів за кордоном. Вони взяли кілька Lighthouse-звітів і звинуватили тему. Після спринту тему полегшили, а проблема залишилась.
Під час розбору інциденту хтось нарешті виміряв TTFB з двох місць: з сервера і з віддаленого регіону. Всередині VPC TTFB був стабільно низький. Ззовні він стрибав і хвилювався. Застосунок не був повільним; шлях до застосунку був.
Балансувальник термінував TLS, і WAF-правило виконувало глибоку інспекцію на кожному запиті — включно з кешованими. Під піковим трафіком черга інспекції росла, і TTFB ріс разом з нею. Усі вважали «WAF практично безкоштовний». Ні.
Вони виправили це, звузивши набір правил, обминаючи інспекцію для відомих статичних шляхів і додавши потужність там, де це було важливо. Зміни теми трохи допомогли, але справжнє виправлення — визнати неправильне припущення: «TTFB = час PHP». Так не було.
Історія 2: Оптимізація, що відкотилась назад
Інша компанія вирішила виграти бій за TTFB, агресивно збільшивши PHP-FPM pm.max_children. Графіки в staging виглядали добре. У продакшні це спрацювало кілька хвилин.
Потім база даних почала таймаутити. Не тому, що її було недостатньо, а тому що нова PHP-конкурентність спровокувала stampede. Кількість одночасних запитів різко підскочила, buffer pool почав активно перемішуватись, і з’явилось блокування в місцях, які ніхто не профілював. TTFB перейшов з «середньо» в «катастрофа».
Щоб додати гостроти, пам’ять зросла. Машина почала свопитись. Коли своп входить у гру, латентність стає інтерпретаційним танцем: іноді нормально, іноді страшно, завжди важко логічно пояснити.
Кінцеве виправлення було нудне: обмежити PHP-конкуренцію до рівня, який БД може витримати, увімкнути persistent object cache, очистити кілька патологічних запитів і правильно налаштувати buffer pool БД. Також додали навантажувальний тест, що вимірював черги, а не лише середню латентність. Урок: якщо ви «оптимізуєте», збільшуючи паралелізм, ви не оптимізуєте — ви домовляєтесь із вашим вузьким місцем.
Історія 3: Нудна, але правильна практика, що врятувала день
Медіа-сайт запускав WordPress за Nginx і кеш-лойером. У них була звичка, яку я люблю: коли продуктивність дивно змінювалась, вони брали три логи перед тим, як щось робити — Nginx access log з таймінгами апстріму, PHP-FPM slowlog і MySQL slow query log.
Одного дня TTFB періодично стрибав. На виклик можна було б зробити звичний танець: рестарт PHP, очистити кеші, звинуватити останній деплой. Замість цього вони дотримались звички. Nginx показав сплески upstream response time. PHP slowlog показав воркерів, застряглих у файлових викликах. MySQL slow log був тихий.
Вони перевірили метрики сховища і знайшли сплески латентності, що збігалися з автоматичним бекапом, який зсунувся у бізнес-години через зміну часової зони. Нікому не довелось гадати. Нікому не довелось сперечатися. Перенесли вікно бекапу і додали сигналізацію на стійке disk await.
Це не було героїчною інженерією. Це була проста операційна гігієна. Така, що виглядає непримітно, поки не врятує вам вихідні.
Поширені помилки: симптом → корінь → виправлення
-
Симптом: Високий TTFB лише для першого запиту після деплою/рестарту
Корінь: «холодний» OPcache, холодний page/object cache, розігрів JIT, порожній DNS-кеш
Виправлення: Розігрійте кеші (preload URL), переконайтеся, що OPcache правильно розмірений, уникайте частих рестартів і за потреби попередньо розв’язуйте критичні зовнішні DNS. -
Симптом: TTFB стрибає під час пік-трафіку, поза піком — нормально
Корінь: Насичення PHP-FPM і черги, ліміти з’єднань БД, cache stampede
Виправлення: Правильний розмірpm.max_children, додайте page/object кеш, реалізуйте блокування кешу або коалесценцію запитів, зменшіть дорогі некешовані ендпоїнти. -
Симптом: wp-admin повільний, фронтенд переважно ок
Корінь: Адмін-запити обходять page cache; важкі запити, перевірки оновлень плагінів, зовнішні виклики
Виправлення: Профілюйте адмін-ендпоїнти; відключіть або розплануйте перевірки оновлень; виправте повільні запити; розділіть адмін-трафік при потребі. -
Симптом: Статичні ресурси швидкі, HTML повільний
Корінь: Домінують PHP/БД; відсутній ефективний кеш; повільні зовнішні виклики
Виправлення: Додайте кеш сторінок там, де безпечно; додайте persistent object cache; приберіть синхронні зовнішні виклики; налаштуйте OPcache і БД. -
Симптом: Випадкові довгі аутлайєри TTFB (погано p95/p99)
Корінь: Хвостова латентність сховища, очікування блокувань БД, ефекти шумних сусідів, cron/бекапи, що накладаються на трафік
Виправлення: Перенесіть БД на стабільне низьколатентне сховище; зменшіть блокування; перенесіть важкі cron/бекапи; додайте алерти на I/O-латентність і очікування блокувань. -
Симптом: Збільшення PHP-FPM children зробило гірше
Корінь: БД стала вузьким місцем; пам’ять призвела до свопінгу
Виправлення: Зменшіть конкуренцію; виміряйте RSS на процес; налаштуйте БД та кеші спочатку; масштабуйтесь вертикально/горизонтально з наміром. -
Симптом: Зовнішній моніторинг показує високий TTFB, внутрішні перевірки в порядку
Корінь: DNS, TLS handshake, WAF/LB-черги, втрата пакетів
Виправлення: Вимірюйте curl timing з кількох регіонів; перевіряйте метрики LB/WAF; налаштуйте TLS resumption; виправте мережеві втрати; обминайте дорогі інспекції для безпечних шляхів. -
Симптом: TTFB погіршився після ввімкнення плагіна кешу
Корінь: Конфігурація кешу спричиняє контенцію, шторм очищення кешу або руйнує OPcache-дружні патерни
Виправлення: Віддавайте перевагу кешуванню на Nginx/проксі де можливо; переконайтеся, що ключі кешу коректні; уникайте очищення всього кешу при дрібних змінах; перевіряйте під навантаженням.
Контрольні списки / покроковий план
Покроково: від «TTFB поганий» до конкретного виправлення за одну робочу сесію
- Встановіть базову лінію: запустіть curl timing з ноутбука і з сервера. Збережіть числа.
- Підтвердіть охоплення: протестуйте головну сторінку, типовий пост, сторінку продукту і статичний ресурс. Визначте «лише динаміка» повільність.
- Читайте правду від вебсерверу: увімкніть/підтвердіть поля Nginx
rtіurt. Знайдіть найповільніші URL. - Перевірте насичення PHP-FPM: погляньте на
listen queueіmax children reached. Вирішіть, чи відбувається чергування. - Увімкніть PHP slowlog (якщо ще ні) з порогом, який ви витримуєте (наприклад, 2s). Захопіть стек-трейси під час повільності.
- Перевірте на вихідні виклики: slowlog стеки і вивід
ss. Якщо є — виправте це в першу чергу — інше не має значення. - Перевірте повільні запити БД і блокування: увімкніть slow query logging; перегляньте processlist на предмет блокувань.
- Перевірте латентність сховища: використайте
iostat. Якщо await і util погані, вважайте сховище основним підозрюваним. - Впровадьте одну зміну за раз: шар кешу, налаштування OPcache, розмір buffer pool БД, налаштування пулу PHP.
- Підтвердіть тими ж вимірюваннями: curl timing і таймінги з логів. Зберігайте докази «до/після».
Операційний контрольний список: щоб TTFB не регресував наступного місяця
- Access логи містять час запиту і upstream response time.
- Статус PHP-FPM захищений і моніториться (черга, активні процеси, max children reached).
- Повільні логи увімкнені (PHP і БД) з розумною ротацією.
- Бекапи і важкі cron-роботи заплановані і моніторяться на дрейф часу виконання.
- Процес деплою розігріває кеші або принаймні уникає одночасних холодних стартів на всіх нодах.
- Є навантажувальний тест, що вимірює p95/p99, а не лише середнє.
- Політика по плагінам: все, що робить синхронний зовнішній I/O у шляху запиту, вважається ризиком для продакшну.
Поширені питання
1) Який «хороший» TTFB для WordPress?
Для кешованого HTML на edge або проксі: десятки мілісекунд до низьких сотень. Для некешованого динамічного WordPress: прагніть до p50 менше 400 мс на здоровому стеку. Ваш p95 — це те, що вам болітиме публічно.
2) Чому TTFB високий, навіть якщо завантаження CPU виглядає низьким?
Тому що ви можете чекати, не будучи обмеженим CPU: очікування I/O, блокування БД, вихідні HTTP-виклики, DNS-таймаути або насичення черги PHP-FPM, коли воркери затримані в іншій роботі.
3) Мені потрібен плагін кешу?
Не обов’язково. Кешування на рівні сервера/проксі часто передбачуваніше і швидше. Плагіни можуть допомагати з керуванням очищення кешу, але також додавати складність і режими відмов. Якщо ви використовуєте плагін, ставтесь до нього як до інфраструктури: налаштуйте, виміряйте і протестуйте.
4) Якщо я додам Redis, чи TTFB магічно впаде?
Ні. Redis допомагає, коли БД виконує багато повторюваних читань (опції, мета, таксономії). Він не виправить повільний PHP-код, зовнішні виклики або блокування БД, спричинені записами. Це важіль, а не релігія.
5) Чи просто збільшити PHP-FPM pm.max_children?
Тільки якщо ви довели, що черга існує і у вас є запас CPU/RAM. Інакше ви збільшите конкуренцію до тих пір, поки не вдаритесь у реальний вузький місце (часто БД) і не погіршите латентність.
6) Чому TTFB раптом стрибає вночі?
Бо «ніч» — це час, коли працюють cron-роботи і бекапи. Шукайте дампи БД, знімки файлової системи, ротацію логів, сканування на наявність шкідливого ПЗ або призначені завдання плагінів, що зсунулися у вікно піку.
7) Як зрозуміти, чи база даних — вузьке місце без складного APM?
Корелюйте: сплески upstream response time у Nginx + PHP slowlog показує час у викликах до БД + MySQL slow log/lock waits показують активність. Якщо БД домінує, ви побачите це в усіх цих сигналах.
8) HTTP/2 або HTTP/3 зменшують TTFB?
Вони можуть зменшити накладні витрати на з’єднання і покращити мультиплексування, особливо в мережах з втратою пакетів. Але якщо ваш origin витрачає 1–2 секунди на побудову HTML, протокол вас не врятує. Виправте origin спочатку, потім користуйтеся вигодами протоколу.
9) CDN вартий, якщо мій TTFB високий?
Так — для статичних ресурсів і кешованого HTML, бо це зменшує навантаження на origin і покращує глобальну латентність. Але не використовуйте CDN як привід ігнорувати продуктивність origin. Промахи кешу все одно покажуть правду.
10) Чому wp-admin повільніший після того, як я «оптимізував» фронтенд?
Бо більшість оптимізацій були для кешованих сторінок. wp-admin динамічний, часто обходить кеші і викликає додаткові поведінки плагінів (перевірки оновлень, аналітика, зовнішні API). Профілюйте його окремо.
Наступні кроки, які можна зробити цього тижня
- Отримайте числа: curl timing зсередини і ззовні, і логи Nginx з upstream таймінгами.
- Доведіть або спростуйте чергування PHP-FPM: увімкніть і перевірте
/fpm-status; спостерігайтеmax children reachedі listen queue під навантаженням. - Увімкніть PHP slowlog і дайте йому показати неприємну правду (зазвичай зовнішні виклики або один дорогий шлях коду).
- Увімкніть DB slow query log на день, потім виправте головні проблеми і блоат autoload.
- Провалідируйте латентність сховища за допомогою
iostatпід час піку. Якщо ви на bursty або shared томах — плануйте міграцію. - Додайте кешування свідомо: кеш сторінок для анонімного трафіку; object cache для повторюваних динамічних звернень; тримайте ключі кешу і поведінку очищення в розумних межах.
- Повторно протестуйте і закріпіть результати: ті ж curl вимірювання, та ж інспекція логів, і невеликий навантажувальний тест. Запишіть результати, щоб майбутній ви не повторював ту саму детективну роботу.
Якщо ви зробите вимірювання і все одно не зможете пояснити, куди йде час, це сигнал додати трасування (навіть легке) і перестати гадати. Продукційні системи не реагують на відчуття.