WordPress Object Cache: wp_cache_set, Groups, and Persistent Backends Explained

WordPress executes dozens of database queries per page load — fetching post data, option values, term counts, nav menus, and user metadata. The Object Cache is the in-memory key-value store that prevents identical queries from hitting the database twice within the same request. WordPress uses it internally for almost everything: get_post(), get_option(), get_terms(), and nav menu loading all populate and read the cache automatically. The default implementation stores data in a PHP array and lives only for the duration of the request. The real performance gain comes from a persistent backend — Redis or Memcached — which preserves cached data across requests and processes, turning a 200ms database query into a sub-millisecond memory read on every subsequent request. Using wp_cache_set(), wp_cache_get(), and proper cache groups in your own plugin code means your custom queries benefit from the same infrastructure that WordPress itself uses.

Problem: Your plugin runs an expensive custom $wpdb query on every page load — counting records, aggregating stats, or looking up configuration data — that returns the same result for minutes or hours at a time, causing unnecessary database load.

Solution: Wrap the expensive query with wp_cache_get() before executing it and wp_cache_set() after, using a descriptive cache key and an appropriate expiry. Invalidate the cache entry on save_post or whichever hook signals that the underlying data has changed.

<?php
/**
 * Get the count of published posts in a custom post type, cached for 5 minutes.
 */
function get_published_resource_count() {
    $cache_key   = 'resource_count';
    $cache_group = 'my_plugin';

    $count = wp_cache_get( $cache_key, $cache_group );

    if ( false === $count ) {
        global $wpdb;
        $count = (int) $wpdb->get_var(
            "SELECT COUNT(*) FROM {$wpdb->posts}
             WHERE post_type = 'resource' AND post_status = 'publish'"
        );
        // Cache for 5 minutes (300 seconds). 0 = cache indefinitely.
        wp_cache_set( $cache_key, $count, $cache_group, 300 );
    }

    return $count;
}

// Invalidate when a 'resource' post is saved, published, or deleted
add_action( 'save_post_resource',   'invalidate_resource_count_cache' );
add_action( 'delete_post',          'invalidate_resource_count_cache' );
add_action( 'transition_post_status', function ( $new, $old ) {
    if ( $new !== $old ) {
        invalidate_resource_count_cache();
    }
}, 10, 2 );

function invalidate_resource_count_cache() {
    wp_cache_delete( 'resource_count', 'my_plugin' );
}

Cache groups make it easy to delete all entries for a plugin at once, and to avoid key collisions between plugins:

<?php
// Store per-user data under a namespaced key
$user_id = get_current_user_id();
$key     = 'user_dashboard_data_' . $user_id;
$group   = 'my_plugin_user';

$data = wp_cache_get( $key, $group );

if ( false === $data ) {
    $data = expensive_user_query( $user_id );
    wp_cache_set( $key, $data, $group, HOUR_IN_SECONDS );
}

// Check which backend is active
global $wp_object_cache;
$backend = get_class( $wp_object_cache );
// 'WP_Object_Cache'          — default PHP array (per-request only)
// 'Redis_Object_Cache' etc.  — persistent backend

NOTE: The default WordPress Object Cache is not persistent — it only survives for a single request. If you rely on wp_cache_set() to avoid repeat database hits across multiple requests, you must install a persistent cache drop-in (Redis Object Cache plugin, Memcached Object Cache). Without a persistent backend, wp_cache_get() always returns false on a fresh request and the underlying query runs every time. Verify the active backend with get_class( $wp_object_cache ) during development.