[Из песочницы] 16 тонн. Как я спасал гибнущий под нагрузкой сайт на WordPress, имея весьма поверхностные знания в области этой CMS

Статья будет короткой и сумбурной — я пишу ее с целью скоротать пару часов перед тем как начать откатывать сайт к предыдущему «нормальному» состоянию.

Эта история началась пять часов назад. Ко мне обратился владелец одного тематического новостного сайта. Тематика — спортивные соревнования. У сайта есть две проблемы. Во-первых, в моменты крупных и сильно ожидаемых состязаний количество посетителей на сайте увеличивается на порядок. Вторая проблема — он сделан на WordPress, причем довольно небрежно. Думаю, что изначально это был обычный WP-сайт. Но потом он многократно «дорабатывался»: куда ни попадя втыкались разные рекламные блоки, вводились новые «решения», ставились всякие плагины для «оптимизации» и расширения возможностей. Кроме того, каждый день? на протяжении нескольких лет, появлялось около десятка постов. Размер БД — несколько гигабайт, «upload» идет на десятки гигабайт. Со временем сайт превратился во что-то похожее на это:

image

И вот, где-то в 0.00 по Москве сайт начинает падать под наплывом любителей спорта. Главная страница то отдает код 500, то загружается дольше минуты. Подозреваю, что периоды таких «набегов» посетителей — самое хлебное время для подобных ресурсов: клики, ставки и все такое. Владелец нервничает, что, в общем-то, понятно. И так получается, что из тех, кто не спит в эту субботнюю ночь и что-то понимает в веб-разработке у владельца есть только я.

И тут следующая проблема. Я, мягко говоря, не очень люблю WordPress. И, что самое плохое в этой ситуации, не очень в нем разбираюсь. Возможно, потому и не люблю, что не умею его правильно готовить. Но факт остается фактом — до этого момента я никогда не имел дела со сколько-нибудь крупными проектами, которые сделаны на его основе. Я, конечно, встречался с личными блогами и сайтами небольших контор на WP, где несколько сотен посетителей в сутки — счастье. Там он вполне справлялся, никаких проблем. Но и особых причин выяснять как он работает с ресурсами и как обеспечить его нормальную производительность там не было. Работает и работает.

Но в сложившейся ситуации нужно решить проблему его производительности. Причем очень быстро. Времени на чтение мануалов нет, особых знаний и умений тоже нет. Что делать? Тут и я начинаю нервничать — передается настроение владельца сайта.

Разумеется, первая мысль — пойти по пути наименьшего сопротивления. Т.е. просто перейти на старший, более «мощный» тариф. Сайт размещен на VDS. И тут выясняется, что этот способ не пройдет: переход на большие ресурсы — только по заявке в тех. поддержку хостера. Срок исполнения — от нескольких часов. Хостер объясняет это виртуализацией KVM. Ok, этот вариант не подходит. Одновременный звонок в поддержку и чтение сайта хостера занимают около пяти минут.

Следующий вариант, который проносится в моей голове: «WordPress славен своими плагинами. Я даже помню названия двух плагинов для кэширования: WP Super Cache и WP Fastest Cache». Как я узнаю позже, оба входят во всякие списки вроде «Топ-10 плагинов кэширования для WP» и т.п. Я подозреваю, что устанавливать плагины на постоянно падающем сайте — очень неприятное занятие. Поэтому сайт около пяти минут работает только для меня. Я быстро ставлю первый плагин, пробегаю глазами настройки — вроде все ok. Включаю сайт для всех посетителей. Результат… Ноль. Возможно, что-то улучшилось, но это как мертвому припарки — улучшение незаметно невооруженным глазом. Сайт упорно продолжает падать. Неудачный выбор плагина? Еще быстрее жму на кнопки и ставлю второй плагин. Результат… Да такой же. Сношу и этот плагин. На все эти манипуляции у меня уходит почти пятнадцать минут.

Я подозреваю, что эти плагины вполне работают. У них тысячи и тысячи пользователей и большинство из них, очевидно, счастливы. Но мой пациент не реагирует на них. Возможно, причина в его запущенном внутреннем состоянии. В любом случае этот вариант отметается — разбираться что и почему там не работает некогда.

Владелец продолжает присылать нервные письма. Я уже собираюсь ответить, что несмотря на все старания, спасти пациента сегодня не удастся и я удалюсь на свидание с подушкой. Если, конечно, ее еще не захватила жена. И тут в голову приходит еще одна мысль.

Руки начинают искать и скачивать чистый дистрибутив WP, а голова — думать. Перед глазами стоит образ дядюшки, который любил часто (и весьма ехидно) говорить мне во времена моей юности: «Знание немногих принципов часто заменяет знание многих фактов». И вот о чем я подумал:

  1. Все запросы на выдачу страниц сайта, скорее всего, проходят через некоторую единую «точку входа», роутер. Там определяется (или начинает определяться), что и в каком порядке будет дальше подключаться/считаться/выводиться.
  2. Дальше, собственно, все подключается, считается и, к сожалению, выводится. Именно тут, скорее всего, происходит пожирание ресурсов. На бесконечные запросы к БД и прочие ужасы.
  3. Ужасы заканчиваются, а результаты в некоторой «точке выхода» отдаются пользователю.


