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

2 дек. 2012 г., 23:23:26
Просмотры: 21.4K
Голосов: 24

Я ищу окончательный ответ на этот вопрос. Когда включено кэширование объектов, где в итоге хранятся опции и временные данные (transients)?

По умолчанию, и то и другое хранится в базе данных. Но я слышал упоминания о том, что Memcache хранит их где-то в другом месте, а APC делает что-то совершенно иное. Где именно эти данные будут сохраняться в обоих случаях?

1
Комментарии

Статья, на которую ссылается @toscho, теперь доступна на archive.org: Исследование WordPress Cache API

here here
11 нояб. 2015 г. 02:29:03
Все ответы на вопрос 4
6
37

WordPress по умолчанию использует форму "Кэширования объектов", но его время жизни ограничено одной загрузкой страницы.

Опции (options) - отличный пример этого. Посмотрите этот ответ для более подробной информации. Краткое содержание:

  1. Начинается загрузка страницы
  2. Все опции загружаются простым SQL-запросом SELECT option_name, option_value from $wpdb->options
  3. Последующие запросы этих опций (например, вызов get_option) не обращаются к базе данных, так как они хранятся в кэше WordPress.

Опции всегда "живут" в базе данных и сохраняются там — это их "канонический" источник. Тем не менее, опции загружаются в кэш объектов, поэтому при запросе опции с вероятностью 99% этот запрос не обратится к базе данных.

Транзиенты (transients) работают немного иначе.

WordPress позволяет заменить API кэширования с помощью дроп-ина — файла, который размещается непосредственно в папке wp-content. Если вы создадите собственный дроп-ин для кэша или воспользуетесь существующим плагином, вы можете сделать кэш объектов постоянным, а не только на время загрузки страницы. В этом случае поведение транзиентов изменится.

Давайте посмотрим на функцию set_transient в файле wp-includes/option.php.

<?php
/**
 * Устанавливает/обновляет значение транзиента.
 *
 * Вам не нужно сериализовать значения. Если значение требует сериализации, 
 * оно будет сериализовано перед сохранением.
 *
 * @since 2.8.0
 * @package WordPress
 * @subpackage Transient
 *
 * @uses apply_filters() Вызывает хук 'pre_set_transient_$transient', позволяющий переопределить
 *  значение транзиента перед сохранением.
 * @uses do_action() Вызывает хуки 'set_transient_$transient' и 'setted_transient' при успешном сохранении.
 *
 * @param string $transient Имя транзиента. Не должно быть экранировано для SQL.
 * @param mixed $value Значение транзиента. Не должно быть экранировано для SQL.
 * @param int $expiration Время жизни в секундах, по умолчанию 0
 * @return bool False, если значение не было сохранено, и true, если сохранение прошло успешно.
 */
function set_transient( $transient, $value, $expiration = 0 ) {
    global $_wp_using_ext_object_cache;

    $value = apply_filters( 'pre_set_transient_' . $transient, $value );

    if ( $_wp_using_ext_object_cache ) {
        $result = wp_cache_set( $transient, $value, 'transient', $expiration );
    } else {
        $transient_timeout = '_transient_timeout_' . $transient;
        $transient = '_transient_' . $transient;
        if ( false === get_option( $transient ) ) {
            $autoload = 'yes';
            if ( $expiration ) {
                $autoload = 'no';
                add_option( $transient_timeout, time() + $expiration, '', 'no' );
            }
            $result = add_option( $transient, $value, '', $autoload );
        } else {
            if ( $expiration )
                update_option( $transient_timeout, time() + $expiration );
            $result = update_option( $transient, $value );
        }
    }
    if ( $result ) {
        do_action( 'set_transient_' . $transient );
        do_action( 'setted_transient', $transient );
    }
    return $result;
}

Хм, $_wp_using_ext_object_cache? Если это значение true, WordPress использует кэш объектов вместо базы данных для хранения транзиентов. Но как оно становится true? Давайте разберемся, как WordPress настраивает свой API кэширования.

Почти все можно отследить до wp-load.php или wp-settings.php — оба файла критичны для процесса загрузки WordPress. В нашем случае важные строки находятся в wp-settings.php.

// Запускает кэш объектов WordPress или внешний кэш объектов, если присутствует дроп-ин.
wp_start_object_cache();

Помните про дроп-ин, о котором говорилось выше? Давайте посмотрим на функцию wp_start_object_cache в файле wp-includes/load.php.

<?php
/**
 * Запускает кэш объектов WordPress.
 *
 * Если в директории wp-content существует файл object-cache.php,
 * он используется как внешний кэш объектов.
 *
 * @access private
 * @since 3.0.0
 */
