Як ми прискорили PHP-проекти в 40 разів за допомогою кешування



Питання SEO-оптимізації і поліпшення User eXperience, які в певний момент постали перед командою Wrike, вимагали значного збільшення швидкості роботи наших веб-проектів. На той момент їх було близько десяти (основний сайт, блог, довідковий центр тощо). Рішення щодо прискорення проектів було виконано на основі зв'язки Nginx + fastcgi cache + LUA + LSYNC.



Дано

На більшості проектів для зручності, універсальності і розширення можливостей плагінами ми використовували зв'язку Wordpress + themosis, на деяких — просто Wordpress. Природно, на Wordpress ще було «навішені» безліч плагінів + наша тема: на серверні ноди з веб-проектами — Nginx + php-fpm, а перед — Entry Point (Nginx + proxy_pass на них).

Кожне додаток перебувало на своєму сервері апстрима, на якому був proxy_pass по round-robin. Самі розумієте, чекати від такої зв'язки хороших показників не доводилося.
На той момент TTFB (Time To First Byte) і Upstream Response Time в більшості випадків становили від 1 до 3 секунд. Такі показники нас не влаштовували.

Перш ніж зайнятися пошуком рішення, ми визначили, що нас влаштує 50 мс для upstream response time. Upstream response time обрали як найбільш показову величину, яка показувала виключно час відповіді сервера з веб-додатком і не залежала від інтернет-з'єднання.

Крок 1: fastcgi

За результатами ресерча зупинилися на fastcgi-кеші. Штука виявилася дійсно хороша, що настроюється і чудово справляється зі своїм завданням.

Після її включення і налаштування на ноди показники покращилися, але незначно. Вагомих результатів домогтися не вдалося із-за того, що Entry Point розкидав запити по round-robin алгоритмом всередині апстрима, і, відповідно, кеш на кожному з серверів для одного і того ж додатка був свій, нехай і однаковий. Наша архітектура не дозволяла нам складати кеш на наш Entry Point, тому довелося думати далі.

Крок 2: lsyncd

Рішення було обрано наступне: використовувати lsyncd для дистрибуції кешу між нодами апстрима події inotify. Сказано — зроблено: кеш відразу ж під час створення на одній ноде за inotify починав «летіти» на інші ноди, але успіху це, звичайно, не призвело. Сторінки в кеші знав тільки Nginx тієї ноди, запит в якій був оброблений.

Ми трохи подумали і знайшли спосіб, яким можна і інші ноди навчити працювати з кешем, отриманими через lsyncd. Спосіб виявився не витонченим — це рестарт Nginx, після якого він запускає cache loader (за хвилину), а той в свою чергу починає завантажувати в зону кешу інформацію про закэшированных даних — тим самим він дізнається про кеші, який був синхронізований з інших нсд. На цьому етапі також було прийнято рішення про те, що кеш повинен жити дуже довго і генеруватися в більшості випадків через спеціального бота, який би ходив по потрібних сторінок, а не через відвідувачів сайту та пошукових роботів. Відповідно були подтюнены опції fastcgi_cache_path і fastcgi_cache_valid.

Все добре, але як ревалидировать кеш, який, наприклад, необхідне після кожного деплоя. Питання ревалідацію вирішили за допомогою спеціального заголовка в опції типу fastcgi_cache_bypass:

fastcgi_cache_bypass $skip $http_x_specialheader;
 


Тепер залишалося зробити так, щоб наш бот після деплоя починав ревалидацию проекту, використовуючи такий заголовок:

--header='x-specialheader: 1'
 


В ході процесу ревалідацію кеш відразу ж «розлітався на всі ноди (lsyncd), а так як час життя кешу у нас велика і Nginx знає, що закешовані сторінки, то він починає віддавати відвідувачам вже новий кеш. Так, на всякий випадок додаємо опцію:

fastcgi_cache_use_stale error timeout updating invalid_header http_500;
 


Опція стане у нагоді, якщо, наприклад, php-fpm раптом випадково відвалився, або в продакшн приїхав код, який з якихось неймовірних причин повертає 500-ки. Тепер Nginx буде повертати не 500-ку, а поверне старий «робочий» кеш.

Також схема ревалідацію з допомогою заголовка дозволила нам зробити веб-інтерфейс для ревалідацію певних урлов. Він був зроблений на основі php-скриптів, які відправляли спеціальний заголовок на потрібний URL і ревалидировали його.
Тут ми відчули потрібний приріст швидкості віддачі сторінок. Справа пішла в потрібне русло :)

Крок 3: LUA

Але залишалося одне «але»: нам необхідно було керувати логікою кешування в залежності від тих чи інших умов: запити з певним параметром, кукой і т. д… Працювати з «if» в Nginx не хотілося, та й не вирішив би він всіх тих завдань з логікою, які перед нами стояли.

Почався новий ресерч, і в якості прошарку для управління логікою кешування був обраний LUA.

Мова виявився дуже простим, швидким і, головне, через модуль добре інтегрувався з Nginx. Процес складання відмінно задокументований тут.
Оцінивши можливості зв'язки Nginx + LUA, ми вирішили покласти на нього такі обов'язки:
редиректи з кількома умовами;
експерименти з розподілом запитів на різні лендінгем по одному URL (різний відсоток запитів на різні лендінгем);
блокування умовами;
прийняття рішення про необхідність кешування тієї чи іншої сторінки. Це робилося за наперед заданими умовами, конструкціями виду:

location ~ \.php {
 
set $skip 0;
 
set_by_lua $skip '
 
local skip = ngx.var.skip;
 
if string.find(ngx.var.request_uri, "test.php") then
 
app = "1";
 
end
 
return app;
 
';
 
...
 
fastcgi_cache_bypass $skip $http_x_specialheader;
 
fastcgi_no_cache $skip;
 
...
 
}
 

 

Виконана робота дозволила отримати такі результати:

  • Upstream response time для переважної більшості запитів перестало виходити за межі 50 мс, а в більшості випадків воно ще менше.
  • Також це відзначилося в консолі Google ~25% зниженням Time spent downloading a page (роботи).
  • Значно покращилися Apdex-показники по Request Time.
  • Бонусом стала опція fastcgi_cache_use_stale, яка послужить своєрідним захисником від 500-до в разі невдалого деплоя або проблем з php-fpm.
  • Можливість тримати набагато більше RPS за рахунок того, що звернення до php минимизировались, а кеш, грубо кажучи, є статичним html, який віддається прямо з диска.


На найбільш показовому прикладі upstream response time одного з додатків динаміка виглядала так:



Динаміка в консолі Google протягом впровадження рішення виглядає наступним чином:



Розбіжності в графіках закономірні, так як консоль показує динаміку всіх проектів домену. Саме кешування не принесло значних незручностей, так як інструменти для його ревалідацію виявилися дуже простими.

Таким чином ми досягли значного збільшення швидкості наших веб-проектів, практично не витративши ресурсів розробників на модернізацію додатків, і вони могли продовжувати займатися розробкою фіч. Даний спосіб хороший швидкістю і зручністю реалізації, однак його слабка сторона в тому, що системний кеш, хоч і вирішує проблему повільного рендеринга сторінок, але не усуває корінь проблем — повільну роботи самих скриптів.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.