Intro to advanced caching in WordPress

Preview:

DESCRIPTION

Presented at WordCamp Jerusalem 2013 (Feb. 20, 2013)

Citation preview

Advanced CachingAn Introduction to

methods in WordPress

Maor Chasen

WP Developer @ illumineaCore contributor for 3.5Have plugins on WordPress.orgOpen Source FanaticDesign EnthusiastFlying kites

It's not scary.

It's fun.

1. It's easy2. Your site = faster3. Can save you $$4. It will knock

your socks off5. You'll sleep better

at night.

Why caching is good for you?A question you shouldn't ask your mom

What we will talk about● What is fragment / object caching

● Why caching is important

● How to cache

● What to cache

● When to cache

What we won't talk about● Server setups

● Caching plugins

Transients APIfirst things first

Transients are● temporary bits of data (usually raw data)

● like options, but w/ expiration time

● always persistent out of the box

● stored in wp_options by default

● stored in memory if a caching backend (APC,

Memcache, XCache, etc.) is available

● available since 2.8

● fetching data from a remote source

● performing an expensive* query or request

○ meta_query

○ tax_query

● data persistence is absolutely required**

Use Transients when

* operations that require high CPU usage or high latency (Identify slow

queries with Debug Bar)

** ex. - when polling data from external sources

Transients functionsat your disposal

read core?wp-includes/option.php

get_transient( $transient );

Get it.

unique string (key) representing a piece of data

set_transient( $transient,$value,$expiration = 0

);

Set it.the data you wish to store

for how long?

delete_transient( $transient );

Scrap it.

unique string (key) representing a piece of data

get_site_transient();

set_site_transient();

delete_site_transient();

Multisite?You're in luck! All Transients functions have their network-wide counterparts.

Use case:Using Transients to store

fragmented data

wp_nav_menu( array( 'theme_location' => 'primary',

) );

<ul id="menu-primary" class="menu">

<li id="menu-item-10" class="menu-item">

<a href="...">...</a>

</li>

...

</ul>

Without usingwp_nav_menu()

After usingwp_nav_menu()

That is ~5 extra queries(for each menu)

Instead, we can cache the menu in a transientand save on DB queries

// Get an existing copy of our transient data

$menu_html = get_transient( 'wcj_2013_menu' );

if ( false === $menu_html ) {

// data is not available, let's generate it

$menu_html = wp_nav_menu( array(

'theme_location' => 'primary',

'echo' => false,

) );

set_transient( 'wcj_2013_menu', $menu_html, DAY_IN_SECONDS );

}

// do something with $menu_html

echo $menu_html; 60 * 60 * 24

returns the menu instead of printing

// Get an existing copy of our transient data

$menu_html = get_transient( 'wcj_2013_menu' );

if ( false === $menu_html ) {

// data is not available, let's generate it

$menu_html = wp_nav_menu( array(

'theme_location' => 'primary',

'echo' => false,

) );

set_transient( 'wcj_2013_menu', $menu_html );

}

// do something with $menu_html

echo $menu_html; doesn't expire, but it might anyway

function wcj_2013_invldt_menu( $menu_id, $menu_data ) {

delete_transient( 'wcj_2013_menu' );

}

add_action( 'wp_update_nav_menu', 'wcj_2013_invldt_menu', 10, 2 );

Now, let's invalidate

this action runs whenever a menu is being saved

Use Transients when fetching data from a remote source

http://...https://...

Twitter APIFacebook API

Google+ API

Pick your poison

last.fm API

Example (Twitter API):Retrieve follower count and cache the results