function wp_start_object_cache() {
    global $_wp_using_ext_object_cache, $blog_id;

    $first_init = false;
    if ( ! function_exists( 'wp_cache_init' ) ) {
        if ( file_exists( WP_CONTENT_DIR . '/object-cache.php' ) ) {
            require_once ( WP_CONTENT_DIR . '/object-cache.php' );
            $_wp_using_ext_object_cache = true;
        } else {
            require_once ( ABSPATH . WPINC . '/cache.php' );
            $_wp_using_ext_object_cache = false;
        }
        $first_init = true;
    } else if ( !$_wp_using_ext_object_cache && file_exists( WP_CONTENT_DIR . '/object-cache.php' ) ) {
        // Иногда advanced-cache.php может загрузить object-cache.php до того, как он будет загружен здесь.
        // Это нарушает проверку function_exists выше и может привести к некорректному значению $_wp_using_ext_object_cache.
        // Двойная проверка наличия внешнего кэша.
        $_wp_using_ext_object_cache = true;
    }

    // Если кэш поддерживает сброс, сбрасываем вместо инициализации, если уже инициализирован.
    // Сброс сигнализирует кэшу, что глобальные ID изменились, и возможно потребуется обновить ключи
    // и очистить кэши.
    if ( ! $first_init && function_exists( 'wp_cache_switch_to_blog' ) )
        wp_cache_switch_to_blog( $blog_id );
    else
        wp_cache_init();

    if ( function_exists( 'wp_cache_add_global_groups' ) ) {
        wp_cache_add_global_groups( array( 'users', 'userlogins', 'usermeta', 'user_meta', 'site-transient', 'site-options', 'site-lookup', 'blog-lookup', 'blog-details', 'rss', 'global-posts', 'blog-id-cache' ) );
        wp_cache_add_non_persistent_groups( array( 'comment', 'counts', 'plugins' ) );
    }
}

Релевантные строки функции (те, которые относятся к $_wp_using_ext_object_cache и изменяют способ хранения транзиентов):

if ( file_exists( WP_CONTENT_DIR . '/object-cache.php' ) ) {
    require_once ( WP_CONTENT_DIR . '/object-cache.php' );
    $_wp_using_ext_object_cache = true;
} else {
    require_once ( ABSPATH . WPINC . '/cache.php' );
    $_wp_using_ext_object_cache = false;
}

Если object-cache.php существует в вашей директории контента, он подключается, и WordPress предполагает, что вы используете внешний, постоянный кэш — устанавливает $_wp_using_ext_object_cache в true.

Если вы используете внешний кэш объектов, транзиенты будут использовать его. Это поднимает вопрос: когда использовать опции, а когда транзиенты?

Все просто. Если данные должны храниться неограниченное время, используйте опции. Они кэшируются, но их канонический источник — база данных, и они никогда не исчезнут, пока пользователь явно не удалит их.

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

Другие вопросы и замечания:

  1. Можно ли делать много вызовов get_option? Да, скорее всего. Они создают нагрузку на вызов функции, но вряд ли обратятся к базе данных. Нагрузка на базу данных часто является более серьезной проблемой для масштабируемости веб-приложений, чем работа языка программирования при генерации страницы.
  2. Как понять, когда использовать транзиенты, а когда API кэширования? Если данные должны храниться определенное время, используйте API транзиентов. Если неважно, сохранятся ли данные (например, их получение/вычисление занимает немного времени, но не должно выполняться чаще одного раза за загрузку страницы), используйте API кэширования.
  3. Все ли опции действительно кэшируются при каждой загрузке страницы? Не обязательно. Если вы вызываете add_option с последним необязательным аргументом no, они не будут автозагружаться. Однако после первого запроса они попадут в кэш, и последующие вызовы не обратятся к базе данных.
3 дек. 2012 г. 07:19:47
Комментарии

придирка 1: Не все опции загружаются при старте страницы, а только те, которые помечены как "autoload=yes" при создании. Значение по умолчанию для этого параметра в add_option — 'yes', и большинство разработчиков плагинов не заморачиваются с пониманием разницы при использовании 'no', что делает ваше утверждение практически верным.

Mark Kaplun Mark Kaplun
3 дек. 2012 г. 07:32:29

Даже не автозагружаемые опции кэшируются после первого их получения. Они могут не загружаться изначально, но после этого попадают в кэш объектов. Даже несуществующие опции кэшируются! https://github.com/WordPress/WordPress/blob/master/wp-includes/option.php#L58-L71 Я добавил примечание о параметре autoload.

