ГлавнаяРазноеТранзитное кэширование в высоконагруженных проектах WordPress

Транзитное кэширование в высоконагруженных проектах WordPress

Кэширование является неотъемлемой частью любого высоконагруженного проекта на WordPress. Ранее мы уже обсуждали основы кэширования и кэширование объектов в WordPress. В этой статье мы рассмотрим транзитное кэширование в WordPress и в частности библиотеку TLC Transients.

Транзитное кэширование в WordPress

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

Предположим следующую функцию:

function get_facebook_followers_count() {
    $result = wp_remote_get( 'https://graph.facebook.com/wpmag.ru' );
    $result = json_decode( wp_remote_retrieve_body( $result ) );
    return $result->likes;
}

echo "Количество лайков: " . get_facebook_followers_count();

Данная функция обращается к API Facebook, запрашивает объект (точнее страницу) wpmag.ru и возвращает количество лайков этой страницы. Время выполнения функции зависит от многих факторов, включая место положения вашего сервера относительно серверам Facebook, состояние и скорость сети и другое. В среднем функция может занимать 1-3 сек.

Это значит, что при использовании данной функции на сайте, время загрузки каждой страницы вырастет на 1-3 секунды. Более того, в случае вызова данной функции более чем 600 раз за 600 секунд, Facebook начнет выдавать ошибку вместо результата.

Пример транзитного кэширования

Чтобы ускорить эту функцию, можно воспользоваться транзитным кэшированием WordPress, и сохранить результат на 1 час:

function get_facebook_followers_count() {
    // Выдача из транзитного кэша
    $cached = get_transient( 'fb_followers' );
    if ( $cached !== false )
        return $cached;

    $result = wp_remote_get( 'https://graph.facebook.com/wpmag.ru' );
    $result = json_decode( wp_remote_retrieve_body( $result ) );
    $likes = $result->likes;

    // Запись в транзитный кэш на 1 час
    set_transient( 'fb_followers', $likes, 1 * HOUR_IN_SECONDS );

    return $likes;
}

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

С помощью такого подхода только один вызов этой функции в час увеличит время запроса на 1-3 секунды, а последующие вызовы будут выдаваться мгновенно.

Но для высоконагруженных проектов, к сожалению, даже такой вариант не подходит.

Транзитное кэширование в высоконагруженных проектах

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

В нашем примере обновление транзитного кэша занимает 1-3 секунды. При нагрузке в 50 запросов в секунду мы можем получить до 50-150 запросов, которые попытаются обновить этот же транзитный кэш.

Иными словами это 50-150 HTTP запросов на Facebook, и до 150 запросов с задержкой в 1-3 секунды (в лучшем случае). Для проекта подобного масштаба это катастрофа.

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

function get_facebook_followers_count() {
    // Выдача из транзитного кэша
    $cached = get_transient( 'fb_followers' )
    if ( $cached !== false )
        return $cached;

    // Установить блокировку
    $lock = my_acquire_lock( 'fb_followers' );
    if ( ! $lock )
        return 0;

    $result = wp_remote_get( 'https://graph.facebook.com/wpmag.ru' );
    $result = json_decode( wp_remote_retrieve_body( $result ) );
    $likes = $result->likes;

    // Запись в транзитный кэш на 1 час
    set_transient( 'fb_followers', $likes, 1 * HOUR_IN_SECONDS );

    // Снять блокировку
    my_release_lock( 'fb_followers' );
    return $likes;
}

Функции my_acquire_lock() и my_release_lock() являются псевдо-функциями, они здесь лишь для демонстрации. Механизм для блокировок может быть разным в зависимости от проекта, например сервер Memcached или Redis, GET_LOCK() в MySQL, или даже дополнительный транзит в WordPress.

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

Этот метод является далеко не идеальным по двум основным причинам:

  • Некоторые посетители смогут увидеть неточное количество лайков на протяжении 1-3 секунд
  • Первый запрос все равно добавит 1-3 секунды ко времени загрузки страницы

Можно конечно пойти дальше, записать последнее количество лайков в опцию и возвращать ее вместо нуля, а обновление опции можно вовсе сделать фоновым с помощью планировщика задач в WordPress. Но зачем изобретать колесо, когда есть библиотека TLC Transients.

Библиотека TLC Transients

TLC Transients — это библиотека, написана Марком Джейквитом, одним из ведущих разработчиков ядра WordPress. Она обеспечивает функционал схожий с обычным транзитным кэшем в WordPress, но имеет некоторые приятные дополнения:

  • При истечении срока кэша значение не удаляется
  • Обновление кэша происходит исключительно с помощью фонового запроса
  • Удобный синтаксис для получения и обновления кэша

Рассмотрим пример использования библиотеки TLC Transients на основе нашей задачи получить количество лайков страницы Facebook:

// Получить количество лайков страницы в Facebook
function get_facebook_followers_count() {
    $result = wp_remote_get( 'https://graph.facebook.com/wpmag.ru' );
    $result = json_decode( wp_remote_retrieve_body( $result ) );
    return $result->likes;
}

// Вывести количество лайков с помощью TLC Transients
echo tlc_transient( 'fb_followers' )
    ->updates_with( 'get_facebook_followers_count' )
    ->expires_in( 1 * HOUR_IN_SECONDS )
    ->background_only()
    ->get();

Функция get_facebook_followers_count обращается к Facebook API за данными, но в отличие от нашего первого примера, данную функцию мы никогда не вызываем напрямую. Вместо этого мы используем объект tlc_transient указав нашу функцию для обновления, время жизни кэша и принудительный фоновый режим.

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

Учтите, что функция передаваемая методу updates_with() должна существовать в основном контексте WordPress, так как процедура обновления выполняется асинхронно отдельно от текущего запроса.

Подробнее о проекте TLC Transients можно узнать на GitHub, где вы найдете исходный код библиотеки и несколько дополнительных примеров.

Константин Ковшенин

Сооснователь журнала WP Magazine и первой конференции WordCamp в России. Работал в Automattic, WordPress.com, WooCommerce. Принимает активное участие в развитии ядра WordPress. Любимый язык программирования: Python.

Подписаться на рассылку

Подписаться → Подпишитесь на бесплатную рассылку журнала WP Magazine и получайте новости, события, подборки тем и плагинов, уроки, советы и многое другое в мире WordPress!