WordPress Transients: A Technical Guide to a Powerful API

Ryan Kanner (@CodeProKid)

slides.com/codeprokid/transients

Me.

  • WordPress Developer for 8+ years
  • Transplant from the east coast
  • Work for a company called Digital First Media, working on websites for newspapers such as the Denver Post, Orange County Register, and Mercury News.

What I'll be talking about & why I'm talking about it

  • What: The ins and outs of the Transients API.
  • What: A behind the scenes look at what's going on
  • What: Practical samples of how to leverage the API
  • Why: We use transients heavily, and I wanted to know what made them tick.
  • Why: Transients are sometimes misunderstood.

The Basics

What the heck are transients?

  • Transients provide a simple way to cache data on your server and have it expire at defined intervals.
  • All transients have the following components
    • Key - A Unique identifier to find, update, or delete your transient.
    • Value - The data you want to cache. It could be a string, array, or object. Essentially anything you can store in an option.
    • Expiration - The maximum amount of time your data should be valid for. 

... sooooo, what are transients?

http://i.imgur.com/EAvvfw2.gifv

What are transients

  • Caching is a necessity in the modern web
  • We have browser caching for assets like images, js, and css
  • We have transients for caching data for things like remote requests, complex queries, and data that doesn't change often.

How they work

  • They are essentially a key value store, with the added benifit of having a max-age.
  • When you go to retrieve the data, it does a check to see if it's passed it's max-age. If it has, it deletes the data, and will return false.
  • By default your site will use the database as it's storage engine, but if you are using an object cache (like memcache) it will skip the database, and only be stored in the cache.

With Object Cache

Without Object Cache

What they look like

if ( false === ( $data = get_transient( 'my_transient' ) ) ) {

    $data = 'some stuff';

    set_transient( 'my_transient', $data, HOUR_IN_SECONDS );

}

echo $data;

In The Database

The API

Overview

  • The API mainly consists of 3 functions...
    • set_transient
    • get_transient
    • delete_transient
  • Multisite has companion functions that essentially work the same. They are: set_site_transient(), get_site_transient(), and delete_site_transient().
  • When using an object cache, the functions mainly rely on wp_cache_* functions.
  • Without an object cache the functions mainly rely on *_option functions.
  • $transient(string) - Unique name to use when saving the transient. Becomes the "Key" in the key => value.
  • $value(mixed) - Data to be stored in the the transient. Can be an array, string, object or an integer.
  • $expiration(int) - Optional integer passed to set max age. Adds _transient_timeout_$transient setting to options table, or uses built in object cache expiration.
  • @return(bool) - Returns true if successful, false if unsuccessful.
set_transient( $transient, $value, $expiration );
set_transient Hooks & Filters
  • pre_set_transient_$transient - exposes the value to be stored to the transient to a filter. Expiration and transient name are passed as context.
  • expiration_of_transient_$transient - exposes the expiration time to a filter. Transient name and value passed as context
  • set_transient_$transient - action that fires after a specific transient has been saved. Transient name, Value, and expiration passed as context.
  • setted_transient - action that fires after any transient is saved. Transient name, Value, and expiration passed as context.
  • $transient(string) - Name of the transient to retrieve. Will use get_option() to retrieve a transient saved in the database. Will use wp_cache_get() if saved in the object cache.
  • This function does a check to see if the transient data has expired. If it has, it will delete the data from the database.
  • @return (bool|mixed) - If the transient is expired, or it doesn't exist, it will return false. Otherwise it will return the data stored in the transient.
get_transient( $transient );
get_transient Hooks & Filters
  • pre_transient_$transient - By default this value is set to false. This can be set to true via this filter to short circuit the process of returning a transients value. Transient name is passed as context.
  • transient_$transient - exposes value of the transient being returned to a filter. Transient name and value passed as context.
  • $transient(string) - Name of the transient to delete. Will use delete_option() to delete a transient saved in the database. Will use wp_cache_delete() if saved in the object cache.
  • This function will also delete the timeout (expiration) value from the database.
  • @return (bool) - Returns true if deletion is successful, false if it isn't.
delete_transient( $transient );
delete_transient Hooks & Filters
  • delete_transient_$transient - action that fires before a transient is actually deleted. Transient name is passed as context.
  • deleted_transient - action that fires after any transient is deleted. Transient name is passed as context.

Implementations

External API call


if ( false === ( $photos = get_transient( 'instagram_photos' ) ) ) {

    $url = add_query_arg( 
        array(
            'access_token' => 'xxx-xxx-xxxx',
            'count' => 30,
        ),
        'https://api.instagram.com/v1/users/1234/media/recent/'
    );

    $response = wp_remote_get( esc_url( $url ), array( 'timeout' => 600 ) );

    $photos = $response['body'];

    set_transient( 'instagram_photos', $photos, 3 * HOUR_IN_SECONDS );

}

print_r( $photos );

WP Query

