Сплески трафіку самі по собі не вбивають WordPress. Небажана поведінка кешу — вбиває. Сайт виглядає нормально при 30 запитах на секунду… поки кеш головної сторінки не спливає,
відбувається purge, користувачі в сесії потрапляють за межі кешу, і раптом PHP та MySQL починають робити CrossFit без вашої згоди.
Це практична, виробнича конфігурація кешування, яка витримує, коли маркетинг «просто запускає» кампанію, сканер знаходить щось цікаве, або ваш CEO
оновлює головну сторінку 400 разів, щоб «перевірити». Ми побудуємо багатошаровий кеш, визначимо, що не повинно кешуватися, і зробимо історію з purge нудною — бо
нудність це спосіб зберегти доступність.
Що насправді витримує реальний трафік (і що — ні)
Розмови про продуктивність WordPress часто починаються з плагінів. Реальний трафік не цікавиться вашим списком плагінів. Його цікавить, чи може
архітектура віддати 95% запитів, які мають бути ідентичні, не запускаючи PHP взагалі.
Витривала конфігурація має три властивості:
- Багатошаровість: CDN/edge кеш, origin full-page cache, об’єктний кеш, і лише потім обчислення.
- Правильні правила обходу: сесії залогінених користувачів, кошики, прев’ю та адмін не повинні кешуватися так само, як публічні сторінки.
- Контрольована інвалідація: ви очищуєте вузько і передбачувано, уникаючи натовпу запитів при спливанні кешу.
Якщо ваш «кеш» все ще означає «викликати PHP, а потім можливо використати плагін, щоб зберегти HTML на диск», ви оптимізуєте не те. Дисковий ввід/вивід і PHP-воркери
— не те, де має осідати пік трафіку.
Мета: для анонімного трафіку повертати кешовану відповідь із CDN або Nginx за кілька мілісекунд, поки PHP спить, а MySQL спокійно нічого не знає.
Для залогінених та транзакційних потоків зробити динамічний шлях ефективним і стабільним. Ви не зможете закешувати поломаний чек-аут, але зможете
запобігти підпалу всього сайту головною сторінкою.
Факти та історичний контекст (коротко, корисно, трохи тривожно)
- WordPress почався (2003) у час, коли «кешування» часто означало «увімкнути gzip і молитися». Сучасні шаблони трафіку суворіші: боти, скрепери й прев’ю посилань поводяться як постійні навантажувальні тести.
- Memcached передував Redis і був популярним об’єктним кешем для раннього масштабування WordPress; Redis набув популярності пізніше через персистентність, структури даних та операційні інструменти.
- Varnish популяризував HTTP-кешування перед динамічними додатками; Nginx пізніше взяв на себе багато цієї ролі для багатьох команд завдяки FastCGI cache і простішим операціям.
- «Вибух cookie» — це сучасний податок WordPress: аналітика, A/B-тести, чат-виджети, банери згоди — кожний cookie може непомітно вбити хітрейт кешу, якщо ви варіюєте на ньому.
- HTTP/2 не вирішив проблеми бекенду: він покращив обробку з’єднань і мультиплексування, але якщо PHP-FPM насичений, браузер все одно терпляче чекає.
- Більшість WordPress-сайтів — read-heavy доти, поки раптом не стають іншим. Маркетингова кампанія може перетворити «переважно читання» на «усі б’ють /wp-admin/admin-ajax.php» за хвилини.
- Кеш запитів MySQL зник (видалено в MySQL 8). Якщо ви все ще покладаєтесь на це як на концепт, ви живете в музеї з дуже швидким Wi‑Fi.
- OPcache — це не опція для продуктивності PHP; без нього PHP перекомпільовує скрипти під навантаженням і ви платите CPU за привілей бути повільним.
Цільова архітектура: багатошарове кешування, яке можна пояснити
Ось стек, який реально поводиться під тиском:
- CDN (edge): кешує публічні сторінки й ресурси; забезпечує дисципліну ключів кешу; захищає origin від сплесків.
- Origin Nginx full-page cache: FastCGI cache для анонімних GET/HEAD; мікрокешування для певних ендпоінтів за потреби.
- PHP-FPM + OPcache: налаштовані ліміти воркерів; стабільна пам’ять; ні в кого немає «лотереї max_children».
- Redis об’єктний кеш: кешує дорогі WP lookup’и; уникає повторних звернень до БД для опцій/транзієнтів.
- MySQL: налаштований на конкуренцію; видимість повільних запитів; індекси, що відповідають реальності.
- Наблюваність: метрики хітрейту кешу, таймінги upstream та класифікація запитів (cached vs bypass vs dynamic).
Ви помітите, чого немає: «плагін, що обіцяє 10x швидкість». Плагіни можуть допомогти з інтеграцією та purge hooks, але основний механізм виживання знаходиться на HTTP-рівні. Саме там ви зупиняєте навантаження від попадання у runtime.
Одна парафразована ідея, яку варто приклеїти до кожного on-call ноутбука, приписувана Werner Vogels (надійність/архітектура): Усе рано чи пізно ламається; проектуйте так, щоб режими відмов були ізольовані й передбачувані.
Жарт №1: Інвалідація кешу складна. Інша складна проблема — пояснити фінансам, чому «більше CPU» не виправило «занадто багато запитів».
Шар 1: CDN-кеш, що не ламає сайт
Якщо у вас є CDN, а origin все ще бачить більшість анонімного трафіку, ваш CDN — це по суті дуже дорогий TLS-термінатор.
Перше завдання — навчити CDN впевнено кешувати те, що безпечно.
Що CDN повинен кешувати
- Статичні ресурси: зображення, CSS, JS, шрифти. Довгі TTL, незмінні імена файлів за можливості.
- Публічні HTML-сторінки для анонімних користувачів: головна, пости, категорії, теги, маркетингові сторінки.
- Деякі API-відповіді, якщо ви контролюєте їх і вони публічні (рідко для WordPress, хіба що фронт для read-only endpoint).
Що CDN не повинен кешувати (або має варіювати обережно)
- Усе, що змінюється для кожного користувача: сторінки для залогінених, адмін, акаунт, checkout, кошик.
- Відповіді, що встановлюють cookies або залежать від cookies, якщо ваш ключ кешу не дисциплінований.
- Прев’ю сторінки, чернетки, записи з паролем.
Практична порада: ставтесь до Set-Cookie як до символу токсичних відходів для публічного HTML. Якщо origin ставить cookies на анонімні перегляди через плагін «бо так треба», ваш CDN-хітрейт загине тихо.
Дисципліна ключа кешу: від неї залежить хітрейт
За замовчуванням багато CDN можуть варіювати кеш за довгим списком: query string, заголовки та іноді cookies. Це — знаряддя вбивства хітрейту.
Для WordPress зазвичай ви хочете:
- Варіювати за шляхом URL та контрольованим набором параметрів запиту (часто жодного для HTML).
- Ігнорувати більшість cookies для анонімного трафіку, але обходити кеш, якщо є специфічні WordPress cookies.
- Підтримувати
Cache-Controlвід origin лише якщо ви довіряєте origin. Більшість з них спочатку неправильні.
Якщо ваш CDN підтримує «serve stale on error» та «serve stale while revalidating», увімкніть це для публічного контенту. Це різниця між
«origin хворіє, але користувачі в порядку» і «origin хворіє і всі про це знають».
Правила ключа кешу: cookies, заголовки і чому ваш показник хітрейту бреше
WordPress встановлює і читає кілька cookie, що мають значення:
wordpress_logged_in_*: користувач залогінений. Обходьте full-page cache і CDN HTML cache.wp-postpass_*: вміст, захищений паролем. Не кешувати публічно.woocommerce_cart_hash,woocommerce_items_in_cart: транзакційний стан. Розглядайте як сигнали обходу.comment_author_*: може впливати на відображення сторінки для форм коментарів. Зазвичай краще обходити або обережно варіювати.
Тепер більш тонка проблема: сторонні cookies. Багато плагінів ставлять cookies для «групи A/B-тесту», «джерела реферала», «згоди», «чат-сесії» тощо. Якщо ваш кеш шар варіює по cookie, або origin віддає різний HTML залежно від цих cookie, ви фрагментуєте кеш на дрібні шматки.
Правило ухвалення рішення: якщо cookie не змінює суттєво HTML для анонімних користувачів, воно не повинно бути частиною ключа кешу. Якщо воно змінює HTML, вам потрібно вирішити, чи персоналізація варта витрат на продуктивність. У більшості корпоративних середовищ відповідь — «ні, не для критичних лендингів».
Шар 2: origin full-page cache (Nginx FastCGI) правильно налаштований
Origin full-page caching — це робоча конячка. Коли CDN промахується (cold edge, purge, гео-специфічна поведінка), ваш origin все ще має віддавати кешований HTML
без запуску WordPress. Nginx FastCGI cache робить це надійно, якщо ви налаштуєте його дорослими правилами.
Чому FastCGI cache кращий за «плагіни, що зберігають HTML» у виробництві
- Конкурентність: Nginx може віддавати кешовані відповіді з мінімальними накладними витратами, навіть під високим навантаженням.
- Ізоляція: кеш сидить перед PHP, отже виснаження PHP-воркерів не руйнує публічні сторінки одразу.
- Контроль: ви можете визначити ключі кешу, правила обходу та TTL в одному місці, і це можна моніторити.
Базовий паттерн Nginx FastCGI cache (концептуально)
Ви хочете ключ кешу, що ігнорує сміття, обходити кеш для залогінених/кошика/адмін cookie, і захист від stampede.
Використовуйте fastcgi_cache_lock, щоб один запит прогрівав кеш, а інші коротко чекали, замість того щоб одночасно навантажувати PHP.
Практичні рекомендації по TTL:
- Публічні записи/сторінки: 5–30 хвилин на origin — звичне рішення, якщо у вас є purge hooks; довше, якщо немає.
- Головна сторінка: часто коротше, якщо вона часто змінюється, але все одно кешується.
- Сторінки пошуку: складно; часто кешувати коротко (30–120 секунд) або пропускати, якщо персоналізовані.
Мікрокешування (1–5 секунд) — легітимний інструмент для ендпоінтів, які не можна повністю кешувати, але які піддаються сильному навантаженню (деякі AJAX ендпоінти). Не починайте з нього.
Використовуйте, коли є докази.
Шар 3: об’єктний кеш (Redis) без перетворення в смітник
Об’єктне кешування допомагає головним чином тоді, коли ваші сторінки не повністю кешовані: панелі залогінених, потоки WooCommerce, і будь-який сайт з великою кількістю динамічних блоків.
Воно зменшує повторні запити до бази даних для опцій, транзієнтів та повторних lookup’ів у межах одного запиту і між запитами.
Але Redis як об’єктний кеш має режим відмови: він може стати спільною шухлядкою з необмеженими ключами, непередбачуваними TTL та «допоміжними» плагінами, що зберігають
цілі відрендерені фрагменти. Коли Redis починає виганяти гарячі ключі або свопити, він буде не просто повільним — він буде креативно повільним.
Правила, що роблять Redis корисним
- Обмежте пам’ять: встановіть
maxmemoryта розумну політику витіснення (частоallkeys-lfuдля змішаних навантажень). - Тримайте Redis локально, коли можна: мережеве пізняння накопичується. Якщо він повинен бути віддаленим, розташуйте ближче і моніторте p99.
- Використовуйте явний префікс: уникайте колізій ключів і зробіть можливим очищення за префіксом при потребі.
- Слідкуйте за фрагментацією та вигнаннями: evictions — не «нормальне» явище. Це симптом «ми гадаємо, скільки потрібно пам’яті».
Коли об’єктний кеш не допомагає
- Якщо ви вже віддаєте більшість трафіку через CDN + full-page cache, об’єктний кеш не змінить основний шлях.
- Якщо вузьке місце — CPU PHP через важку шаблонну логіку, Redis не дуже допоможе.
- Якщо база даних повільна через відсутність індексів, кешування може приховати проблему, поки не перестане допомагати.
Шар 4: PHP-FPM та OPcache (бо промахи кешу реальні)
Ви можете побудувати чудовий кеш і все одно розплавитись, бо не кешований шлях нестабільний. Залогінені користувачі, адмін-сторінки та прогрівання після purge влучать у PHP.
Якщо PHP-FPM налаштований як хобі-проєкт, він буде поводитися відповідно.
PHP-FPM: налаштовуйте з урахуванням пам’яті перш за все, потім конкуренції
Класична помилка — встановлювати pm.max_children на основі кількості CPU. WordPress багато їсть пам’яті. Ви встановлюєте max children на основі:
(доступна RAM – запас безпеки) / середній RSS процесу PHP.
Також потрібно спостерігати:
max children reachedподії: означає, що запити ставлять у чергу.- записи в slow log: означає, що певні скрипти виконуються занадто довго.
- розподіл тривалості запитів: p95 і p99 важливіші за середнє.
OPcache: найдешевше прискорення, яке можна купити
З OPcache, увімкненим і правильно розміреним, PHP уникає перекомпіляції скриптів і тримає гарячий код у пам’яті.
Під навантаженням неправильний розмір OPcache проявляється як високий CPU, часті перезапуски і «чому після деплою стало повільніше?»
Шар 5: тюнінг MySQL для робочих навантажень WordPress
WordPress не екзотичний. Це класичний веб-додаток: багато читань, кілька записів, і декілька запитів, що стають монструозними при рості даних.
Більшість базових проблем WordPress із БД походять від:
- Відсутніх індексів для таблиць, створених плагінами, або для meta-запитів.
- Надмірного autoload опцій у
wp_options. - Повільних адмін-запитів, яких ніхто не тестував на обсягах продакшн-даних.
- Штормів з’єднань, коли кеші спливають і PHP-воркери різко зростають.
База даних не повинна бути вашим кешем сторінки. Вона має бути довговічним джерелом істини. Якщо для головної потрібно 200 запитів, кешування приховує це; воно не вирішує.
Але з правильними шарами кешу навантаження на БД стає передбачуваним і ви можете налаштувати індекси під реальні винятки.
Інвалідація кешу та purge: передбачуваність краща за хитрість
«Purge everything on publish» — це WordPress-еквівалент натискання на пожежну кнопку, щоб її протестувати. Так, тривога спрацювала. Тепер усі на вулиці й сердиті.
Інвалідація має бути:
- Вузькою: очищати URL, що змінився, і невеликий набір сторінок, що посилаються на нього (сторінка категорії, головна, фіди).
- Обмеженою за частотою: особливо для масових оновлень, імпортів і редакційних процесів.
- Наблюваною: ви повинні бачити обсяг purge і корелювати його зі спадом хітрейту кешу та навантаженням на origin.
Захист від натовпу важливий. Якщо ви очищаєте популярний URL і 5 000 користувачів запитують його одночасно, ви хочете, щоб один запит відбудував кеш, а решта чекали або отримували трохи застарілий контент.
Жарт №2: Найпростіший спосіб підвищити хітрейт кешу — публікувати менше. Редактори не прийняли цю революційну ідею.
Швидкий план діагностики: знайти вузьке місце за хвилини
Коли сайт повільний, часу на філософію немає. Потрібна швидка класифікація: чи ми пропускаємо кеші, чи PHP насичений, чи блокування у базі даних? Ось порядок, що швидко дає відповіді.
По-перше: підтвердьте, де витрачається час (edge vs origin vs upstream)
- Перевірте заголовки статусу CDN/edge cache (HIT/MISS/BYPASS). Якщо в більшості випадків MISS, виправте правила кешування до того, як торкатиметесь PHP.
- Перевірте заголовки відповіді origin для статусу FastCGI cache (HIT/MISS/BYPASS/EXPIRED).
- Виміряйте TTFB ззовні і порівняйте з таймінгами origin. Якщо edge швидкий, але origin повільний, у вас проблема origin, але користувачі можуть бути в порядку.
По-друге: перевірте сигнали насичення
- PHP-FPM: max children reached, listen queue, записи в slowlog.
- MySQL: running threads, повільні запити, lock waits.
- CPU і пам’ять: свопінг, steal time (VM), iowait (зберігання).
По-третє: ідентифікуйте клас запитів, що спричиняє біль
- Чи це /wp-admin/admin-ajax.php?
- Чи це пошук?
- Чи це сканер, що ігнорує robots?
- Чи це шторм purge?
Найшвидший шлях до стабільності часто не «оптимізувати WordPress». Це «зупинити відправлення динамічного трафіку до WordPress».
Це означає кешування, rate limiting і дисципліну обходу.
Практичні завдання (команди + виводи + рішення)
Це завдання, які я реально виконую під час інцидентів і тюнінгу продуктивності. Кожне включає: команду, що означає вивід, і рішення, яке слід ухвалити.
Завдання 1: Виміряти TTFB і підтвердити заголовки кешу на edge
cr0x@server:~$ curl -s -D- -o /dev/null https://example.com/ | egrep -i '^(HTTP/|age:|cache-control:|cf-cache-status:|x-cache:|via:|server-timing:)'
HTTP/2 200
cache-control: public, max-age=300
age: 142
cf-cache-status: HIT
via: 1.1 varnish
Значення: cf-cache-status: HIT і ненульове age вказують, що CDN віддає кешований HTML. Добре.
Рішення: Якщо ви бачите MISS для ваших топ-сторінок під звичайним трафіком, виправте правила кешу CDN і логіку обходу cookie перед тим, як торкатись налаштувань origin.
Завдання 2: Порівняти edge і origin напряму (обійти CDN)
cr0x@server:~$ curl -s -D- -o /dev/null --resolve example.com:443:203.0.113.10 https://example.com/ | egrep -i '^(HTTP/|x-fastcgi-cache:|x-cache:|server:|cache-control:)'
HTTP/2 200
server: nginx
cache-control: public, max-age=300
x-fastcgi-cache: HIT
Значення: Ви потрапили на IP origin; x-fastcgi-cache: HIT означає, що Nginx віддає кешований HTML без PHP.
Рішення: Якщо origin — MISS, а CDN також MISS, очікуйте колапсу: ви запускаєте WordPress для анонімного трафіку. Негайно виправляйте origin full-page cache.
Завдання 3: Перевірити коефіцієнт хітів Nginx з логів (швидко і грубо)
cr0x@server:~$ sudo awk '{print $NF}' /var/log/nginx/access.log | sort | uniq -c
81234 HIT
10421 MISS
3211 BYPASS
442 EXPIRED
Значення: Це припускає, що формат логів закінчується статусом кешу. Високий HIT — добре; багато BYPASS вказує на cookie-правила або трафік авторизації.
Рішення: Якщо BYPASS високий для сторінок, що мають бути публічними, знайдіть, які cookies спричиняють bypass, і припиніть їх встановлювати для анонімних користувачів.
Завдання 4: Ідентифікувати топ-URL, що навантажують origin
cr0x@server:~$ sudo awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head
15432 /wp-admin/admin-ajax.php
9821 /
6110 /wp-json/wp/v2/posts
5880 /product/widget-1/
4122 /?s=widget
Значення: Це показує найзавантаженіші ендпоінти. Admin AJAX і пошук — звичні підозрювані.
Рішення: Якщо /wp-admin/admin-ajax.php домінує, розслідуйте, що його викликає (функції на фронтенді, heartbeat, плагіни), потім rate-limit або кешуйте/мікрокешуйте там, де безпечно.
Завдання 5: Перевірити насичення PHP-FPM (status page)
cr0x@server:~$ curl -s http://127.0.0.1/php-fpm-status | egrep -i 'pool|process manager|active processes|idle processes|max active processes|listen queue'
pool: www
process manager: dynamic
active processes: 48
idle processes: 2
max active processes: 50
listen queue: 37
Значення: Ви на стелі; черга зростає. Запити чекають воркерів.
Рішення: Якщо пам’ять дозволяє, збільшіть pm.max_children. Якщо пам’яті недостатньо — зменшіть динамічне навантаження через кешування/правила обходу та виправте дорогі ендпоінти.
Завдання 6: Підтвердити події «max children reached»
cr0x@server:~$ sudo journalctl -u php8.2-fpm --since "30 min ago" | egrep -i 'max_children|max children'
Feb 04 10:12:09 web1 php-fpm8.2[1203]: [WARNING] [pool www] server reached pm.max_children setting (50), consider raising it
Значення: Тверді докази насичення.
Рішення: Розглядайте це як сигнал до планування ємності. Або збільшіть воркерів (якщо RAM дозволяє), або зменшіть некешований трафік до PHP.
Завдання 7: Оцінити пам’ять PHP-воркера, щоб безпечно встановити max_children
cr0x@server:~$ ps -ylC php-fpm8.2 --sort:rss | awk 'NR==1{print} NR>1{rss+=$8; n++} END{printf "avg_rss_kb=%d\n", rss/n}'
S UID PID PPID C PRI NI RSS SZ WCHAN TTY TIME CMD
avg_rss_kb=142000
Значення: Середня resident пам’ять на воркер ≈142MB. Це типово для WordPress з плагінами.
Рішення: Якщо у вас, скажімо, 8GB вільного для PHP, ви не можете ставити 200 воркерів. Ви почнете свопити, і продуктивність повільно та публічно впаде.
Завдання 8: Перевірити статус OPcache і тиск пам’яті
cr0x@server:~$ php -i | egrep -i 'opcache.enable|opcache.memory_consumption|opcache.interned_strings_buffer|opcache.max_accelerated_files'
opcache.enable => On => On
opcache.memory_consumption => 256
opcache.interned_strings_buffer => 16
opcache.max_accelerated_files => 20000
Значення: OPcache увімкнено й розмірений. Пам’ять все ще може бути недостатньою залежно від розміру кодової бази.
Рішення: Якщо деплої викликають CPU-спайки і скидання кешу, збільште пам’ять OPcache і перевірте, що ви не часто перезапускаєте PHP-FPM.
Завдання 9: Перевірити здоров’я Redis для об’єктного кешу
cr0x@server:~$ redis-cli INFO memory | egrep -i 'used_memory_human|maxmemory_human|mem_fragmentation_ratio'
used_memory_human:1.42G
maxmemory_human:2.00G
mem_fragmentation_ratio:1.57
Значення: Redis використовує 1.42G з 2G, фрагментація помірна/висока. Фрагментація може зростати при сильному churn.
Рішення: Якщо фрагментація зростає і латентність піднімається, розгляньте тюнінг політики витіснення, зменшення churn ключів (погані плагіни) або збільшення запасу пам’яті.
Завдання 10: Перевірити evictions у Redis (тиха вбивця продуктивності)
cr0x@server:~$ redis-cli INFO stats | egrep -i 'evicted_keys|expired_keys|keyspace_hits|keyspace_misses'
keyspace_hits:98234123
keyspace_misses:7712231
expired_keys:412332
evicted_keys:118443
Значення: Evictions означають, що Redis під тиском пам’яті і викидає ключі, які ви хотіли кешувати.
Рішення: Якщо evictions продовжуються під час піку, збільшіть пам’ять Redis, змініть те, що ви зберігаєте, або встановіть більш розумні TTL. Не ігноруйте evictions.
Завдання 11: Швидко перевірити потоки MySQL та contention
cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Threads_running'; SHOW GLOBAL STATUS LIKE 'Threads_connected';"
+-----------------+-------+
| Variable_name | Value |
+-----------------+-------+
| Threads_running | 64 |
+-----------------+-------+
+-------------------+-------+
| Variable_name | Value |
+-------------------+-------+
| Threads_connected | 210 |
+-------------------+-------+
Значення: Багато running threads вказує, що БД зайнята; connected threads вказує на тиск з’єднань.
Рішення: Якщо running threads стрибнуть при спливанні кешу, у вас проблема stampede. Додайте cache locks, serve stale, і зменшіть bypass трафік.
Завдання 12: Знайти повільні запити (топ винуватців)
cr0x@server:~$ sudo mysqldumpslow -s t -t 10 /var/log/mysql/mysql-slow.log
Count: 18 Time=2.31s (41s) Lock=0.00s (0s) Rows=1.0 (18), root[root]@localhost
SELECT option_value FROM wp_options WHERE option_name = 'autoload_big_blob' LIMIT 1;
Count: 9 Time=1.97s (17s) Lock=0.12s (1s) Rows=1200.0 (10800), app[app]@10.0.0.12
SELECT * FROM wp_postmeta WHERE meta_key = '...' AND meta_value LIKE '...%';
Значення: Lookup’и опцій і неіндексовані meta-запити вбивають вас. Другий запит кричить «плагін робить пошук по meta».
Рішення: Виправте autoload bloat, додайте цільові індекси там, де безпечно, і подумайте про редизайн запитів (або відключення фічі плагіна). Кешування саме по собі не зробить unindexed LIKE швидким.
Завдання 13: Перевірити розмір autoloaded options (типова міна WordPress)
cr0x@server:~$ mysql -e "SELECT ROUND(SUM(LENGTH(option_value))/1024/1024,2) AS autoload_mb FROM wp_options WHERE autoload='yes';"
+-------------+
| autoload_mb |
+-------------+
| 18.47 |
+-------------+
Значення: 18MB autoloaded опцій означає, що кожен запит підвантажує купу речей у пам’ять. Це тихо зростає з часом.
Рішення: Проведіть аудит, які опції автозавантажуються, відключіть autoload для великих blob’ів і виправте плагін/тему, що це створює.
Завдання 14: Підтвердити cache-control і vary заголовки від origin
cr0x@server:~$ curl -s -D- -o /dev/null https://example.com/ | egrep -i 'cache-control:|pragma:|expires:|vary:|set-cookie:'
cache-control: public, max-age=300
vary: Accept-Encoding
Значення: Немає Set-Cookie на публічній головній, а Vary не вибухає на неважливих заголовках.
Рішення: Якщо ви бачите Set-Cookie на публічних сторінках, знайдіть плагін, що це робить, і припиніть. Інакше ви платите за кеш і не отримуєте кеш.
Завдання 15: Швидко виявити сплески ботів і погані актори
cr0x@server:~$ sudo awk -F\" '{print $6}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head
22110 Mozilla/5.0 (compatible; SomeBot/1.0; +http://bot.example)
11842 Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36 Chrome/121.0 Mobile
7441 Mozilla/5.0 (compatible; AnotherCrawler/2.1)
Значення: Один бот домінує у трафіку. Це може бути нормально, якщо кешовано; катастрофа, якщо обходить кеш.
Рішення: Якщо боти долблять динамічні ендпоінти, додайте rate limits, загостріть robots правила, і забезпечте кешування публічних сторінок, щоб боти були дешевими.
Завдання 16: Перевірити розподіл upstream response time у Nginx
cr0x@server:~$ sudo awk '{print $(NF-1)}' /var/log/nginx/access.log | sed 's/upstream_response_time=//' | awk -F, '{print $1}' | sort -n | tail -n 5
0.842
1.003
1.217
2.884
5.991
Значення: Найповільніші upstream-відповіді — це кілька секунд. Це ймовірно cache misses, що потрапляють у PHP/MySQL.
Рішення: Розслідуйте повільні ендпоінти і узгодьте політику кешу: або кешуйте їх, або зробіть дешевшими (індекси, виправлення коду), або захистіть (rate limits).
Поширені помилки: симптом → корінь проблеми → виправлення
1) Низький CDN hit rate, хоча сторінки «кешовані»
Симптоми: CDN показує багато MISS/BYPASS; навантаження на origin високе під час сплесків.
Корінь проблеми: Ключ кешу варіюється за cookies або query string, які не повинні варіюватися; origin відправляє Set-Cookie на анонімний HTML.
Виправлення: Відфільтруйте неважливі cookies з ключа кешу; обходьте лише на WordPress auth/cart cookies; припиніть ставити маркетингові cookies на анонімні сторінки; нормалізуйте query string.
2) «Працює в staging», але продакшн плавиться при спливанні кешу
Симптоми: Кожні кілька хвилин латентність стрибає; PHP і БД піднімаються; потім «відновлюється».
Корінь проблеми: Cache stampede: багато клієнтів одночасно промахуються; немає блокування кешу; короткий TTL на гарячих сторінках; purge-шторм.
Виправлення: Увімкніть cache lock на origin, serve stale while revalidating на edge, розкидайте TTL (jitter), і припиніть чистку «всього».
3) WooCommerce сторінки випадково показують неправильний контент
Симптоми: Витік вмісту корзини, «Привіт Боб» з’являється в акаунті Аліси, ціни різняться несподівано.
Корінь проблеми: Full-page кеш застосовано до персоналізованих/транзакційних сторінок; ключ кешу ігнорує стан сесії.
Виправлення: Обходьте кеш на cart/checkout/my-account, обходьте по WooCommerce cookies, і ніколи не кешуйте відповіді, що встановлюють сесійнi cookies.
4) Адмін повільний, а публічний сайт швидкий
Симптоми: Редактори скаржаться; wp-admin таймаути; публічні сторінки в порядку.
Корінь проблеми: Адмін обходить full-page cache і потрапляє на важкі запити (пошук по postmeta, autoload bloat) і повільне виконання PHP.
Виправлення: Додайте Redis об’єктний кеш, зменшіть autoloaded опції, виправте повільні запити, налаштуйте PHP-FPM для конкуренції залогінених, і подумайте про відділення адмін-трафіку при потребі.
5) Після ввімкнення Redis продуктивність погіршилась
Симптоми: Вища латентність, таймаути, CPU Redis високий, evictions.
Корінь проблеми: Redis недопропорційований; churn ключів та evictions; віддалений Redis з латентністю; використання Redis для гігантських transient blob’ів.
Виправлення: Збільшіть пам’ять, виберіть розумну політику витіснення, тримайте Redis поруч з додатком, проведіть аудит плагінів, що зберігають великі значення, і моніторте evictions та латентність.
6) «Просто додати більше PHP воркерів» спричиняє своп і колапс
Симптоми: Load average росте; iowait стрибає; усе сповільнюється; kernel логи показують тиск пам’яті.
Корінь проблеми: Кількість воркерів встановлена понад можливості RAM; RSS на воркер недооцінений; OPcache або PHP memory leaks; немає запасу пам’яті.
Виправлення: Виміряйте RSS, встановіть max_children на основі пам’яті, залиште запас, і зменшіть динамічний трафік через кешування. Неправильне масштабування гірше за відсутність масштабування.
7) Purge викликає короткі простої
Симптоми: Відразу після деплоя/публікації origin стрімко навантажується; CDN переходить у MISS; латентність для користувачів стрибає.
Корінь проблеми: Очистка широких шаблонів (все); немає прогріву кешу; немає cache lock; TTL занадто короткий.
Виправлення: Очищайте лише змінені URL, обмежуйте частоту purge, використовуйте cache lock, опційно прогрівайте критичні сторінки, і дозволяйте edge serve stale.
Три корпоративні міні-історії з передової
Міні-історія 1: Інцидент через неправильне припущення
Середня компанія використовувала WordPress як маркетингові двері для продукту, що швидко зростав. У них був CDN, керована база даних і платформа на контейнерах. Все виглядало сучасно. Продуктивність була «ок»… поки не стала.
Неправильне припущення: «Якщо ми поставимо Cache-Control: public, CDN кешуватиме HTML і ми все зробили». Насправді плагін згоди поставив cookie на першому перегляді для кожного користувача, а конфіг CDN за замовчуванням варіював кеш по всіх cookies. Отже у кожного користувача виявився власний приватний запис кешу. Хітрейт впав, і ніхто цього не помітив, бо у дашборду все ще виглядало пристойно.
Потім вони запустили кампанію. Головна сторінка раптом стала PHP-ендпоінтом знову. PHP-FPM вичерпався, з’єднання MySQL стрибнули, і health checks почали флапати. CDN їх не захистив, бо він не кешував те, що вони думали.
Виправлення було нудне й ефективне: обходити кеш лише на специфічних WordPress cookies, ігнорувати cookie згоди в ключі кешу, і припинити виставлення маркетингових cookie на HTML-відповіді, коли це не треба. Також додали origin FastCGI cache як підстраховку.
Урок: у роботі з продуктивністю ворог не в складності. Він у неявній поведінці. Якщо ви не можете пояснити свій ключ кешу одним реченням, у вас не кеш — у вас чутка.
Міні-історія 2: Оптимізація, що відбилася боком
Інша команда хотіла швидшої «свіжості» для редакторів. Вони скоротили TTL на все до 30 секунд і додали агресивне purge-on-publish для втіхи. Публічний сайт здавався швидшим для оновлень. Редактори були щасливі. Приблизно тиждень.
Відбилося під час звичайного трафіку. Кожні 30 секунд популярні сторінки одночасно спливали. CDN і origin кеш синхронно промахувалися. Раптом тисячі запитів на хвилину перевідтворювали однакові сторінки, запускаючи PHP і вдаряючи MySQL. Це був підручниковий stampede, але самоподібний.
Вони спробували «вирішити» це, додавши більше PHP-FPM воркерів. Сервери опинилися під тиском пам’яті і почали свопити. Латентність погіршилась і стала непередбачуваною. Монітори виглядали як сучасне мистецтво: різнобарвні спайки, непридатні для ухвалення рішень.
Справжнє виправлення: довші origin TTL, cache locking, edge stale-while-revalidate, і цілеспрямовані purge тільки для змінених URL. Додали jitter до TTL для кількох гарячих маршрутів, щоб експірації не вирівнювались. Свіжість залишилась прийнятною, бо purge були правильними, а не тому, що TTL були крихітні.
Урок: зменшення TTL — це не «більше реального часу». Це «більше навантаження». Якщо вам потрібна свіжість, побудуйте інвалідацію, якій можна довіряти.
Міні-історія 3: Нудна практика, що врятувала день
Велике підприємство мало кілька WordPress-проперті за спільним edge. Команда була не показна. У них були runbook’и, протестовані відновлення і звичка вимірювати перед змінами. Їх «інновація» була у записуванні, що означає кожен cookie.
Під час великого анонсу продукту трафік виріс, а потім виріс знову, коли агрегатор новин підхопив його. Те, що могло бути пожежною тривогою, стало трохи напруженою годиною спостереження за дашбордами.
Причина: у них була строгa політика кешування для анонімного трафіку, з явними правилами обходу для auth/cart cookies, і вони логували статус кешу на кожен запит. Коли edge почав бачити більше MISS через регіональні затримки, origin FastCGI cache вже був прогрітий і захищений lock-ом.
Використання PHP зросло трохи, але ніколи не стрибнуло в чергу.
Один бот почав долбити пошук, який не кешувався. Їхні правила rate limit зловили його, повертаючи 429 без шкоди реальним користувачам.
Редактори продовжували публікувати без очищення всього сайту, бо їхня інтеграція purge була вузькою і обмеженою за частотою.
Урок: нудні практики — документовані ключі кешу, логування статусу кешу і rate limiting — це не бюрократія. Це спосіб уникнути пояснень інциденту людям, які не хочуть вивчати, що таке cookie.
Контрольні списки / покроковий план
Фаза 1: Зробити анонімний трафік дешевим (1–2 дні)
- Визначте «анонімний» точно: ніяких
wordpress_logged_in_*, ніяких WooCommerce cart cookies, ніяких cookie захисту постів паролем. - Увімкніть CDN кешування для публічного HTML з контрольованим ключем кешу; обходити лише на відомих auth/cart cookies.
- Переконайтесь, що origin не ставить cookies на публічних сторінках. Виправте плагін або налаштуйте його, щоб уникати анонімних cookies.
- Розгорніть origin full-page cache (Nginx FastCGI) з увімкненим cache lock і чіткими правилами обходу.
- Логуйте статус кешу на CDN (якщо можливо) і на origin. Якщо ви не можете виміряти хітрейт, ви гадаєте.
Фаза 2: Стабілізувати динамічний шлях (2–5 днів)
- Увімкніть OPcache і підтвердіть, що він правильно розмірений.
- Налаштуйте PHP-FPM max children на основі виміряного RSS і доступної RAM.
- Розгорніть Redis об’єктний кеш якщо трафік залогінених і адмін значущий. Встановіть maxmemory і моніторте evictions.
- Увімкніть slow logs для PHP-FPM і MySQL. Визначте топ-виновників перед «оптимізацією».
- Виправте autoload bloat у
wp_options. Це часто дає непропорційні виграші.
Фаза 3: Зробити інвалідацію безпечною (постійно)
- Реалізуйте вузькі purges при публікації/оновленні: змінена сторінка + головна + релевантні архіви, а не «purge all».
- Обмежуйте частоту викликів purge і пакетно обробляйте їх для масових операцій.
- Увімкніть stale serving на edge для коротких вікон під час ревалідації або помилок origin.
- Прогрівайте вибірково (головна і топ лендинги) після деплоїв, а не весь sitemap.
- Постійно валідуйте правила обходу коли маркетинг додає скрипти або плагіни додають cookie.
FAQ
1) Чи справді мені потрібні і CDN кеш, і origin full-page cache?
Так, якщо вам важливо виживати під реальним трафіком. CDN зменшує глобальну латентність і поглинає сплески, але промахи трапляються (purges, cold edges,
географія, заголовки). Origin cache — ваш підстраховувач, щоб промахи не перетворювались автоматично на роботу PHP.
2) Чи вистачить плагіна кешування WordPress?
Плагіни допомагають з purge hooks і деякою інтеграцією, але найнадійніше кешування відбувається на HTTP-рівні (CDN + Nginx). Плагін-файловий кеш може бути
достатній для маленьких сайтів, але це не та архітектура, на яку ви хочете ставити інцидент-респонс.
3) Як зрозуміти, чи cookie вбиває мій хітрейт кешу?
Інспектуйте заголовки відповіді на наявність Set-Cookie на публічних сторінках і перегляньте правила ключа кешу CDN. Корелюйте статус кешу (HIT/MISS) з
наявністю певних cookie в запитах. Якщо «анонімні» запити все ще несуть десятки cookie, очікуйте фрагментації.
4) Чи варто кешувати результати пошуку?
Інколи. Якщо пошук публічний і не персоналізований, короткотривале кешування (або мікрокеш) може дуже допомогти. Якщо пошук залежить від стану користувача або містить
ціни для конкретного користувача, будьте обережні. Альтернатива — rate limit зловживання та покращення реалізації пошуку.
5) Що щодо WooCommerce?
Кешуйте сторінки товарів для анонімних користувачів. Обходьте кеш для cart, checkout та account сторінок, і для запитів з cart/session cookies. Також слідкуйте за
AJAX ендпоінтами і фрагментами; вони можуть створювати несподіване навантаження.
6) Redis чи Memcached для об’єктного кешу?
Обидва можуть працювати. Redis часто перемагає по операційній гнучкості і інструментах у багатьох організаціях. Суть не в бренді; суть — у розмірі пам’яті, політиці витіснення, латентності та дисципліні плагінів.
7) Якими мають бути мої TTL?
Достатньо довгими, щоб бути корисними, і достатньо короткими, щоб бути правильними. Якщо у вас надійні purges, TTL може бути довшим. Якщо purge ненадійні, TTL стає страховою сіткою.
Для багатьох сайтів 5–30 хвилин на origin для публічних сторінок — розумна відправна точка, а edge часто кешує довше.
8) Чи варто ввімкнути «cache everything» на CDN?
Не бездумно. Можна агресивно кешувати публічний HTML, але необхідно реалізувати явні правила обходу для залогінених і транзакційних cookies. «Cache everything» без дисципліни cookies — це шлях до інцидентів безпеки, замаскованих під перемоги з продуктивності.
9) Мої публічні сторінки швидкі, але API повільний. Що тоді?
Визначте, які ендпоінти повільні і чи їх слід кешувати або обмежувати. Для wp-json вирішіть, що публічне і стабільне. Для адмін AJAX дослідіть виклики і зменшіть «балакучість». Швидка головна не допоможе, якщо решта додатка породжує DoS.
Наступні кроки, які можна відправити цього тижня
- Інструментувати статус кешу наскрізь: статус edge cache, статус origin FastCGI cache, час відповіді upstream.
- Виправити ключ кешу: обходити лише на WordPress auth/cart cookies; ігнорувати решту для анонімного HTML.
- Розгорнути origin FastCGI cache з lock: зупинити stampede, що б’є в PHP.
- Робіть purges вузькими: очищайте змінені URL та невеликий набір залежностей; обмежуйте масові операції.
- Налаштуйте PHP-FPM на основі пам’яті: виміряйте RSS; безпечно встановіть max_children; підтвердьте відсутність свопінгу.
- Аудит autoload опцій: зменшіть bloat; видаліть blob’и плагінів з autoload.
- Захистіть гарячі динамічні ендпоінти: rate limit зловживання; мікрокешувати лише за наявності доказів.
Результат, якого ви прагнете — не «швидкий сайт у лабораторії». Це сайт, що залишається швидким, коли приходять реальні люди з реальними браузерами, реальними ботами
і реальним хаосом. Багатошарове кешування, дисципліновані правила обходу і нудна інвалідація приведуть вас туди.