Каждый запрос к WordPress — это обращение к базе данных, сборка шаблона, подключение плагинов. При высокой посещаемости сервер начинает тормозить. Кеширование позволяет сохранить результат тяжёлой операции и отдавать его повторно без лишних вычислений. WordPress предоставляет для этого два встроенных механизма: Transients API и Object Cache.
Transients API: кеширование с TTL
Transients — это временные данные с ограниченным сроком жизни. По умолчанию они хранятся в таблице wp_options. Три функции составляют весь API:
// Сохранение транзиента
set_transient( 'weather_data', $weather, 3600 ); // 3600 секунд = 1 час
// Получение транзиента
$weather = get_transient( 'weather_data' );
// Вернёт false, если транзиент истёк или не существует
// Удаление транзиента
delete_transient( 'weather_data' );
// Для сетевых транзиентов (мультисайт)
set_site_transient( 'global_stats', $stats, DAY_IN_SECONDS );
get_site_transient( 'global_stats' );
delete_site_transient( 'global_stats' );
WordPress предоставляет удобные константы для времени: MINUTE_IN_SECONDS (60), HOUR_IN_SECONDS (3600), DAY_IN_SECONDS (86400), WEEK_IN_SECONDS (604800).
Практический пример: кеширование внешнего API
Типичная задача — получить данные с внешнего API и показать на сайте. Без кеширования каждый посетитель будет ждать ответа от стороннего сервера. С кешированием запрос к API выполняется раз в час:
function get_github_repos( $username ) {
// Ключ кеша уникален для каждого пользователя
$cache_key = 'github_repos_' . sanitize_key( $username );
// Пробуем получить из кеша
$repos = get_transient( $cache_key );
if ( false !== $repos ) {
// Данные в кеше — возвращаем без запроса к API
return $repos;
}
// Кеш пуст или истёк — делаем запрос
$response = wp_remote_get(
"https://api.github.com/users/{$username}/repos?sort=updated&per_page=10",
array(
'timeout' => 10,
'headers' => array(
'Accept' => 'application/vnd.github.v3+json',
),
)
);
// Обработка ошибок
if ( is_wp_error( $response ) ) {
error_log( 'GitHub API error: ' . $response->get_error_message() );
// Пробуем отдать устаревший кеш (stale cache)
$stale = get_option( '_stale_' . $cache_key );
return $stale ?: array();
}
$code = wp_remote_retrieve_response_code( $response );
if ( 200 !== $code ) {
error_log( "GitHub API returned HTTP {$code}" );
return array();
}
$body = wp_remote_retrieve_body( $response );
$repos = json_decode( $body, true );
if ( ! is_array( $repos ) ) {
return array();
}
// Сохраняем в кеш на 2 часа
set_transient( $cache_key, $repos, 2 * HOUR_IN_SECONDS );
// Сохраняем резервную копию без TTL
update_option( '_stale_' . $cache_key, $repos, false );
return $repos;
}
// Использование в шаблоне
$repos = get_github_repos( 'developer' );
if ( $repos ) {
echo '<ul class="github-repos">';
foreach ( $repos as $repo ) {
printf(
'<li><a href="%s">%s</a> — %s (%d stars)</li>',
esc_url( $repo['html_url'] ),
esc_html( $repo['name'] ),
esc_html( $repo['description'] ?? '' ),
absint( $repo['stargazers_count'] )
);
}
echo '</ul>';
}
Паттерн «stale cache» — хранение устаревших данных как запасной вариант — полезен, когда внешний API недоступен. Лучше показать данные часовой давности, чем пустую страницу.
Кеширование тяжёлых WP_Query
Сложные запросы к базе данных — ещё один кандидат для кеширования. Популярные записи, статистика, навигация — всё это можно закешировать:
function get_popular_posts_cached( $count = 5 ) {
$cache_key = 'popular_posts_' . $count;
$posts = get_transient( $cache_key );
if ( false !== $posts ) {
return $posts;
}
$query = new WP_Query( array(
'posts_per_page' => $count,
'meta_key' => 'post_views_count',
'orderby' => 'meta_value_num',
'order' => 'DESC',
'post_status' => 'publish',
'no_found_rows' => true, // ускоряет запрос
'fields' => 'ids', // только ID
) );
$posts = array();
foreach ( $query->posts as $post_id ) {
$posts[] = array(
'id' => $post_id,
'title' => get_the_title( $post_id ),
'url' => get_permalink( $post_id ),
'thumbnail' => get_the_post_thumbnail_url( $post_id, 'thumbnail' ),
'views' => get_post_meta( $post_id, 'post_views_count', true ),
);
}
// Кешируем на 6 часов
set_transient( $cache_key, $posts, 6 * HOUR_IN_SECONDS );
return $posts;
}
// Сброс кеша при публикации новой записи
add_action( 'publish_post', 'clear_popular_posts_cache' );
add_action( 'delete_post', 'clear_popular_posts_cache' );
function clear_popular_posts_cache() {
delete_transient( 'popular_posts_5' );
delete_transient( 'popular_posts_10' );
}
Параметры no_found_rows и fields => 'ids' значительно ускоряют запрос: первый отключает подсчёт общего количества записей (не нужен для пагинации), второй возвращает только ID вместо полных объектов.
Object Cache: кеширование в памяти
Object Cache — это внутренний кеш WordPress, который по умолчанию живёт только в рамках одного HTTP-запроса. Данные хранятся в оперативной памяти PHP-процесса:
// Сохранение в object cache
wp_cache_set( 'my_data', $data, 'my_plugin', 3600 );
// Получение
$data = wp_cache_get( 'my_data', 'my_plugin' );
// Вернёт false, если ключ не найден
// Проверка с получением
$data = wp_cache_get( 'my_data', 'my_plugin', false, $found );
if ( ! $found ) {
// Ключа нет в кеше (отличает false-значение от отсутствия)
$data = expensive_computation();
wp_cache_set( 'my_data', $data, 'my_plugin', 3600 );
}
// Удаление
wp_cache_delete( 'my_data', 'my_plugin' );
// Добавление (не перезапишет существующий)
wp_cache_add( 'my_data', $data, 'my_plugin', 3600 );
// Инкремент / декремент
wp_cache_incr( 'page_views', 1, 'stats' );
wp_cache_decr( 'remaining_quota', 1, 'api' );
Второй параметр — группа. Она позволяет организовать кеш и избежать коллизий имён. Используйте имя плагина или темы как группу.
Persistent Object Cache: Redis и Memcached
По умолчанию Object Cache не переживает HTTP-запрос — это ограничивает его полезность. Но если подключить persistent object cache через Redis или Memcached, кеш сохраняется между запросами и разделяется между всеми процессами PHP:
// wp-config.php — настройка Redis
define( 'WP_REDIS_HOST', '127.0.0.1' );
define( 'WP_REDIS_PORT', 6379 );
define( 'WP_REDIS_DATABASE', 0 ); // номер БД Redis
define( 'WP_REDIS_PREFIX', 'wp_mysite_' ); // префикс ключей
// Для авторизации (если настроен пароль)
define( 'WP_REDIS_PASSWORD', 'your_redis_password' );
// Таймаут подключения
define( 'WP_REDIS_TIMEOUT', 1 );
define( 'WP_REDIS_READ_TIMEOUT', 1 );
Для подключения нужен drop-in файл wp-content/object-cache.php. Его устанавливают плагины:
- Redis Object Cache — для Redis. Устанавливает drop-in автоматически.
- Memcached Object Cache — для Memcached.
- LiteSpeed Cache — включает свой object cache.
После подключения persistent object cache все вызовы wp_cache_set/get и транзиенты начинают использовать Redis/Memcached вместо БД. Транзиенты перестают записываться в wp_options — это снижает нагрузку на MySQL.
Transients vs Object Cache: когда что использовать
Разберём разницу на конкретных сценариях:
// TRANSIENTS — когда нужен TTL и данные должны пережить запрос
// даже без Redis/Memcached (хранятся в wp_options)
// Результат внешнего API
set_transient( 'exchange_rates', $rates, HOUR_IN_SECONDS );
// Тяжёлый запрос к БД
set_transient( 'monthly_stats', $stats, DAY_IN_SECONDS );
// OBJECT CACHE — когда данные нужны только в текущем запросе
// или у вас есть persistent object cache
// Кеширование результата функции внутри одного запроса
function get_user_permissions( $user_id ) {
$cache_key = 'permissions_' . $user_id;
$perms = wp_cache_get( $cache_key, 'my_plugin' );
if ( false !== $perms ) {
return $perms;
}
// Тяжёлое вычисление
$perms = calculate_complex_permissions( $user_id );
wp_cache_set( $cache_key, $perms, 'my_plugin' );
return $perms;
}
// Кеширование результатов в цикле
function get_author_stats( $author_ids ) {
$stats = array();
foreach ( $author_ids as $id ) {
$cached = wp_cache_get( "author_stats_{$id}", 'stats' );
if ( false !== $cached ) {
$stats[ $id ] = $cached;
continue;
}
$data = fetch_author_data( $id );
wp_cache_set( "author_stats_{$id}", $data, 'stats', HOUR_IN_SECONDS );
$stats[ $id ] = $data;
}
return $stats;
}
Фрагментное кеширование HTML
Иногда полезно кешировать не данные, а готовый HTML-фрагмент. Например, сайдбар, меню навигации или блок «Популярные записи»:
function render_cached_sidebar() {
$cache_key = 'sidebar_html_' . get_locale();
$html = get_transient( $cache_key );
if ( false !== $html ) {
echo $html;
return;
}
ob_start();
// Здесь тяжёлый рендеринг
dynamic_sidebar( 'sidebar-main' );
$html = ob_get_clean();
set_transient( $cache_key, $html, 30 * MINUTE_IN_SECONDS );
echo $html;
}
// Сброс кеша сайдбара при обновлении виджетов
add_action( 'update_option_sidebars_widgets', function() {
delete_transient( 'sidebar_html_ru_RU' );
delete_transient( 'sidebar_html_en_US' );
} );
Стратегии инвалидации кеша
Кеширование бесполезно без правильной инвалидации. Основные подходы:
- TTL (Time To Live) — кеш автоматически истекает через заданное время. Самый простой подход, подходит для данных, которые не критично обновлять мгновенно.
- Event-based — сброс кеша при конкретном действии. Используйте хуки
save_post,delete_post,switch_theme,update_optionдля сброса связанного кеша. - Версионирование — вместо удаления кеша меняйте ключ. Например,
menu_v2вместоmenu_v1. Старый ключ истечёт по TTL.
// Event-based инвалидация
add_action( 'save_post', 'invalidate_related_caches', 10, 2 );
function invalidate_related_caches( $post_id, $post ) {
if ( 'publish' !== $post->post_status ) {
return;
}
// Сбрасываем кеш категорий этой записи
$categories = wp_get_post_categories( $post_id );
foreach ( $categories as $cat_id ) {
delete_transient( 'category_posts_' . $cat_id );
}
// Сбрасываем общие кеши
delete_transient( 'recent_posts' );
delete_transient( 'popular_posts_5' );
// Для object cache — очистка группы (если поддерживается)
if ( function_exists( 'wp_cache_flush_group' ) ) {
wp_cache_flush_group( 'my_plugin_posts' );
}
}
Мониторинг и отладка кеша
Чтобы понять, работает ли кеширование, используйте плагин Query Monitor. Он показывает количество SQL-запросов, время выполнения и попадания в object cache. Без кеширования типичная WordPress-страница делает 50-100 SQL-запросов. С правильным кешированием — 10-20.
Для Redis полезна команда redis-cli monitor — она показывает все операции в реальном времени. Команда redis-cli info memory покажет, сколько памяти занимает кеш.
Кеширование — один из самых эффективных способов ускорить WordPress без смены хостинга. Transients API доступен из коробки и не требует дополнительного ПО. Object Cache с Redis выводит производительность на новый уровень. Начните с кеширования самых тяжёлых запросов и внешних API-вызовов — результат будет заметен сразу.