if ( false === ( $query = get_transient( 'query_transient' ) ) {
    $args = array(
        'post_type' => 'post',
        'post_status' => 'publish',
        'posts_per_page' => 100,
        'meta_query' => array(
            'AND',
            array(
                'key' => 'key_1',
                'value' => 'value_1',
            ),
            array(
                'key' => 'key_2',
                'value' => 'value_2',
            ),
        ),
    );
    $query = new WP_Query( $args );
    set_transient( 'query_transient', $query, 0 );
}

echo '<ul>';
if ( $query->have_posts() ) : while ( $query->have_posts() ) : $query->the_post();
    echo '<li>' . get_the_title() . '</li>';
endwhile;
endif;
echo '</ul>';

Dynamic Invalidation


function transient_flusher( $meta_id, $object_id, $meta_key ) {

    if ( 'key_1' === $meta_key || 'key_2' === $meta_key ) {
        delete_transient( 'query_transient' );
    }

}

add_action( 'updated_post_meta', 'transient_flusher', 10, 3 );

Shortfalls, quirks, and best practices

Transients can be kinda....

Shortfalls to the API

  • There's no soft expiration. Data is immediately deleted and regenerated if expired.
  • If you have multiple transients on a page that expire at the same time, the page can take a while to load for the user that triggers the cache rebuild. A work around to this is to use something like this for your expiration: MINUTE_IN_SECONDS + rand( 0, 60 )
  • There's no update locking, so if you run a high traffic sites, there's a chance you could run into timeout issues if you are storing data that takes a long time to rebuild.

no no's

  • Caching paginated archives is not the best idea. You would have to cache each page, and could quickly get unruly.
  • Caching a query, or other information that is already in the database might not make sense for you. It only makes sense if the query is really complicated, or you are dealing with a large dataset.
  • Caching menus gets really funky. A lot of the classes are based on the page context like "current-page". Your menu will not have these classes if you store it in a transient.
  • Never assume the data stored in a transient is actually there.

Garbage Collection

  • There is no garbage collection, so if a transient is invalid, but hasn't been called with get_transient it will remain in the DB until it's called, or manually deleted.
  • This often happens if you rename a transient. The old transient will remain in the DB (even after it has expired), since it won't be called anymore.
  • WP-CLI has some nice commands to delete expired transients from the database.
  • If you are using an object cache, orphaned transients aren't that big of a deal since the cache will just clear them out to make space for other transients that are actually being used.

Autoloading

  • Transients that don't have an expiration will be autoloaded from the databases with the rest of the options set to be autoloaded.
  • This can become an issue if you have a lot of orphaned transients in the DB without an expiration and large values.

Transient Helpers

DFM-Transients to the rescue!

http://dlopez1986.blogspot.com/2012/09/trying-to-make-animated-gifs.html

About the plugin

  • Built out of frustration with the shortcomings of the transients API.
  • A good solution for high traffic sites utilizing the transients API, but looking for a larger feature set.
  • Supports multiple caching engines.
  • Asynchronous data processing a main focus
  • Provides admin UI for tracking cached data
  • Find it here -> https://github.com/dfmedia/DFM-Transients

Basic Example

// Register the transient
function register_sample_transient() {

  $transient_args = array(
    'cache_type' => 'transient',
    'callback' => 'transient_callback',
    'expiration' => DAY_IN_SECONDS,
    'soft_expiration' => true,
    'async_updates' => true,
    'update_hooks' => array(
        'updated_post_meta' => 'transient_meta_update_cb',
    ),
  );

   dfm_register_transient( 'sample_transient', $transient_args );

}

add_action( 'after_setup_theme', 'register_sample_transient' );

// Callback to populate the data to store in the transient
function transient_callback( $modifier ) {
  $args = array(
    'post_type' => 'post',
    'post_status' => 'publish',
    'posts_per_page' => 5,
    'tax_query' => array(
      array(
        'taxonomy' => 'category',
        'terms' => $modifier,
      ),
    ),
  );
  $posts = new WP_Query( $args );
  return $posts;
}

// Callback to decide if we should actually regenerate the data on this hook
function transient_meta_update_cb( $args ) {
  // Matches $meta_key value (3rd arg passed to hook)
  if ( 'my_meta_key' === $args[2] ) {
    // Returns post ID
    return $args[1]
  } else {
    // If this callback returns false, we will not regenerate the transient data.
    return false;
}

Wrapping it up

 http://gph.is/1QW9HqB

Takeaways

  • Leveraging transients is a great way to cache the results for complex queries or remote requests, and speed up your site.
  • There's almost no reason not to use transients to cache remote API requests.
  • Transients have some quirks, but there are ways to get around them.
  • Using an object cache will immediately super charge your transients.
  • Never store anything in a transient that you can't recreate on demand.
  • Never assume your transient data is actually there.

Sources

Questions?

WordPress Transients: A Technical Guide to a Powerful API

By Ryan Kanner

WordPress Transients: A Technical Guide to a Powerful API

  • 4,661