Pull to refresh

Всесторонняя оптимизация сайта на WordPress

Reading time8 min
Views57K
Уважаемые жители Хабра!

Вашему вниманию представляется история о том, как мы оптимизировали свой сайт. Сайт работает на движке Wordpress (на этой фразе большинство читателей должны поморщиться, зная, как обстоят дела у WordPress со скоростью). Однако все-таки у нас получилось, и сайт стал летать. Сразу скажу, что меня вряд ли можно считать профессионалом по серверной оптимизации, однако то, чего удалось достичь, меня сильно радует. Также, был получен бесценный опыт, которым я хочу поделиться с читателями Хабра.

Поехали


Мы начали с исследования причин торможения сайта. Лично мне больше всего нравится сервис LoadImpact. Не так давно у него появились новые фичи и новый интерфейс, и сам сервис стал называться «LoadImpact 2.0». Все это сказано не рекламы ради — сервис действительно хорош. Также потестировать сайт можно с помощью аналогичных сервисов — Pingdom Full Page Test и Google PageSpeed.

С помощью Load Impact Page Analyzer мы обнаружили большое значение параметра Time to first byte (4 секунды), который рассказал нам о том, что PHP-скрипты, формирующие страницу, выполняются слишком долго. Нужно было искать самые тормозные компоненты сайта.

Добавив в шаблон footer.php строчку

<!-- <?php echo get_num_queries(); ?> queries -->

мы увидели, что для генерации главной страницы требуется 82 SQL-запроса, а это очень много. Стало ясно, что нужно сокращать количество модулей WordPress, а вместе с ними и количество SQL-запросов. Поразбиравшись с установленными плагинами WordPress, которых было около 40 штук, мы поняли, что все-таки большинство из них действительно нужны.

Решили сделать профилирование всего PHP-кода в шаблоне. Мы не стали заморачиваться с Xdebug + Performance Testing (для этого нужно было ставить кучу стороннего софта), а провели простое самописное профилирование вида:

<!-- HEADER01 - <?php print microtime(true) ?> -->

У нас сразу обнаружились следующие основные проблемы:
  • модуль рекламы с геотаргетингом
  • модуль, отображающий курс акций Apple
  • боковые блоки «Обзор приложений» и «Самое интересное»
Каждая из этих проблем при ближайшем рассмотрении вызывает эталонный facepalm. Сейчас расскажу о каждой из них.

Раз


Самым большим тормозом в нашем сайте оказался модуль геотаргетированной рекламы. Суть ее в том, что на сайте показывается два типа баннеров — один отображается у посетителей из Санкт-Петербурга, другой — у всех остальных. Углубившись в модуль, я обнаружил вот такой, простите, говнокод:

if ($ip_part)
{
    for ($y1=$ip_part[0]['start'];$y1<=$ip_part[0]['end'];$y1++)
    for ($y2=$ip_part[1]['start'];$y2<=$ip_part[1]['end'];$y2++)
    for ($y3=$ip_part[2]['start'];$y3<=$ip_part[2]['end'];$y3++)
    for ($y4=$ip_part[3]['start'];$y4<=$ip_part[3]['end'];$y4++)
    {
        $ip[] = $y1.".".$y2.".".$y3.".".$y4;
        if ($ip_addr == $y1.".".$y2.".".$y3.".".$y4) return true;
    }
}

В этот момент мне даже стало немного жалко процессор, который все это время выполнял пустую работу…

Исходные диапазоны IP-адресов Санкт-Петербурга задавались заранее в массиве $ip_part. Как видно из этого куска кода, при каждом обращении к функции зачем-то создавался динамический массив $ip и заполнялся строковыми (!) данными с IP-адресами, тупейшим образом через 4 вложенных цикла. В каждой итерации цикла IP-адрес посетителя сравнивался построчно (!) с получившейся строкой IP-адреса. Пофиксить это было просто:

$ip_tmp = explode(".",$ip_addr);
if ( $ip_part )
    if ( ($ip_part[0]['start'] <= $ip_tmp[0]) and ($ip_tmp[0] <= $ip_part[0]['end']) )
    if ( ($ip_part[1]['start'] <= $ip_tmp[1]) and ($ip_tmp[1] <= $ip_part[1]['end']) )
    if ( ($ip_part[2]['start'] <= $ip_tmp[2]) and ($ip_tmp[2] <= $ip_part[2]['end']) )
    if ( ($ip_part[3]['start'] <= $ip_tmp[3]) and ($ip_tmp[3] <= $ip_part[3]['end']) )
        return true;

