Ускорение загрузки WordPress на примере

Есть у меня один сайт, поддержкой которого занимаюсь уже долгое время, — авто.Магадан. По большому счету это блог на WordPress с возможностью публикации пользовательского контента (объявлений) через фронтэнд без регистрации. Основная масса посетителей этого сайта это жители Магадана и области. Сайт хостится в Магадане у местного интернет провайдера. Так его могут посещать и пользователи с местных локальных сетей (интернет в городе дорогой, только через спутник), у сайта это значимая часть аудитории.
Зато вот пользователи с интернета получают огромные задержки.
Это была предыстория (ну и как бы оправдание, почему сайт и до сих пор грузится не супер быстро 🙂 — убрать задержки со спутника все же не в моих силах). Дальше под катом описание моих действий, что я делал, чтоб хоть сделать сайт более комфортным.

Перед началом любой оптимизации нужно выяснить, что тормозит. Соответственно, что нужно оптимизировать.
В этом случае тормозило все — апач, мускуль, сам сайт, даже домен прикреплен был с ошибками 🙂
top показывал большую загрузку у процессора (average 6-10, idle 0 и тп)
Сразу после перезапуска апача\мускуля свободная память съедалась до 20-30 мб
Поэтому начать решил с оптимизации и чистки сервера. Существенный нюанс — невозможность обновления ПО сервера и невозможность добавления всяких полезных штук вроде APC, memcached, ngnix и тп.

Ранее в некоторых разделах сайта для подсчета статистики использовался piwik. За, примерно, год этот счетчик база этого счетчика разрослась более 5 гигабайт. Счетчик убрал, базу грохнул.

Поставил mysqltuner.pl перенастроил мускуль по его рекомендациям: перевел все в MyISAM, отключил Innodb. Чуть по другому перенастроил память.
Позже отключил и slow queries log.

Мускуль настраивал недели две — менял настройки памяти, оставлял на несколько дней, результаты сравнивал. Вот такой конфиг вышел самым оптимальным.

Практически не отличается от значений по умолчанию в my-huge.cnf

Следующий этап, оптимизация апача.
Получился вот такой конфиг, те куски, которые я менял:

Количество серверов посчитал из расчета 2048mb выделено под апач / каждый процесс максимум 40mb = 2048mb/40mb = 50.

В списке загружаемых для php модулей найти ненужные не получилось. Оставил как есть.

Дальше перешел к самому сайту.

Загрузка картинок, js, css

Перед началом этого этапа я разнес все скрипты (большую часть вынес в футер) и файлы стилей (оставил один общий, и несколько мелких, подгружаемых на редко посещаемых страницах). Постарался убрать inline js код.

