How does object caching work?

I’m looking for a definitive answer here. When object caching is enabled, where do options and transients end up living?

By default, both are stored in the database. But I’ve heard some references that memcache will store them elsewhere and APC will do something else entirely. Where, exactly, will this data be persisted in both cases?

Solutions Collecting From Web of "How does object caching work?"

WordPress, by default, does a form of “Object Caching” but its lifetime is only a single page load.

Options are actually a really good example of this. Check out this answer for more info. The summary:

  1. A page starts
  2. All options are loaded with a simple SELECT option_name, option_value from $wpdb->options statement
  3. Subsequent requests for those options (eg a call to get_option never hit the database because they are stored with the WP cache API.

Options always “live” in the database and are always persisted there — that’s their “canonical” source. That said, options are loaded into the object cache so when you request an option there’s a 99% chance that request will never hit the database.

Transients are a bit different.

WordPress allows you to replace the cache api with a drop-in — a file that gets placed directly in your wp-content folder. If you create your own cache drop in or use an existing plugin, you can make the object cache persist longer than a single page load. When you do that, transients, change a bit.

Let’s take a look at the set_transient function in wp-includes/option.php.

 * Set/update the value of a transient.
 * You do not need to serialize values. If the value needs to be serialized, then
 * it will be serialized before it is set.
 * @since 2.8.0
 * @package WordPress
 * @subpackage Transient
 * @uses apply_filters() Calls 'pre_set_transient_$transient' hook to allow overwriting the
 *  transient value to be stored.
 * @uses do_action() Calls 'set_transient_$transient' and 'setted_transient' hooks on success.
 * @param string $transient Transient name. Expected to not be SQL-escaped.
 * @param mixed $value Transient value. Expected to not be SQL-escaped.
 * @param int $expiration Time until expiration in seconds, default 0
 * @return bool False if value was not set and true if value was set.
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;

Hmmm $_wp_using_ext_object_cache? If it’s true, WordPress uses the object cache instead of the database to store transients. So how does that get set to true? Time to explore how WP sets up its own cache API.

You can trace almost everything to wp-load.php or wp-settings.php — both of which are crucial to the bootstrap process of WordPress. In our cache, there are some relevant lines in wp-settings.php.

// Start the WordPress object cache, or an external object cache if the drop-in is present.

Remember that drop in thing from above? Let’s take a look at wp_start_object_cache in wp-includes/load.php.

 * Starts the WordPress object cache.
 * If an object-cache.php file exists in the wp-content directory,
 * it uses that drop-in as an external object cache.
 * @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' ) ) {
        // Sometimes advanced-cache.php can load object-cache.php before it is loaded here.
        // This breaks the function_exists check above and can result in $_wp_using_ext_object_cache
        // being set incorrectly. Double check if an external cache exists.
        $_wp_using_ext_object_cache = true;

    // If cache supports reset, reset instead of init if already initialized.
    // Reset signals to the cache that global IDs have changed and it may need to update keys
    // and cleanup caches.
    if ( ! $first_init && function_exists( 'wp_cache_switch_to_blog' ) )
        wp_cache_switch_to_blog( $blog_id );

    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' ) );

The relevant lines of the function (the ones that pertain to $_wp_using_ext_object_cache that alters how transients are stored).

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;

if object-cache.php exist in your content directory it gets included and WP assumes you’re using an external, persistent cache — it sets $_wp_using_ext_object_cache to true.

If you’re using an external object cache transients will use it. Which brings up the question of when to use options vs. transients.

Simple. If you need data to persist indefinitely, use options. They get “cached”, but their canonical sources is the database and they will never go away unless a user explicitly requests it.

For data that should be stored for a set amount of time, but does not need to persist beyond a specified lifetime use transients. Internally, WP will try to use an external, persistent object cache if it can otherwise data will go into the options table and get garbage collected via WordPress’ psuedo-cron when they expire.

Some other concerns/questions:

  1. Is it okay to do a ton of calls to get_option? Probably. They incur the call to a function overhead, but it likely won’t hit the database. Database load is often a bigger concern in web application scalability than the work your language of choice does generating a page.
  2. How do I know to use transients vs. the Cache API? If you expect data to persist for a set period, use the transient API. If it doesn’t matter if data persists (eg. it doesn’t take long to compute/fetch the data, but it shouldn’t happen more than once per page load) use the cache API.
  3. Are all options really cached on every pageload? Not necessarily. If you call add_option with its last, optional argument as no they are not autoloaded. That said, once you fetch them once, they go into the cache and subsequent calls won’t hit the database.

there are 4 cache types that I know of

  1. Trivial – It is always on and takes affect before any other caching comes into play. It stores the cached items in an php array which means that it consumes memory from your php execution session, and that the cache is emptied after php execution is over. i.e. even without using any other cache if you call get_option(‘opt’) twice in a row you will make a DB query only the first time and the second time the value will be returned from memory.

  2. File – Cached values are stored in files somewhere under your root directory. I believe it proved to be not effective in terms of performance unless you have a very fast disk or memory mapped file storage.

  3. APC (or other php accelerator based caching) – Cached values are stored in the memory of your host machine and outside of your php memory allocation. The biggest potential pitiful is that there is no scoping of data and if you run two sites potentially each can access the cached data of the other, or overwrite it.

  4. Memcahce – it is a network based cache. You can run the caching service anywhere on the network and It probably stores values in its host memory. You probably don’t need memcache unless you have a load balancing in action.

BTW, object caching is caching much more then options, it will store almost anything that was retrieved from the DB using high level WP API.

Options are always stored in the database, while transients may be stored only in shared memory if APC and a plugin that implements APC caching in WP is installed. Memcache uses memory as well.

Options are also stored in memory, and loaded from there when possible (if not, a db query is performed).

Great question.

I think the part with how WordPress uses WP_Object_Cache class is still missing, so I will add that.

From the docs:

DEF: The WordPress Object Cache is used to save on trips to the database. The Object Cache stores all of the cache data to memory and makes the cache contents available by using a key, which is used to name and later retrieve the cache contents.

Here is the WP_Object_Cache structure.

enter image description here

Note + is public, – private, # protected.

You use the stats() method to show general statistics about the global cache object and what is in there. Here is the output:

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 )