Не отрицаю, что этот код можно переписать и более оптимально, однако и после этой правки сайт заработал на 2 секунды быстрее!

Два


Второй плагин под названием Embedded Stock Data, который у нас на страничке показывал котировки Apple. Обнаружилось, что этот гад при каждой загрузке страницы ходил на другой сайт (!) через CURL. Естественно, это сильно увеличивало время загрузки страницы. Решили проблему естественным образом — на сервере раз в минуту по крону вызывался скрипт, который скачивал нужную котировку и сохранял ее в специальный файл. Далее мы этот файл инклудили. Это дало нам около 500 мс.

Три


Боковые блоки «Обзор приложений» и «Самое интересное» также выполнялись ощутимое время. Заглянув внутрь шаблона (sidebar.php), я увидел классическую ошибку начинающих веб-программистов под названием «ORDER BY RAND()». Подробнее о сути этой проблемы можно, например, почитать тут.

Для интересующихся, был вот такой код:

$args=array(
    'caller_get_posts'=>'1',
    'post__not_in' => $sticky,
    'cat'=>'75',
    'fields'=>'ids',
    'post_per_page'=>'20',
    'orderby'=>'ID',
);
$sk_count = new WP_Query($args);

$args=array(
    'caller_get_posts'=>'1',
    'post__not_in' => $sticky,
    'showposts'=>'4',
    'cat'=>'75',
<b>    'orderby'=>'rand'</b>
);
$sk_posts = new WP_Query($args);

В нем мы заменили выделенную строку на

    'post__in'=>array_rand(array_flip($sk_count->posts),4)

Суть решения — мы предварительно выбираем 20 самых свежих записей, потом выбираем из этих 20 записей случайные 4 штуки, а затем уже выбираем из базы посты с этими номерами.

На этом мы сэкономили еще полсекунды.

Переезд на Clodo


Наш железный сервер изначально хостил большое количество сайтов, но самым главным по трафику и по значению был AppleInsider.ru. Нам захотелось обезопасить этот сайт от соседства с другими сайтами, и было решено перенести его в облако компании Clodo. Главным преимуществом Clodo можно назвать «эластичность» тарификации — сколько ресурсов используешь, столько и платишь. Положа руку на сердце, также хочется отметить техподдержку компании, которая даже в не-VIP варианте вела себя корректно, отвечала на все наши каверзные вопросы и помогала собственно в настройке.

«Под шумок» с переносом были сделаны некоторые изменения, например мы убрали насовсем тяжелый веб-сервер Apache, а вместо него сделали связку nginx + php-fpm, которая кушает меньше памяти. Nginx работал по обычной схеме — отдавал статику, а все остальное перенаправлялось к скрипту index.php, в котором идет дальнейшая WordPress-логика.

Чтобы не отнимать ресурсы железа, отведенного под веб-сервер nginx + php-fpm, мы вынесли сервер базы данных MySQL на отдельный сервер, связав его с Web-сервером посредством локальной сети.

Также мы настроили два отдельных «облачных» сервера — для нашего подкаста AppleInsider.ru и Яблочного огрызка и для текстовых онлайн-трансляций. О них мы расскажем подробнее в следующий раз.

Переход на Clodo ненамного сократил время генерации страницы, зато это обезопасило сам сайт.

WP Super Cache и асинхронная реклама


На этом этапе страница у нас формировалась примерно за 1 секунду. Уже неплохо, но нам было мало, ведь оставалось еще несколько моментов, которые можно было оптимизировать.

Например, воспользоваться плагином для кеширования страниц WordPress'а под названием WP Super Cache. Работает как обычный кеш — пользователь заходит на страницу, она при этом генерируется и кладется в каталог /wp-content/cache/supercache. Следующему посетителю будет отдаваться уже статический заранее сформированный HTML-файл. Казалось бы, все супер — сайт летает…

Однако, как вскоре обнаружилось, использование кеширования поломало геотаргетированную рекламу. Проверка на вхождение адреса посетителя в диапазон адресов Санкт-Петербурга выполнялось на уровне PHP, т.е. уже за кешем. Соответственно, результат этой проверки «навеки» оставался в кеше. А нам нужно было все-таки разграничивать показ баннеров.

