И снова о кэшировании фрагментов в 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 мс

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

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

Добавить комментарий

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