Основная проблема для пользователя — физическая удаленность сервера.
Переместить весь сайт на хостинг ближе к Москве нельзя (локальные пользователи не смогут зайти). Поэтому пришлось часть контента вынести на CDN (даже с сервера в Токио файлы загружаются быстрее, чем с сервера в Магадане :(( ).
В зависимости от IP пользователя он получает файлы с сайта либо с CDN. В шаблоне это реализовано примерно так:

Вся статика шаблона (картинки, css, js) раздается с CDN Amazon.

Часть картинок контента я раздаю с CDN cloudinary.com — у этого сервиса есть возможность трансформировать изображения, накладывать фильтры, вотермарки и тп.
Получается разгружаю процессор (часть картинок режется на другом сервере), ускоряю скорость скачивания этих картинок (гружу с CDN) и делаю миниатюры красивее.

На маленькие миниатюры накладываю фильтр для дополнительной четкости, и ужимаю качество картинки до ~80%.

было (3 кб)
auto.magadan-Honda-Z-4-220x1001
стало (2.66 кб)
auto.magadan-Honda-Z-4-220x100
Меньше размер, загружается быстрее, да еще и смотрится лучше 🙂

Перед переносом скриптов на CDN пришлось переписать некоторые плагины. Хотел раздавать скрипты с 2 файлов — plugins.js для плагинов и script.js для моего кода. Но не все плагины, используемые сайтом, написаны согласно спецификации WordPress, поэтому пришлось править их файлы.
Впрочем, эту часть еще не закончил, планирую установить на сайт другой скрипт видео плеера. Код js с текущего плеера в общий файл включать не стал. Пока гружу отдельно.

Загрузка видео.
Пользователи с интернета жаловались на низкую скорость загрузки видео файлов. Пришлось при добавлении видео загружать одну копию на сервер для локальных пользователей и добавлять ссылку на файл в интернете (на ютубе, вимео и тп) для интернет пользователей.
Сейчас это сделано не совсем удобно — через мета поля у записей.

Код сайта.
Частично переписал текущий шаблон.
Переписал вывод рандомных объявлений в верхнем блоке. Число объявлений там небольшое. Раньше выборка рандомных записей делалась через sql, стандартным вордпрессовским orderby=rand. Переписал вот так:

if ( false === ( $vip_loop = get_transient( ‘last_vips1’ ) ) ) {
$args = array(
‘post_type’ => ‘add’,
‘posts_per_page’ => 100,
‘tax_query’ => array(
‘relation’ => ‘AND’,
array(
‘taxonomy’ => ‘vip’,
‘field’ => ‘slug’,
‘terms’ => ‘top’
)
)
);

Т.е. вывожу полностью все объявления с таким статусом (их всегда меньше 100), и кэширую результаты в transient cache на долгий срок (при смене статуса объявления у меня выполняется сброс кэша), и уже только после этого на пхп сортирую полученный массив в произвольном порядке и вывожу.
Получаю на выходе блок с 5-10 объявлениями.
Но опять же, канал у меня медленный, трафик нужно сжимать. На экран у пользователя из полученных объявлений входят не все.
Поэтому сразу показываю картинку только у первых, те, которые не вместились вывожу вот так:

?><img src=»» alt=»» data-img=»» /><?php

Позже, при прокрутке слайдера, меняю src на data-img. Так картинка начинает загружаться только если она нужна пользователю.

В левой части сайта вывожу картинки с инстаграмма и последние записи с форума. И то и то периодически отдается с большой задержкой. В начальном варианте вывод этих блоков был сделан прям в шаблоне. Эти блоки для пользователя не самые важные, поэтому вынес их подгрузку аяксом на потом. Результаты запроса так же пихаю кэш используя transient api.

С другой стороны, все же хотелось бы, чтоб пользователь эти блоки видел (ну и снизить к-во запросов к серверу). Поэтому теперь результаты запроса сохраняются в localStorage. И при загрузке страницы скрипт вначале пробует вернуть данные с компьютера пользователя. Использовал localStorage, а не sessionStorage т.к. первое доступно нескольким открытым вкладкам.

htaccess

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

Прочие оптимизации

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

В хедер сайта добавил вот такой код:

    if(is_front_page()){
?><link rel=»prerender» href=»http://auto.magadan.ru/cars/»><link rel=»prefetch» href=»http://auto.magadan.ru/cars/»><?
} elseif( is_post_type_archive( ‘add’ )){
$paged = (get_query_var(‘paged’)) ? get_query_var(‘paged’) : 1;
$paged=$paged+1;
if($paged<5){
echo ‘<link rel=»prerender» href=»http://auto.magadan.ru/cars/page/’.$paged.’/»><link rel=»prefetch» href=»http://auto.magadan.ru/cars/page/’.$paged.’/»»>’;
}
}

Основной выигрыш получают пользователи хрома. В двух словах, при посещении главной станицы сайта, хром открывает скрытую вкладку, в которую загружает  страницу с объявлениями (http://auto.magadan.ru/cars/). Если пользователь пробудет на главной странице какое-то время, при клике на странице с объявлениями эта страница откроется практически мгновенно.

То же самое и для первых 4 страниц в разделе с объявлениями. Сделал только для 4х первых страниц, посчитал, если пользователь решил искать дальше, то терпения дождаться открытия следующих страниц у него точно хватит 🙂 Ускорять загрузку особо смысла нет.

Итоги

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

Синим цветом отмечено текущее состояние. Красным — до оптимизации.

2014-01-12_1748

Сейчас страница визуально уже заканчивает загружаться к 6-10 секунде, раньше к этому времени не загружало даже 50%.

2014-01-12_1752

Время первого ответа сервера сократилось на почти пол секунды — с 4,5 секунд до 3,9. (Если отключить KeepAlive, время ответа сокращается до 1.8-2 секунд, но общее время на загрузку страницы увеличивается).

2014-01-12_1756

Снизил и общее количество запросов. Большей частью оптимизировав соц.кнопки, баннеры, счетчики статистики. Это, правда, чуть увеличило число js файлов 🙂

2014-01-12_1759

По сравнению с начальным тестом сейчас использую картинки большего размера, добавилось несколько «тяжелых» блоков (вывод картинок с инстаграмма, два блока с картинками в верхней части сайта). Общий вес сайта все равно стал меньше — за счет сжатия и перехода на CDN.

2014-01-12_1808

В итоге сайт загружается значительно быстрее чем некоторые другие местные сайты с более мощным хостингом. 🙂


Небольшое обновление:

как уже писал, сайт физически расположен в Магадане. ДНС сервера домена физически расположены там же. Ресолв домена при первом посещении занимает от 4 секунд.

Доступа к настройкам домена, к сожалению, нет. Добавить что-то к настройкам текущего домена не могу. Сделал проще — зарегистрировал новый домен. Прицепил его же к сайту. Со временем буду перетаскивать пользователей с интернета на новый домен. Старый останется для пользователей в Магадане.

По тестам получилось как-то так:

2014-01-26_0625

domain

Тесты проводил в одно и то же время. Разный вес и разное количество запросов из-за того, что некоторые баннеры в ротации подгружают разный код (на тесте с am49.ru вес страницы вышел на 100кб больше).

С новым доменом получилось, что сайт в ~2 раза быстрее открывается. Не ожидал такого эффекта.


Еще одно добавление:

На сайте две страницы с самой большой посещаемостью — главная и страница /cars/ (главная раздела с объявлениями).
Некоторые пользователи при посещении сайта практически сразу переходят на страницу раздела с объявлениями.
Для ускорения загрузки этой страницы добавил
link rel=»prerender» href=»http://am49.ru/cars/»
link rel=»prefetch» href=»http://am49.ru/cars/»
Как уже писал, если пользователь проводит 10-15 секунд просматривая главную, страница раздела с объявлениями открывается у него мгновенно.

К сожалению, не все пользователи переходят с главной сразу же в этот раздел. Некоторые просматривают главную, переходят в какую-то из новостей, и только после этого открывают страницу раздела с объявлениями.
У таких пользователей «prerendered page» выигрыша в скорости не дает, т.к. хром, при посещении любой другой ссылки, такую вкладку убивает.
Получается лишняя нагрузка на сервер и трата трафика пользователя.

Дописал скрипт, теперь, при переходе на страницу раздела с объявлениями, таблица объявлений сохраняется в localStorage.
И при переходе на главную этого раздела (с любой страницы) подгружает кэшированный контент с компьютера пользователя. Кэш храню около 10 минут (средняя длительность посещения 6 минут), при добавлении нового объявления — сбрасываю.

На мой взгляд, получилось отлично — снижает практически до 0 ложные prerender запросы к этой страницы, ускоряет переход в раздел, несколько минимизирует трафик пользователя.

Автор: dimas

8 комментариев к “Ускорение загрузки WordPress на примере

  • как верно заметил в твиттере @delaz, можно было сделать вот так:



    Для сайта-визитки установка и настройка кэширующего плагина может быть подходящим решением для оптимизации скорости. Для сайтов клиентов так и делаю :)
    А вот на своих проектах, когда есть время, все же делаю в ручную: не каждый плагин подходит может решить полностью все задачи.

    К сожалению нет такого плагина, способного сделать полностью все (ставить зоопарк с плагинов тоже не самый лучший вариант). Даже того же W3 Total Cache иногда не хватает. Хочется еще и такое: перенос статики на cookie-less домен (хотя там есть возможность подключить CDN, но мне функционал не подошел. нужна работа как с pull так и с push CDN), lazy-load картинкам, выдача разного js\css разным группам пользователей.


    Разберу по пунктам с твита:
    1.Кешировать WP в статику (плагин).
    То есть поставить любой из кэширующих плагинов. Согласен, тот же W3 Total Cache делает большую часть из описанного мной в посте буквально за несколько кликов. Делает, но не совсем так, как хотелось бы :)
    К сожалению, установкой плагина решить все проблемы не удалось.

    Во первых сайт раздается в 2х вариантах. Есть две разные версии сайта, нужный вариант шаблона определяется по IP адресу. (Раньше было 4 - для инет пользователей, для локальных, для мобильных, для мобильных с билайн. Упростил, т.к. конверсия с мобильных пользователей минимальна).
    Использованные плагины кэширования страниц в статику "из коробки" разделять пользователей на нужные группы не умеют. (как вариант, можно переписать шаблон, чтоб грузить дополнительный функционал через js разным группам. но это жеж получается нужно переписывать).
    Впрочем, похожий функционал есть В WP Super Cache. Там есть возможность дифференцировать пользователей по IP, но этот плагин не умеет оптимизировать js, css и тп.

    Вторая причина - PHP. Вернее его версия. Для этого сайта обновление PHP очень трудоемко.
    Там же рядом с сайтом стоит форум, на старой версии IPB. Под него покупалась лицензия. В новых версиях IPB сменили тип оплаты за лицензию - нужно платить каждый год. Платить не хочется) Текущая версия форума на новой версии PHP запускаться отказалась. Перенос форума на другой движок - так же очень далеко от реальности.
    На старой версии PHP работают только старые версии WordPress. Соответственно и поставить можно только старые плагины. У старых версий плагинов функционал не настолько продвинут, как у актуальных версий. И есть баги: например, после установки W3 Total Cache и включения кэширования запросов, перестал корректно работать парсинг картинок с инстаграмма и сообщений с твиттера. Вернее эти запросы перестали кэшироваться. Сохранял их через transient API, видимо в старой версии плагина был баг.
    Чуть позже, надеюсь, смогу подобрать подходящую версию WP Super Cache, тогда буду и страницы в статику писать. Это однозначно снизит нагрузку на процессор. Сейчас плагином кэширются только MySQL запросы.

    В общем, получилось, что после установки и настройки кэширования плагинами, большую часть работы все равно приходилось делать руками. Попутно допиливая сами плагины.
    Решил, что по потраченному времени оптимальнее будет сделать как описал в посте - руками (но в начале провести оптимизацию только за счет плагинов я все же пробовал). А уже "доводить" оптимизацию плагинами.
    Ну и работать с своим кодом удобнее, чем искать документацию по старым версиям.

    2.Минимиз. кол-во CSS,Js, Img с домена
    Как уже писал в прошлом абзаце, установка плагина не сработает. Поэтому все руками.
    А как писал в посте, количество файлов я таки снизил. Грузит один css для всех страниц, и несколько мелких, для редко-посещаемых. js разбил на 4: jQuery и modernizr - эти два гружу в начале страницы. И plugins и script - в первом все плагины, в втором мой код. Их гружу в футере. Можно, в принципе, слить последние два файла в 1 общий. Но, считаю, не критично. Перемещение файлов на CDN дало больший прирост в скорости, чем уменьшение количества файлов.
    Остальные js файлы грузят всякие партнерки, гуглы, яндексы и тп.

    3.Убрать inline js.
    Это так же было сделано. Частично :) Не весь инлайн js код можно убрать быстро. При следующем обновлении буду доводить до ума. Сейчас и так пойдет. Главное - результат от оптимизации есть, сайт визуально грузится быстрее, жалобы от пользователей на медленную загрузку пропали. (Не, тут, конечно, есть вариант, что они привыкли и смирились, но все же надеюсь на лучшее :) )
  • Тот сайт требует еще очень много работы, вы протестируйте его с помощью
    http://siteloadtest.com/ru/
    Для начала нужно вычистить миллион плагинов от соцсетей, яндекса и тд.
    • Сергей, все же не соглашусь.
      Во время любой оптимизации главное во время остановиться :) - иметь какую-то цель, по достижении которой работы прекратить.
      При оптимизации этого сайта, целью было сделать посещение сайта чуть более комфортным. Уменьшив время ответа от сервера, которое Time to first byte и визуальное время загрузки сайта (Visual Complete) для пользователей с центральных районов страны.

      Для соц.сетей сейчас используется только share.yandex.ru. Этот блок (подгрузка к-ва лайков) добавляется уже после загрузки. Даже если кнопки уберу, сайт загружаться быстрее (имею в виду не полную загрузку сайта) не станет.

      И по поводу CDN не думаю, что совет правильный.
      В интернетах наоборот активно советуют их использовать. Может я читаю не тот интернет?) Или не правильно вас понял?
      На мой взгляд, переместить контент ближе к пользователю - правильное решение.
  • И нужно избавиться от CDN. Долго объяснять, почему это плохо... Почитайте, подумайте.
  • убрала пивик сайт летает!
  • Как пользователь могу отметить, что реально сайт перестал так дико тупить. Есть только заморочка с am49.ru - если перейти на форум через главную этого сайта, авторизоваться и разместить сообщение со ссылкой на тему в этом же форуме,то она будет так же начинаться на am49.ru Получится, что пользователь с локальной сети города, который видит auto.magadan.ru/talk из локалки начнет ломиться в инет. Если инет есть, то все получится, но попадешь на страницу как гость :( и не сможешь, например, картинки посмотреть или ответить.
    Есть возможность как-то подменять ссылки, исходя из ip ?
    • спасибо за комментарий, как-то упустил этот момент.
      как разгребу текущие заказы, добавлю, чтоб ссылки менялись автоматом.

      для локалки сделаю через js автозамену всех ссылок на текущий адрес сайта.
      думаю, это самый простой способ сделать правильные ссылки.
  • И при этом нет ни времени, ни желания углубляться в дебри оптимизации, надо просто сделать так, чтобы сайт загружался быстро и стабильно. О том, что, ускорение загрузки сайта даже на полсекунды важно уже много написано и известно всем.