This is what I got before at the very beginning of a template such as single.php.

Note the variable we are interested is:
global $wp_object_cache.

The private the member $cache holds the actual caching data.

In programming, cache structures are everywhere. In a simple form they can be recognized as a key value pair. Buckets, NoDB structures, database indexes. WordPress Object Cache ultimate goal was not to have the simplest possible structure, but still key value pairs can be recognized.

Since I was in single.php when I printed the cache:


I am getting a single post cached.

    [last_changed] => 0.34169600 1481802075
    [get_page_by_path:2516f01e446b6c125493ec7824b63868:0.34169600 1481802075] => 0
    [2831] => WP_Post Object
            [ID] => 2831
            [post_author] => 1 
            ... the cached post object goes here

The object would be the value, and the caching key would be

get_page_by_path:2516f01e446b6c125493ec7824b63868:0.34169600 1481802075

In here you may check the $cache_key structure:

File: /wp-includes/post.php
4210: /**
4211:  * Retrieves a page given its path.
4212:  *
4213:  * @since 2.1.0
4214:  *
4215:  * @global wpdb $wpdb WordPress database abstraction object.
4216:  *
4217:  * @param string       $page_path Page path.
4218:  * @param string       $output    Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
4219:  *                                a WP_Post object, an associative array, or a numeric array, respectively. Default OBJECT.
4220:  * @param string|array $post_type Optional. Post type or array of post types. Default 'page'.
4221:  * @return WP_Post|array|null WP_Post (or array) on success, or null on failure.
4222:  */
4223: function get_page_by_path( $page_path, $output = OBJECT, $post_type = 'page' ) {
4224:   global $wpdb;
4226:   $last_changed = wp_cache_get_last_changed( 'posts' );
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:       // Special case: '0' is a bad `$page_path`.
4233:       if ( '0' === $cached || 0 === $cached ) {
4234:           return;
4235:       } else {
4236:           return get_post( $cached, $output );
4237:       }
4238:   }