Решили эту задачу следующим образом — в шаблоне странички в нужных местах были расставлены Javascipt-инструкции, указывающие на то, что в этот div-блок нужно подгрузить контент с определенного URL-а. Таким образом, пользователь беспрепятственно получает закешированную страницу с Javascript-инструкциями, и уже после появления первых букв начинают загружаться рекламные и прочие структурные блоки сайта, хранящимся по отдельным специальным URL-адресам. Визуально это происходит очень быстро. Так нам удалось достичь двух целей — во-первых, за счет кеширования значительно ускорился сайт, и во-вторых, получилась корректно работающая геотаргетированная реклама.

ClodoStorage


Последний шаг, который мы сделали в оптимизации — перенесли статический контент (картинки, стили CSS, Javascript) на CloudStorage. Подробнее о нем можно почитать тут. В двух словах — это Content Delivery Network от компании Clodo, а по-русски — очень быстрый сервер, специально заточенный для раздачи статики.

Интеграция с WordPress состоит в правильной настройке двух плагинов — CDN Sync Tool (для Clodo нужно использовать специальную сборку библиотеки PHP Cloud Files API) и WP Super Cache.

Первый плагин, CDN Sync Tool, занимается тем, что заливает контент WordPress на хранилище, производя попутно с этим самым контентом какие-нибудь действия. При наличии плагина WP Minify он может сжимать CSS- и JS-файлы. Также он может налету сжимать загружаемые картинки. При этом используется библиотека GD, для нас наиболее приемлемым оказался уровень сжатия GD Compression Level = 2 — выражаясь в терминах других программ, это 90% качества. Самое главное, конечно, это то, что при стандартной загрузке картинок авторами, они стали автоматически заливаться на хранилище.

Для статики мы завели отдельный поддомен static.appleinsider.ru, а первоначальную загрузку контента проделывали с помощью CyberDuck — это довольно удобная программа для работы с FTP, SFTP, WebDAV, Cloud Files, Google Docs и Amazon S3.

Вот скриншот плагина CDN Sync Tool, тюнингованного под Clodo (кликабельно):



Второй плагин, о котором мы уже упоминали, WP Super Cache, поступает хитро — он берет сформированную страницу и заменяет в ней все URL-ы для статических файлов на URL в хранилище. Например, http://www.appleinsider.ru/Angry-Birds.jpg станет http://static.appleinsider.ru/Angry-Birds.jpg

Итоги




Сейчас при отсутствии страницы в кеше страница загружается за 1.25 секунды, при наличии в кеше — 250 мс — и это с учетом всех 4 факторов — DNS Lookup + Connection + Time to first + Download. Я считаю, что это потрясающий результат!

В заключение приведу сухие факты — статистику использования ресурсов и финансовые затраты.

За неделю работы 23.11.2011 — 30.11.2011:

Web




Scale Server — ресурсы оперативной памяти 45708.00 GB*min 457.080 руб.
Scale Server — ресурсы процессора 1027116.50 CPU sec 427.300 руб.
Использование дисковых ресурсов 5760.00 GB*hour 57.600 руб.
Входящий трафик (полных гигабайт) 34 GB 6.800 руб.
Исходящий трафик (полных гигабайт) 129 GB 129.870 руб.
Использование диска для резервных копий 1910.00 GB*hour 19.100 руб.
SMS уведомления 1 шт. 5.000 руб.
Всего списано со счета 1102.75 руб.


DB




Scale Server — ресурсы оперативной памяти 22924.00 GB*min 229.240 руб.
Scale Server — ресурсы процессора 67490.34 CPU sec 28.080 руб.
Использование дисковых ресурсов 3840.00 GB*hour 38.400 руб.
Входящий трафик (полных гигабайт) 1 GB 0.200 руб.
Исходящий трафик (полных гигабайт) 12 GB 12.000 руб.
Использование диска для резервных копий 1910.00 GB*hour 19.100 руб.
Всего списано со счета 327.02 руб.


CloudStorage



Cloud Storage — использование диска 1139.00 GB*hour 11.390 руб.
Cloud Storage — входящий трафик (полных гигабайт) - не тарифицируется
Cloud Storage — исходящий трафик (полных гигабайт) 330 GB 330.000 руб.
Всего списано со счета 341.39 руб.

Всего за неделю работы сайта — 1771.16 руб. Довольно много, но стабильность и довольные пользователи стоят того :-)

Очень жду ваших комментариев.
Tags:
Hubs:
+84
Comments31

Articles