И снова о кэшировании фрагментов в WordPress

Перевод статьи: WordPress Fragment Caching Revisited.
Автор: Ryan Burnette.

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

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

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

Многие на самом деле способные люди уже приложили свои усилия к решению данной проблемы. Сообщество WordPress разработчиков представило несколько отличных плагинов кэширования и "W3 Total Cache" один из них. Я с удовольствием и довольно часто использую эти плагины, но в некоторых ситуациях весь предоставляемый ими функционал мне не нужен. У меня, к примеру, могут возникнуть случаи, при которых конфигурация нежелательна в принципе или же могут присутствовать трудно совместимые с кэшированием элементы. Да и вообще, необходимо стараться сводить количество используемых плагинов к минимуму, поскольку это позволяет избежать появления различных сложностей в обслуживании ресурса в будущем.

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

Кэширование фрагментов.

Загрузка страницы в WordPress производится в результате обработки PHP кода и MySQL запросов к базе данных. Выполнение некоторых блоков кода связано с множеством запросов, на обработку которых требуется дополнительное время. Кэширование фрагментов заключается в получении результата работы «проблемного» (в плане производительности) блока кода и хранении его в течении предопределенного промежутка времени. При следующих загрузках документа, в процессе выполнения кода, в период, когда соответствующий временной интервал еще не истек, необходимый блок кода игнорируется, а сохраненный ранее результат его работы (выходные данные) используются при визуализации страницы.

Сама концепция кэширования фрагментов не нова. Разработчик ядра WordPress Марк Джэкит (Marc Jaquith) уже писал об этом ранее. Позже я обнаружил Gist, упрощающий созданный Джэкитом класс до функции и все, что мне оставалось сделать, это произвести форкинг кода с нужного места и модифицировать его.

В ранних версиях WordPress (до 2.5) для постоянного хранения данных или для их хранения на период, превышающий время загрузки одной страницы могли быть использованы объекты WP_Cache, что и продемонстрировал в своем примере Джэкит. С помощью Transients API можно создать перманентные объекты базы данных, наделенные удобным свойством определения их срока жизни. В моем сниппете, содержащем пример кэширования фрагментов, используется этот метод хранения.

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

function fragment_cache($key, $ttl, $function) {
if ( is_user_logged_in() ) {
call_user_func($function);
return;
}
$key = apply_filters('fragment_cache_prefix','fragment_cache_').$key;
$output = get_transient($key);
if ( empty($output) ) {
ob_start();
call_user_func($function);
$output = ob_get_clean();
set_transient($key, $output, $ttl);
}
echo $output;
}

У этой функции есть три аргумента:

  • Key: обычная строка, с помощью которой идентифицируется кэшируемый фрагмент. Заметьте, что функция добавляет к строке свой префикс чтобы избежать конфликтов с другими кэшированными данными («транзиентами»). Вы можете изменить этот префикс, отредактировав саму функцию или же путем добавления фильтра, который совпадает с тегом 'fragmentcacheprefix'.
  • ttl (time to live): временной интевал в секундах, в течении которого сохраняется кэш. Я обычно использую для этих целей временные константы. Если сутки перевести в секунды, то получим 86400 секунд, что соответствует константе DAYINSECONDS. Это облегчает задачу тем из нас, у кого нет желания возиться с простыми математическими расчетами.
  • Function: функция, результат работы которой нам, собственно, и необходимо сохранить. Как видно из примера, это может быть что угодно.

Примеры использования.

Применять кэширование фрагментов на практике очень легко, это можно сравнить с обычным включением HTML и PHP кода в функцию.

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

<p>Here's some HTML.</p>

<? php
// Here's some PHP
$args = array(
'post_type' => 'my_data',
'posts_per_page' => -1
);
$posts = get_posts($args);
foreach ( $posts as $p ) {
echo '<pre>';
echo get_post_meta($p,'some_meta', true);
echo '</pre>';
}?>

