Свой REST-эндпоинт в WordPress: как кеш снижает нагрузку в 12 раз
Кастомный REST API без кеширования и оптимизации прав съедает до 300 мс на запрос. Разбираем, как правильно добавить кеш и проверку доступа, чтобы эндпоинт отдавал ответ за 20–30 мс.

Эндпоинт /wp-json/mysite/v1/stats на сайте с 50 000 записей и десятком плагинов начал отдавать ответ за 280–320 мс. Каждый запрос к нему запускал тяжёлую выборку из базы и проверку прав через WP_Query. После добавления кеша на уровне транзиентов и оптимизации permission_callback время ответа упало до 22–28 мс — в двенадцать раз.
Почему кастомные эндпоинты медленные по умолчанию
WordPress REST API не кеширует ответы автоматически. Каждый запрос к вашему эндпоинту:
- Загружает ядро WordPress целиком — инициализация, подключение к базе, загрузка активных плагинов.
- Выполняет
permission_callback— если там стоитcurrent_user_can('manage_options'), WordPress поднимает сессию пользователя и проверяет права. - Запускает вашу функцию обработчика — обычно там
WP_Query,get_posts()или прямой SQL. - Сериализует результат в JSON и отдаёт клиенту.
Для публичных данных, которые меняются раз в час или реже, это избыточно. Эндпоинт с агрегацией по тысяче записей будет выполнять тяжёлый запрос при каждом обращении, даже если результат не изменился.
Добавляем кеширование через транзиенты
Транзиенты — встроенный механизм временного хранения данных в WordPress. Они записываются в таблицу wp_options и автоматически удаляются по истечении срока.
add_action('rest_api_init', function () {
register_rest_route('mysite/v1', '/stats', [
'methods' => 'GET',
'callback' => 'mysite_get_stats',
'permission_callback' => '__return_true', // публичный эндпоинт
]);
});
function mysite_get_stats(WP_REST_Request $request) {
// Ключ кеша — уникальный для эндпоинта
$cache_key = 'mysite_stats_v1';
$cached = get_transient($cache_key);
if (false !== $cached) {
return new WP_REST_Response($cached, 200);
}
// Тяжёлая выборка — выполняется только при промахе кеша
$stats = [
'total_posts' => wp_count_posts('post')->publish,
'total_users' => count_users()['total_users'],
'recent_posts' => get_posts([
'numberposts' => 10,
'post_status' => 'publish',
'fields' => 'ids', // только ID, не весь объект
]),
];
// Кешируем на 1 час (3600 секунд)
set_transient($cache_key, $stats, 3600);
return new WP_REST_Response($stats, 200);
}
После первого запроса результат записывается в базу. Следующие запросы в течение часа отдают закешированный ответ без обращения к WP_Query и count_users().
Object Cache для высоконагруженных сайтов
Если на сервере установлен Redis или Memcached, WordPress может использовать их через wp_cache_* функции. Это быстрее транзиентов, потому что данные хранятся в памяти, а не в таблице базы.
function mysite_get_stats_cached(WP_REST_Request $request) {
$cache_key = 'mysite_stats_v1';
$group = 'mysite_api'; // группа для логического разделения
$cached = wp_cache_get($cache_key, $group);
if (false !== $cached) {
return new WP_REST_Response($cached, 200);
}
$stats = [ /* тяжёлая выборка */ ];
// Кешируем на 1 час
wp_cache_set($cache_key, $stats, $group, 3600);
return new WP_REST_Response($stats, 200);
}
Проверить, доступен ли объектный кеш, можно через wp_using_ext_object_cache(). Если функция возвращает false, wp_cache_* работают как алиасы к транзиентам.
| Подход | Где хранится | Скорость чтения | Требует настройки |
|---|---|---|---|
| Без кеша | — | 280–320 мс | Нет |
| Транзиенты | Таблица wp_options | 35–50 мс | Нет |
| Object Cache (Redis) | Память сервера | 20–30 мс | Да, нужен Redis и drop-in |
Оптимизация проверки прав
Если эндпоинт доступен только администраторам, проверка current_user_can('manage_options') в permission_callback добавляет 15–25 мс на каждый запрос — WordPress поднимает сессию и загружает роли пользователя.
Для публичных эндпоинтов ставьте 'permission_callback' => '__return_true' — это встроенная функция, которая всегда возвращает true, без проверки прав.
Для закрытых эндпоинтов с кешем проверяйте права один раз, а результат кешируйте отдельно для каждого пользователя:
function mysite_get_user_stats(WP_REST_Request $request) {
$user_id = get_current_user_id();
if (!$user_id) {
return new WP_Error('rest_forbidden', 'Авторизуйтесь', ['status' => 401]);
}
// Кеш уникален для каждого пользователя
$cache_key = "mysite_user_stats_{$user_id}";
$cached = get_transient($cache_key);
if (false !== $cached) {
return new WP_REST_Response($cached, 200);
}
// Данные конкретного пользователя
$stats = [
'posts_count' => count_user_posts($user_id, 'post'),
'comments_count' => get_comments(['user_id' => $user_id, 'count' => true]),
];
set_transient($cache_key, $stats, 1800); // 30 минут
return new WP_REST_Response($stats, 200);
}
Кеш на уровне пользователя решает две задачи: снимает нагрузку с базы и исключает утечку данных между пользователями — каждый видит только свой закешированный результат.
Как замерить разницу
WordPress не показывает время выполнения REST-запросов из коробки. Используйте плагин Query Monitor или замеряйте вручную:
function mysite_get_stats_measured(WP_REST_Request $request) {
$start = microtime(true);
// ваша логика с кешем
$response = mysite_get_stats($request);
$time = round((microtime(true) - $start) * 1000, 2);
error_log("REST /stats выполнен за {$time} мс");
return $response;
}
Логи попадут в wp-content/debug.log, если в wp-config.php включена отладка:
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);
Сделайте десять запросов к эндпоинту через curl или Postman, соберите медианное время. Сравните результат до и после добавления кеша. На сайте средней нагруженности разница будет от 5 до 15 раз.
Когда инвалидировать кеш
Транзиенты удаляются автоматически по истечении срока, но если данные изменились раньше — например, администратор опубликовал новую запись — кеш нужно сбросить вручную.
// Сбрасываем кеш статистики при публикации записи
add_action('publish_post', function ($post_id) {
delete_transient('mysite_stats_v1');
});
// Сбрасываем кеш пользователя при добавлении комментария
add_action('comment_post', function ($comment_id, $approved) {
$comment = get_comment($comment_id);
$cache_key = "mysite_user_stats_{$comment->user_id}";
delete_transient($cache_key);
}, 10, 2);
Для объектного кеша используйте wp_cache_delete($key, $group).
Где подход ломается
- Данные в реальном времени. Если эндпоинт отдаёт текущее количество товаров на складе или свободных мест на событие, кеш на час неприемлем — пользователь увидит устаревшую информацию. Сократите TTL до 1–5 минут или откажитесь от кеширования.
- Персонализация. Если ответ зависит от IP, геолокации или кастомных заголовков запроса, включите эти параметры в ключ кеша. Иначе первый пользователь из Москвы закеширует результат, а второй из Владивостока получит московские данные.
- Множественная инвалидация. Если на кеш влияют десятки разных событий (публикация поста, изменение настроек, обновление плагина), проще поставить короткий TTL — 5–10 минут — чем отлавливать все хуки.
- Размер кеша. Транзиенты хранятся в
wp_options, которая загружается целиком при инициализации WordPress на некоторых хостингах. Если вы кешируете массив из 10 000 элементов, это замедлит загрузку всего сайта. Для больших данных используйте Object Cache или храните результат в отдельной таблице.
Что мерить в продакшене
После внедрения кеша отслеживайте три метрики:
- Hit rate кеша — сколько запросов обслужено из кеша, а сколько попало в базу. Если hit rate ниже 80%, либо TTL слишком короткий, либо запросы с разными параметрами, и ключ кеша не учитывает их.
- Время ответа 95-го перцентиля — медиана скроет выбросы, а 95-й перцентиль покажет, как долго ждут самые медленные 5% пользователей. Если там всё ещё 200+ мс, проблема не только в отсутствии кеша.
- Размер таблицы
wp_options— если она выросла на сотни мегабайт после внедрения транзиентов, переходите на Object Cache или чистите устаревшие записи плагином вроде WP-Optimize.
Проверка рекламы на риски: Что в макете вызовет вопросы ФАС: превосходные степени, сравнения, обещания результата, маркировка.
Спросить за 40 ₽Источники: REST API Handbook — WordPress Developer Resources, Transients API — WordPress Code Reference
Частые вопросы
Можно ли использовать этот подход в WordPress 5.x?
Что делать, если после добавления кеша эндпоинт всё равно медленный?
Как понять, нужен ли мне Object Cache или достаточно транзиентов?
Можно ли кешировать POST-запросы?
Что будет, если забыть сбросить кеш при изменении данных?
Грозят ли проблемы с кешем при обновлении WordPress или плагинов?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.