Consistent Cache Keys for Query Groups in WordPress 6.9

Query caches have historically used the last changed timestamp as a salt. While this has proven effective for most sites, it leads to an excessive number of caches which can be problematic on high-traffic and heavily updated sites. WordPress 6.9 introduces changes to how cache keys are created in order to ensure efficient use of object caches and help caches clean up after themselves.

These changes are compatible with existing implementations of persistent caching drop-ins. Vendors are not required to make any changes to their code to support these features in WordPress 6.9. As with other caching functions, the functions are pluggable should vendors wish to optimize for their particular caching implementation. These new functions are:

  • wp_cache_get_salted( string $cache_key, string $group, string|string[] $salt ): mixed
  • wp_cache_set_salted( string $cache_key, mixed $data, string $group, string|string[] $salt, int $expire = 0 ): bool
  • wp_cache_get_multiple_salted( string[] $cache_keys, string $group, string|string[] $salt ): mixed[]
  • wp_cache_set_multiple_salted( mixed[] $data, string $group, string|string[] $salt, int $expire = 0 ): bool[]

What Behavior is Changing

Previous behavior in WordPress 6.8 and earlier:

  • A post object is saved
  • WordPress stores the last changed time of the posts table
  • WP Query is called
  • WordPress caches the database query using a key containing the last changed time
  • Another post is saved, updating the posts table’s last changed time
  • The previous cache becomes unreachable
  • WP Query is called
  • WordPress does not see a cached query
  • WordPress caches the database query using a new key containing the updated last changed time

New behavior in WordPress 6.9

  • A post object is saved
  • WordPress stores the last changed time of the posts table
  • WP Query is called
  • WordPress caches the database query alongside the last changed time
  • Another post is saved, updating the posts table’s last changed time
  • WP Query is called
  • WordPress hits the previously generated cache
  • WordPress uses the last changed value to determine if the cache is up-to-date
  • WordPress replaces the previously generated cache with the new results

While both operations perform two cache lookups, the new behavior re-uses the existing cache key and does an in memory comparison of the last changed time. This prevents the cache from containing unreachable cache keys.

The same change in behavior applies to other Query classes such as term queries, comment queries, user queries, etc.

Checking/setting query caches directly

Broadly, the caches affected are in the following cache groups:

  • comment-queries
  • network-queries
  • post-queries
  • site-queries
  • term-queries
  • user-queries
The following specific cache keys are affected and will now be different.  If you have been directly checking or setting caches that start with the following keys, you will need to adjust your code.
  • get_comments
  • get_comment_child_ids
  • get_network_ids
  • comment_feed
  • wp_query
  • get_sites
  • wp_get_archives
  • adjacent_post
  • get_page_by_path
  • find_post_by_old_slug
  • get_objects_in_term
  • count_user_postscount_user_posts

Additionally, keys that are generated by WP_User_Query:generate_cache_key and WP_Term_Query::generate_cache_key are affected as well. However, these keys are md5 hashes and thus are less likely to be used directly.

When using the new wp_cache_*_salted functions and passing in an array of salts, the order of items in the array must be consistent. If you are using the changed caches in coreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress., please review the order in core and use the same order to ensure cache hits. 

If you need to support multiple versions of WordPress when updating your code, you can use code that looks similar to:

if (function_exists( 'wp_cache_get_salted` ) ){
  $data = wp_cache_get_salted( $cache_key, 'comment-queries', $last_changed );
} else {
  // your current code here
}

See [60697] for specific examples of how core has been updated and [60941] for an example of how these new functions help cache previously uncached code. 

On Upgrade to WordPress 6.9

With the update to cache keys, it’s expected that you may see a short term increase in cache misses upon upgrade. You may wish to preemptively evict the old cache keys in order to prevent stale entries from sticking around, potentially leading to unnecessary evictions based on your cache policies.

For more information, please see #59592.

Props to @desrosj@peterwilsoncc, and @spacedmonkey for review. Props to @spacedmonkey for comments on the ticketticket Created for both bug reports and feature development on the bug tracker. that could be easily reused here.

#6-9, #cache-api, #dev-notes, #dev-notes-6-9