И если это все хотя бы примерно так, то можно применить систему кэширования, которую я называю «Топор». Кроме того, на этом сайте всем посетителям отдаются одинаковые по виду страницы. Т.е. конкретному URL соответствует страница, которая будет выглядеть одинаково для любого пользователя. Ну, кроме тех, что авторизовались в админ. панели WP, но они не в счет. Сейчас это только я.

Теперь нужно найти упомянутые точки выше «входа» и «выхода». «Вход» обнаруживается в файле /index.php. В котором честно написано: «This file doesn’t do anything, but loads…». Чудненько. С «точкой выхода» сложнее, но я вспоминаю, что что-то слышал про событие «shutdown» в WP, которое выполняется после всего. Быстрым поиском по чистому дистрибутиву нахожу функцию «shutdown_action_hook» в /wp-includes/load.php. Еще быстрее, написав внутри нее «echo» проверяю в каком месте страницы она выведет «GoldenAxe!». В конце. Отлично! Искомые точки найдены, на все ушло около 10 минут. Теперь нужно как-то реализовать очень простую логику:

  1. При первом обращении к странице она генерируется как обычно. После того, как весь вывод сгенерируется, его нужно будет записать в отдельный файл. Произойти это должно в «точке выхода». Файл нужно будет записать в папку /_cache/. Имя файла будет выглядеть следующим образом: md5-хэш URL страницы (без параметров), которая запрашивается. Т.к. это единственный параметр от которого зависит вид выводимой страницы, то этого вполне достаточно.
  2. При втором обращении к странице в «точке входа» мы будем проверять есть ли в папке с файлами кэша нужный (с соответствующим запрашиваемому URL md5-именем). Если есть — сразу читаем из него и выводим результат.


Все. Вся система. Остается только придумать как собрать вывод. Как многие знают, шаблоны WP — это адская смесь HTML и PHP.Т. е. в них постоянно встречаются конструкции вроде:

';
	else echo '
'; ?>


Я считаю, что это ужасно. Но так есть. Таким образом, у нас нет некоторой переменной $Content в которую бы собирался весь контент страницы перед выводом и уже затем выводился один раз с помощью того же «echo». У нас есть сотни этих «echo» в шаблонах. И их нужно как-то собрать. Когда-то давно, когда я читал мануал к PHP я встречал там функцию «ob_get_contents ()». Я никогда ей не пользовался, но тут она пригодилась. Она возвращает содержимое буфера вывода и идет в компании с функциями «ob_start ()» и «ob_end_clean ()». Первая включает буферизацию, вторая — выключает и очищает буфер. Все это я мгновенно загуглил. Прошло еще пять минут. Вот какой код я в результате поместил в «точку входа» и «точку выхода»:



Ну, а дальше оставалось только проверить результат. Я специально оставили себе возможность запросить сгенерированную стандартным способом страницу (передав в URL параметр «test» для $_REQUEST в «точке входа»). Получив выдачу страницы после кэширования «Топором» я сравнил ее со «стандартной» выдачей. Нашел небольшое отличие — в кэшированную версию не попал один js-скрипт. Тот, который отвечал за загрузку дополнительных постов в список при нажатии на кнопку «показать еще посты». Выяснять почему он не попадает в вывод было некогда, поэтому я просто добавил вызов скрипта в шаблон. И все заработало.

Конечно, у решения есть масса недостатков: если добавить через админ. панель новый пост, то он не будет отображаться в некоторых списках. В тех, которые на этом сайте не подгружаются с помощью AJAX. По какой-то причине неправильно стал срабатывать подсчет просмотров постов. Но все это мелочи, которые можно пережить несколько часов.

А плюс один, зато большой: сайт перестал отдавать код 500 или ждать ответа и выдачи страницы по полторы минуты. Теперь сам вывод (ответ страницы) занимает около 40 ms вместо 1.5 s и все пользователи без исключения могут нажимать на рекламу. Конечно, полная загрузка страницы занимает больше времени: многочисленные неоптимизированные изображения, скрипты рекламы и прочие шрифты делают свое дело. Но глазу это незаметно. Сайт работает быстро.

На все внедрение моего «Топора» ушло не более получаса. Сейчас я пойду снимать его и возвращать сайт в исходное состояние. Спортивное соревнование окончено, фанаты, а с ними и я, идем спать.

Не судите мое решение слишком строго. Помните, что я принимал его в условиях жестких временных ограничений и без серьезных знаний WordPress. Возможно, существовало более изящное и правильное решение. Если вы его знаете — напишите в комментариях. Я же могу сказать, что чувствовал себя ветеринаром, которому пришлось оперировать человека. Ну или наоборот. Хорошей вам погоды, друзья!

© Habrahabr.ru