function wcj_2013_get_twitter_followers_for( $handle ) {

$key = "wcj_2013_followers_$handle";

if ( false === ( $followers_count = get_transient( $key ) ) ) {

// transient expired, regenerate!

$followers_count = wp_remote_retrieve_body(

wp_remote_get( "http://api.twitter.com/1/users/show.json?

screen_name=$handle" )

);

// request failed?

if ( empty( $followers_count ) )

return false;

// extract the number of followers

$json = (object) json_decode( $followers_count );

$followers_count = absint( $json->followers_count );

// request was complete, store data in a transient for 6 hours

set_transient( $key, $followers_count, 6 * HOUR_IN_SECONDS );

}

return $followers_count;

}

function wcj_2013_get_twitter_followers_for( $handle ) {

$key = "wcj_2013_followers_$handle";

if ( false === ( $followers_count = get_transient( $key ) ) ) {

// transient expired, regenerate!

$followers_count = wp_remote_retrieve_body(

wp_remote_get( "http://api.twitter.com/1/users/show.json?

screen_name=$handle" )

);

// request failed?

if ( empty( $followers_count ) )

return false;

// extract the number of followers

$json = (object) json_decode( $followers_count );

$followers_count = absint( $json->followers_count );

// request was complete, store data in a transient for 6 hours

set_transient( $key, $followers_count, 6 * HOUR_IN_SECONDS );

}

return $followers_count;

}

function wcj_2013_get_twitter_followers_for( $handle ) {

$key = "wcj_2013_followers_$handle";

if ( false === ( $followers_count = get_transient( $key ) ) ) {

// transient expired, regenerate!

$followers_count = wp_remote_retrieve_body(

wp_remote_get( "http://api.twitter.com/1/users/show.json?

screen_name=$handle" )

);

// request failed?

if ( empty( $followers_count ) )

return false;

// extract the number of followers

$json = (object) json_decode( $followers_count );

$followers_count = absint( $json->followers_count );

// request was complete, store data in a transient for 6 hours

set_transient( $key, $followers_count, 6 * HOUR_IN_SECONDS );

}

return $followers_count;

}

function wcj_2013_get_twitter_followers_for( $handle ) {

$key = "wcj_2013_followers_$handle";

if ( false === ( $followers_count = get_transient( $key ) ) ) {

// transient expired, regenerate!

$followers_count = wp_remote_retrieve_body(

wp_remote_get( "http://api.twitter.com/1/users/show.json?

screen_name=$handle" )

);

// request failed?

if ( empty( $followers_count ) )

return false;

// extract the number of followers

$json = (object) json_decode( $followers_count );

$followers_count = absint( $json->followers_count );

// request was complete, store data in a transient for 6 hours

set_transient( $key, $followers_count, 6 * HOUR_IN_SECONDS );

}

return $followers_count;

}

printf( 'I have %d followers!', wcj_2013_get_twitter_followers_for( 'maorh' ) );

Let's spice it upGot some metrics!

Without Transients0.36014295 seconds

WITH Transients0.00010109 seconds

That is

3,562 X FASTER

Object Cache APIlast but not least

Object Cache is● non-persistent out of the box

● stored in PHP memory by default

● ideally* persistent when a caching backend is

available

● similar to Transients

● available since 2.0

* Success depends on server environment

Is post_id = 13

cached?

Do something with that post

Store results in Object Cache

DBSELECT * FROM

wp_posts WHERE ID =

13

Yes

No

Common use of the Object Cache API

Object Cache functionsat your disposal

read core?wp-includes/cache.php

● wp_cache_add( $k, $d, $g, $ex )

● wp_cache_get( $k )

● wp_cache_set( $k, $d, $g, $ex )

● wp_cache_delete( $k, $g )

● wp_cache_incr( $k, $offset, $g )

Object Cache functionsat your disposal

$key, $data, $group, $expiration

$song_obj = wp_cache_get( $id, 'songs' );

if ( false === $song_obj ) {

$song_obj = $wpdb->get_row(

$wpdb->prepare( "SELECT * FROM

$wpdb->songs WHERE ID = %d", $id )

);

wp_cache_set( $id, $song_obj, 'songs' );

}

// do something with $song_obj

Example

Best Practicesfor the best of us

● prefer refreshing the cache ONLY when data is

added/changed

● Avoid, if possible, caching on front end page requests

(instead, generate the data on an admin event*)

● Design to fail gracefully (never assume that data is in the

cache)

* useful events: publish_post, transition_post_status, created_{$taxonomy}, edited_{$taxonomy}

Out of the boxMemory Cache

Backend

Transients API persistent - database Persistent

Object Cache APInon-persistent -

memoryIdeally Persistent

http://wordpress.stackexchange.com/a/45137

Side-by-side comparison

Questions?Thanks!

@maorhkeep in touch