<p>Имеющийся здесь PHP код выполняется и производит запросы
к БД при каждой загрузке страницы.:(</p>

А теперь давайте применим к этому коду функцию кэширования фрагментов, описанную в предыдущем сниппете. Обратите внимание на то, что никаких кардинальных изменений не происходит, мы как обычно используем комбинированный HTML и PHP код, результат работы которого в данном случае фиксируется функцией и кэшируется.

<? php
// Теперь имеем…
fragment_cache('my_footer', DAY_IN_SECONDS, function() {? >

<p>Here's some HTML.</p>

<? php
// Here's some PHP
$args = array(
'post_type' => 'my_data',
'posts_per_page' => -1
);
$posts = get_posts($args);
foreach ( $posts as $p ) {
echo '<pre>';
echo get_post_meta($p,'some_meta', true);
echo '</pre>';
}
? >

<p>Любой результат выполнения этого блока кода будет
закэширован в виде фрагмента.:)</p>

<? php });? >

Давайте еще раз вкратце рассмотрим три аргумента нашей функции:

  • Тег, представляющий создаваемый кэш. Небольшая подсказка — если результат работы вашего кода зависит от генерируемой в данный момент страницы, то необходимо путем конкатенации включить в тег идентификатор поста (post ID), что позволит вам кэшировать фрагмент для каждой страницы в отдельности. Это особенно важно, когда основной цикл как раз и является кэшируемым фрагментом.
  • Таймаут. Я, как правило, в качестве этого параметра использую временные константы WordPress, но можно, конечно же, указать любую величину в секундах.
  • Собственно сами выходные данные, которые нужно сохранить. Заметьте, что они заключены в тело функции, которая используется как параметр функции кэширования фрагментов. Все верно, в PHP мы можем в качестве аргумента функции передавать другую функцию.

Области применения.

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

Настраиваемые завершители.

Одним из наиболее подходящих случаев является формирование настраиваемого завершителя («подвала»). Я зачастую создаю завершители, содержащие не только стандартные, предусмотренные WordPress платформой элементы меню, включая в их состав данные, полученные с помощью функции get_post(), а также вызываемой в ходе итерации для каждого поста функции get_post_meta(). Таким образом я обнаружил, что в подавляющем большинстве случаев для отображения объемного завершителя требуется примерно 100 — 200 миллисекунд. Благодаря применению кэширования фрагментов время, требуемое для загрузки подобных компонентов страницы будет несущественным.

Таблицы данных.

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

Чрезвычайно сложные и длинные циклы.

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

Пример тестирования.

Я являюсь вебмастером ресурса STUDIOCRIME, который специализируется на объединении видео street art направления. Используемая нами платформа WordPress прекрасно справляется со своей задачей, позволяя нашим кураторам без труда размещать и организовывать имеющуюся на сайте видео информацию. При каждом посещении страниц, на которых представлены коллекции видео, производится загрузка более 80 постов, причем в ходе каждой иттерации выполняются запросы к базе данных для извлечения мета данных соответствующего поста.

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

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

Несмотря на сложившуюся ситуацию мы все же решили не использовать плагин кэширования типа "W3 Total Cache" потому, что хотели оставить возможность контроля над процессом загрузки страницы и отслеживания пользовательских данных в рамках PHP кода. Кэширование страницы смогло бы предотвратить выполнение этого кода.

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

Я выполнил тесты с помощью Apache Bench. Данная утилита производит один и более одновременных или последовательных запросов, предоставляя отчеты о времени, которое уходит на генерацию страниц веб-сервером. Обратите внимание на то, что без применения кэширования одиночный запрос загружается в три раза дольше. Принимая во внимание множественные запросы, время на ответ может достигать весьма существенного значения от 3 до 5 секунд. Применение кэширования фрагментов позволяет «откатить» показатели времени загрузки на приемлемые позиции и сохранить требуемый уровень производительности в более сложных условиях загрузки.

Ниже приведены результаты тестовых загрузок одной страницы при получении 10, 100 и 1000 одновременных запросов.

Количество запросов
Apache Bench
Без кэшированияС кэшированием
10 запросов1426 мс518 мс
100 запросов3498 мс658 мс
1000 запросов5116 мс895 мс

Удачного вам кэширования!

Оставить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *