Перейти к содержимому

Кэширование объектов в WordPress

Кэширование объектов (object cache) — встроенный механизм ядра WordPress, который сохраняет данные произвольного типа и отдаёт их при повторных обращениях. Используется для хранения результатов сложных операций: запросов к базе, вычислений, внешних вызовов.

Пример без кэша — каждый вызов функции бьёт в базу:

function foo() {
global $wpdb;
return $wpdb->get_results( "SELECT ..." );
}
foo(); // SQL!
foo(); // SQL!
foo(); // SQL!

Та же функция с объектным кэшем:

function foo() {
global $wpdb;
$cache = wp_cache_get( 'foo' );
if ( $cache ) {
return $cache;
}
$value = $wpdb->get_results( "SELECT ..." );
wp_cache_add( 'foo', $value );
return $value;
}
foo(); // SQL!
foo(); // cache
foo(); // cache

В ядре объектный кэш используется практически для всего: опции, записи, страницы, метки, категории, пользователи. Именно поэтому повторные вызовы get_option() или get_post() в одном запросе не создают лишних запросов к базе.

Кэш реализован классом WP_Object_Cache, но работать с ним удобнее через вспомогательные функции.

Читает значение по ключу. Если ключ не найден — возвращает false.

$value = wp_cache_get( 'key' );
$value = wp_cache_get( 'key', 'my-group' );

Два редких дополнительных параметра:

  • $force — принудительный поиск во внешнем хранилище в обход локального кэша;
  • $found — переменная по ссылке, становится true/false в зависимости от того, найдено ли значение. Полезно, когда в кэше может лежать легитимный false:
$value = wp_cache_get( 'key', 'default', false, $found );
if ( ! $found ) {
// Ключа точно нет в кэше — регенерируем
}

Добавляет значение по ключу. Если ключ уже существует — ничего не делает и возвращает false.

$added = wp_cache_add( 'key', 'value' );
$added = wp_cache_add( 'key', 'value', 'my-group' );

Параметры: $key, $data, $group (по умолчанию default), $expire (время жизни — работает только в некоторых persistent-плагинах).

Те же параметры, но перезаписывает значение, если оно уже есть. Если значения нет — добавляет.

wp_cache_set( 'key', 'value', 'my-group' );

Заменяет только существующее значение. Если ключа нет — ничего не добавляет.

wp_cache_replace( 'key', 'new-value', 'my-group' );

Логика трёх функций: add — только если нет; set — в любом случае; replace — только если есть.

Удаляет значение по ключу. Вернёт true, если значение найдено и удалено.

$deleted = wp_cache_delete( 'key', 'my-group' );

Инкремент и декремент числовых значений — для счётчиков:

wp_cache_incr( 'key' ); // +1
wp_cache_incr( 'key', 2 ); // +2
wp_cache_incr( 'key', 1, 'my-group' );
wp_cache_decr( 'key' ); // -1

Сбрасывает весь объектный кэш. Использовать с особой осторожностью: на нагруженном сайте полный сброс вызывает всплеск запросов к базе.

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

Это не бесполезно: за один запрос WordPress вызывает get_option() около 500 раз — кэш в памяти схлопывает их до одного обращения к базе.

Постоянный кэш реализуется плагинами, которые подменяют хранилище на внешнее:

Технически это drop-in object-cache.php в wp-content/. При выборе хранилища учитывайте, сколько памяти потребуется самым частым объектам — и конфигурируйте сервер с запасом.

Проверка наличия внешнего кэша в коде плагина:

if ( wp_using_ext_object_cache() ) {
wp_cache_set( 'key', 'value' );
} else {
update_option( 'key', 'value' );
}

С внешним кэшем появляется понятие локального кэша: при повторном запросе ключа в рамках одного запроса страницы плагин не дёргает Memcached/Redis повторно, а отдаёт значение из памяти PHP. Параметр $force в wp_cache_get() позволяет обойти это поведение.

Параметр $group позволяет использовать один и тот же ключ в разном контексте — фактически это префикс ключа. Группы имеют значение при работе в режиме Multisite и с внешними хранилищами.

Группы ядра: default, posts, options, comment, themes, plugins, users.

В Multisite к каждому ключу добавляется префикс с ID сайта (аналог префиксов таблиц wp_3_posts, wp_4_posts). Глобальные группы — исключение: они общие для всей сети.

Классический пример — users: пользователи хранятся в одной глобальной таблице независимо от сайта. Другие глобальные группы ядра: themes, blog-details, site-options, site-transient.

Добавить свою глобальную группу:

wp_cache_add_global_groups( 'my-global-group' );

Постоянные группы — те, что пишутся во внешнее хранилище при наличии плагина. По умолчанию постоянны все группы; исключить группу можно так:

wp_cache_add_non_persistent_groups( 'my-group' );

Значения такой группы сохраняются только в локальном кэше PHP и не уходят в Redis/Memcached. Непостоянные группы ядра: comment, counts, themes, plugins.

При импорте большого объёма данных добавление каждого элемента в кэш — лишняя работа. Отключить добавление на время:

wp_suspend_cache_addition( true ); // отключить добавление в кэш
// ... импорт тысяч записей ...
wp_suspend_cache_addition( false ); // включить обратно

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

Опции имеют особое отношение с кэшем. При первом вызове get_option() ядро выполняет wp_load_alloptions() — одним запросом загружает все опции с флагом autoload в кэш.

Первый get_option() вызывается ядром ещё до загрузки темы. Поэтому такой код не выполняет ни одного запроса к базе:

echo get_option( 'blogdescription' );
if ( get_option( 'comments_open' ) ) {
printf( 'На сайте %s комментарии открыты', get_option( 'blogname' ) );
}
echo 'Свяжитесь с нами: ' . get_option( 'admin_email' );

Практический вывод: не бойтесь get_option(), bloginfo() и get_post_meta() (использует такой же подход). Советы «убрать обращения к get_option() для ускорения» — миф, основанный на непонимании кэша.

Следствие для архитектуры: опции с autoload = yes загружаются при каждом запросе. Не складируйте в autoload-опции большие массивы данных — это замедляет каждый запрос.

Статистику обращений к кэшу и потребление памяти по группам показывает плагин Debug Bar. При наличии внешнего кэша Debug Bar покажет каждое обращение к внешнему серверу.

Для глубокого изучения механизма смотрите исходник wp-includes/cache.php в ядре.