chrisguitarguy chrisguitarguy
3 дек. 2012 г. 07:37:21

это была придирка 2 ;)

Mark Kaplun Mark Kaplun
3 дек. 2012 г. 07:42:31

Спасибо за отличную статью и за время, потраченное на её обобщение.

prosti prosti
15 дек. 2016 г. 15:54:31

Какая блестящая и полезная информация, спасибо!

And Finally And Finally
11 июн. 2020 г. 11:42:04

@chrisguitarguy: Что произойдет при изменении $_wp_using_ext_object_cache во время выполнения, например, с помощью wp_using_ext_object_cache(false)? Будет ли временно отключен кэш для последующих вызовов БД/кэша на время загрузки этой страницы?

tim tim
5 янв. 2022 г. 19:15:14
Показать остальные 1 комментариев
7

Известны 4 типа кэширования:

  1. Простое (Trivial) - Всегда активно и работает до применения других видов кэширования. Хранит кэшированные элементы в массиве PHP, что означает использование памяти в рамках сессии выполнения PHP. Кэш очищается после завершения выполнения PHP. Например, даже без использования других видов кэширования, если вызвать get_option('opt') дважды подряд, запрос к базе данных произойдет только первый раз, а второй раз значение будет взято из памяти.

  2. Файловое - Кэшированные значения хранятся в файлах где-то в корневой директории. Считается, что этот метод не очень эффективен с точки зрения производительности, если только у вас нет очень быстрого диска или хранилища с отображением файлов в память.

  3. APC (или другое кэширование на основе PHP-акселератора) - Кэшированные значения хранятся в памяти сервера за пределами выделенной памяти PHP. Основной подводный камень — отсутствие разграничения данных: если у вас два сайта, каждый может получить доступ к кэшированным данным другого или перезаписать их.

  4. Memcache - Сетевой кэш. Служба кэширования может работать где угодно в сети, и значения, скорее всего, хранятся в памяти сервера. Memcache обычно нужен только при использовании балансировки нагрузки.

Кстати, объектное кэширование охватывает гораздо больше, чем просто настройки — оно сохраняет почти все, что было получено из базы данных через высокоуровневый API WordPress.

3 дек. 2012 г. 07:13:52
Комментарии

Знаю, что ответ довольно старый, но я бы также добавил отличный Redis.

Cranio Cranio
12 апр. 2016 г. 13:32:27

@Cranio, ты прав, но... Redis по сути является вариантом memcache с хранением данных, и поэтому это (NoSQL) база данных. На мой взгляд, это скорее недостаток, так как при отказе узла или невозможности его обновления можно получить устаревшую информацию. У него есть опция отключения режима базы данных, но я не уверен, включена она по умолчанию или нет.

Mark Kaplun Mark Kaplun
12 апр. 2016 г. 13:42:19

Это идеальная замена для Memcached (даже лучше), чего ещё нужно? Наиболее распространённый вариант использования, который я видел — это просто хранилище ключ-значение в оперативной памяти (да, помимо этого данные можно сделать персистентными, кластеризация в разработке, есть возможности управления очередями, но все добавляют Redis как отличный вариант кэширования для WP)

Cranio Cranio
12 апр. 2016 г. 15:45:58

каждый может также прыгнуть с моста ;) но дополнительная сложность абсолютно не нужна для кеширования

Mark Kaplun Mark Kaplun
12 апр. 2016 г. 16:04:28

Это совершенно бессмысленно; вам нужно кеширование в оперативной памяти, Redis делает кеширование в оперативной памяти, точка; и делает это прекрасно. Здесь абсолютно нет дополнительной сложности, если вы не хотите углубляться в это. Так что, сэр, я действительно не понимаю вашу точку зрения.

Cranio Cranio
13 апр. 2016 г. 13:41:08

ЛОЛ, если вам нужно 5 строк кода для реализации X, а я предлагаю вам эти 5 строк плюс дополнительные 10, то по определению это имеет более высокую сложность. Нет причины менять технологии только из-за хайпа. Если вы сможете показать, что Redis лучше в любом аспекте, связанном с кешированием объектов, тогда, возможно, у вас будет аргумент, но сейчас его нет.

Mark Kaplun Mark Kaplun
13 апр. 2016 г. 13:53:11

Это опять же совершенно бессмысленно в попытке НЕ включать Redis в список. По, я полагаю, сугубо личным причинам, которые полностью противоречат цели StackOverflow. Речь не о детской религиозной войне о том, что лучше, а о демонстрации доступных вариантов и технологий. Что касается остального: http://stackoverflow.com/questions/10558465/memcached-vs-redis

Cranio Cranio
13 апр. 2016 г. 13:56:13
Показать остальные 2 комментариев
0

Отличный вопрос.

Думаю, часть о том, как WordPress использует класс WP_Object_Cache, всё ещё отсутствует, поэтому я её добавлю.

Из документации:

ОПРЕДЕЛЕНИЕ: WordPress Object Cache используется для уменьшения количества обращений к базе данных. Объектный кеш сохраняет все данные кеша в памяти и делает их доступными с помощью ключа, который используется для именования и последующего извлечения содержимого кеша.

Вот структура WP_Object_Cache.

Структура WP_Object_Cache в WordPress

Примечание: + означает public, - private, # protected.

Вы можете использовать метод stats() для отображения общей статистики о глобальном объекте кеша и его содержимом. Вот пример вывода:

Cache Hits: 110
Cache Misses: 98

Group: options - ( 81.03k )
Group: default - ( 0.03k )
Group: users - ( 0.41k )
Group: userlogins - ( 0.03k )
Group: useremail - ( 0.04k )
Group: userslugs - ( 0.03k )
Group: user_meta - ( 3.92k )
Group: posts - ( 1.99k )
Group: terms - ( 1.76k )
Group: post_tag_relationships - ( 0.04k )
Group: category_relationships - ( 0.03k )
Group: post_format_relationships - ( 0.02k )
Group: post_meta - ( 0.36k )

Это то, что я получил в самом начале шаблона, например, single.php.

Обратите внимание на переменную, которая нас интересует: global $wp_object_cache.

Приватный член $cache содержит сами данные кеширования.

В программировании структуры кеша встречаются повсеместно. В простейшей форме их можно представить как пары "ключ-значение". Бакеты, NoDB-структуры, индексы базы данных. Конечная цель WordPress Object Cache заключалась не в создании максимально простой структуры, но всё равно можно заметить пары "ключ-значение".

Поскольку я находился в single.php, когда выводил кеш:

print_r($wp_object_cache->cache['posts']);

Я получал закешированный одиночный пост.

    [last_changed] => 0.34169600 1481802075
    [get_page_by_path:2516f01e446b6c125493ec7824b63868:0.34169600 1481802075] => 0
    [2831] => WP_Post Object
        (
            [ID] => 2831
            [post_author] => 1 
            ... здесь идёт закешированный объект поста
        )

Объект будет значением, а ключ кеширования будет таким:

get_page_by_path:2516f01e446b6c125493ec7824b63868:0.34169600 1481802075

Здесь вы можете проверить структуру $cache_key:

Файл: /wp-includes/post.php
4210: /**
4211:  * Получает страницу по её пути.
4212:  *
4213:  * @since 2.1.0
4214:  *
4215:  * @global wpdb $wpdb WordPress database abstraction object.
4216:  *
4217:  * @param string       $page_path Путь страницы.
4218:  * @param string       $output    Опционально. Требуемый тип возвращаемого значения. Один из OBJECT, ARRAY_A или ARRAY_N, что соответствует
4219:  *                                объекту WP_Post, ассоциативному массиву или числовому массиву соответственно. По умолчанию OBJECT.
4220:  * @param string|array $post_type Опционально. Тип записи или массив типов записей. По умолчанию 'page'.
4221:  * @return WP_Post|array|null WP_Post (или массив) при успехе, или null при неудаче.
4222:  */
4223: function get_page_by_path( $page_path, $output = OBJECT, $post_type = 'page' ) {
4224:   global $wpdb;
4225: 
4226:   $last_changed = wp_cache_get_last_changed( 'posts' );
4227: 
4228:   $hash = md5( $page_path . serialize( $post_type ) );
4229:   $cache_key = "get_page_by_path:$hash:$last_changed";
4230:   $cached = wp_cache_get( $cache_key, 'posts' );
4231:   if ( false !== $cached ) {
4232:       // Особый случай: '0' - это некорректный `$page_path`.
4233:       if ( '0' === $cached || 0 === $cached ) {
4234:           return;
4235:       } else {
4236:           return get_post( $cached, $output );
4237:       }
4238:   }
15 дек. 2016 г. 15:53:34
0

Опции всегда хранятся в базе данных, в то время как транзиенты (transients) могут храниться только в общей памяти, если установлены APC и плагин, реализующий кэширование APC в WordPress. Memcache также использует память.

Опции также хранятся в памяти и загружаются оттуда, когда это возможно (если нет, выполняется запрос к базе данных).

3 дек. 2012 г. 06:54:53