Skip to content

Commit e3ba953

Browse files
committed
Caching API: Use consistent cache keys for query groups.
Query-based caches are now improved by reusing cache keys. Previously, cache keys for query caches were generated using the `last_changed` value as part of the key. This meant that whenever `last_changed` was updated, all the previously cached values for the group became unreachable. The new approach allows WordPress to replace previously cached results that are known to be stale. The previous approach relied on the object cache backend evicting stale keys which is done at various levels of efficiency. To address this, the following new helper functions have been introduced: * wp_cache_get_salted * wp_cache_set_salted * wp_cache_get_multiple_salted * wp_cache_set_multiple_salted These functions provide a consistent way to get/set query caches. Instead of using the last_changed value as part of the cache key, it is now stored inside the cache value as a "salt". This allows cache keys to be reused, with values updated in place rather than relying on eviction of outdated entries. Props spacedmonkey, peterwilsoncc, flixos90, sanchothefat, tillkruess, rmccue, mukesh27, adamsilverstein, owi, nickchomey. git-svn-id: https://develop.svn.wordpress.org/trunk@60697 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 428611b commit e3ba953

18 files changed

+562
-82
lines changed

src/wp-includes/cache-compat.php

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,3 +199,116 @@ function wp_cache_supports( $feature ) {
199199
return false;
200200
}
201201
endif;
202+
203+
if ( ! function_exists( 'wp_cache_get_salted' ) ) :
204+
/**
205+
* Retrieves cached data if valid and unchanged.
206+
*
207+
* @since 6.9.0
208+
*
209+
* @param string $cache_key The cache key used for storage and retrieval.
210+
* @param string $group The cache group used for organizing data.
211+
* @param string|string[] $salt The timestamp (or multiple timestamps if an array) indicating when the cache group(s) were last updated.
212+
* @return mixed|false The cached data if valid, or false if the cache does not exist or is outdated.
213+
*/
214+
function wp_cache_get_salted( $cache_key, $group, $salt ) {
215+
$salt = is_array( $salt ) ? implode( ':', $salt ) : $salt;
216+
$cache = wp_cache_get( $cache_key, $group );
217+
218+
if ( ! is_array( $cache ) ) {
219+
return false;
220+
}
221+
222+
if ( ! isset( $cache['salt'] ) || ! isset( $cache['data'] ) || $salt !== $cache['salt'] ) {
223+
return false;
224+
}
225+
226+
return $cache['data'];
227+
}
228+
endif;
229+
230+
if ( ! function_exists( 'wp_cache_set_salted' ) ) :
231+
/**
232+
* Stores salted data in the cache.
233+
*
234+
* @since 6.9.0
235+
*
236+
* @param string $cache_key The cache key under which to store the data.
237+
* @param mixed $data The data to be cached.
238+
* @param string $group The cache group to which the data belongs.
239+
* @param string|string[] $salt The timestamp (or multiple timestamps if an array) indicating when the cache group(s) were last updated.
240+
* @param int $expire Optional. When to expire the cache contents, in seconds.
241+
* Default 0 (no expiration).
242+
* @return bool True on success, false on failure.
243+
*/
244+
function wp_cache_set_salted( $cache_key, $data, $group, $salt, $expire = 0 ) {
245+
$salt = is_array( $salt ) ? implode( ':', $salt ) : $salt;
246+
return wp_cache_set(
247+
$cache_key,
248+
array(
249+
'data' => $data,
250+
'salt' => $salt,
251+
),
252+
$group,
253+
$expire
254+
);
255+
}
256+
endif;
257+
258+
if ( ! function_exists( 'wp_cache_get_multiple_salted' ) ) :
259+
/**
260+
* Retrieves multiple items from the cache, only considering valid and unchanged items.
261+
*
262+
* @since 6.9.0
263+
*
264+
* @param array $cache_keys Array of cache keys to retrieve.
265+
* @param string $group The group of the cache to check.
266+
* @param string|string[] $salt The timestamp (or multiple timestamps if an array) indicating when the cache group(s) were last updated.
267+
* @return array An associative array containing cache values. Values are `false` if they are not found or outdated.
268+
*/
269+
function wp_cache_get_multiple_salted( $cache_keys, $group, $salt ) {
270+
$salt = is_array( $salt ) ? implode( ':', $salt ) : $salt;
271+
$cache = wp_cache_get_multiple( $cache_keys, $group );
272+
273+
foreach ( $cache as $key => $value ) {
274+
if ( ! is_array( $value ) ) {
275+
$cache[ $key ] = false;
276+
continue;
277+
}
278+
if ( ! isset( $value['salt'], $value['data'] ) || $salt !== $value['salt'] ) {
279+
$cache[ $key ] = false;
280+
continue;
281+
}
282+
$cache[ $key ] = $value['data'];
283+
}
284+
285+
return $cache;
286+
}
287+
endif;
288+
289+
if ( ! function_exists( 'wp_cache_set_multiple_salted' ) ) :
290+
/**
291+
* Stores multiple pieces of salted data in the cache.
292+
*
293+
* @since 6.9.0
294+
*
295+
* @param mixed $data Data to be stored in the cache for all keys.
296+
* @param string $group Group to which the cached data belongs.
297+
* @param string|string[] $salt The timestamp (or multiple timestamps if an array) indicating when the cache group(s) were last updated.
298+
* @param int $expire Optional. When to expire the cache contents, in seconds.
299+
* Default 0 (no expiration).
300+
* @return bool[] Array of return values, grouped by key. Each value is either
301+
* true on success, or false on failure.
302+
*/
303+
function wp_cache_set_multiple_salted( $data, $group, $salt, $expire = 0 ) {
304+
$salt = is_array( $salt ) ? implode( ':', $salt ) : $salt;
305+
$new_cache = array();
306+
foreach ( $data as $key => $value ) {
307+
$new_cache[ $key ] = array(
308+
'data' => $value,
309+
'salt' => $salt,
310+
);
311+
}
312+
return wp_cache_set_multiple( $new_cache, $group, $expire );
313+
}
314+
endif;

src/wp-includes/class-wp-comment-query.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -451,8 +451,8 @@ public function get_comments() {
451451
$key = md5( serialize( $_args ) );
452452
$last_changed = wp_cache_get_last_changed( 'comment' );
453453

454-
$cache_key = "get_comments:$key:$last_changed";
455-
$cache_value = wp_cache_get( $cache_key, 'comment-queries' );
454+
$cache_key = "get_comments:$key";
455+
$cache_value = wp_cache_get_salted( $cache_key, 'comment-queries', $last_changed );
456456
if ( false === $cache_value ) {
457457
$comment_ids = $this->get_comment_ids();
458458
if ( $comment_ids ) {
@@ -463,7 +463,7 @@ public function get_comments() {
463463
'comment_ids' => $comment_ids,
464464
'found_comments' => $this->found_comments,
465465
);
466-
wp_cache_add( $cache_key, $cache_value, 'comment-queries' );
466+
wp_cache_set_salted( $cache_key, $cache_value, 'comment-queries', $last_changed );
467467
} else {
468468
$comment_ids = $cache_value['comment_ids'];
469469
$this->found_comments = $cache_value['found_comments'];
@@ -1044,9 +1044,9 @@ protected function fill_descendants( $comments ) {
10441044
if ( $_parent_ids ) {
10451045
$cache_keys = array();
10461046
foreach ( $_parent_ids as $parent_id ) {
1047-
$cache_keys[ $parent_id ] = "get_comment_child_ids:$parent_id:$key:$last_changed";
1047+
$cache_keys[ $parent_id ] = "get_comment_child_ids:$parent_id:$key";
10481048
}
1049-
$cache_data = wp_cache_get_multiple( array_values( $cache_keys ), 'comment-queries' );
1049+
$cache_data = wp_cache_get_multiple_salted( array_values( $cache_keys ), 'comment-queries', $last_changed );
10501050
foreach ( $_parent_ids as $parent_id ) {
10511051
$parent_child_ids = $cache_data[ $cache_keys[ $parent_id ] ];
10521052
if ( false !== $parent_child_ids ) {
@@ -1080,10 +1080,10 @@ protected function fill_descendants( $comments ) {
10801080

10811081
$data = array();
10821082
foreach ( $parent_map as $parent_id => $children ) {
1083-
$cache_key = "get_comment_child_ids:$parent_id:$key:$last_changed";
1083+
$cache_key = "get_comment_child_ids:$parent_id:$key";
10841084
$data[ $cache_key ] = $children;
10851085
}
1086-
wp_cache_set_multiple( $data, 'comment-queries' );
1086+
wp_cache_set_multiple_salted( $data, 'comment-queries', $last_changed );
10871087
}
10881088

10891089
++$level;

src/wp-includes/class-wp-network-query.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -249,8 +249,8 @@ public function get_networks() {
249249
$key = md5( serialize( $_args ) );
250250
$last_changed = wp_cache_get_last_changed( 'networks' );
251251

252-
$cache_key = "get_network_ids:$key:$last_changed";
253-
$cache_value = wp_cache_get( $cache_key, 'network-queries' );
252+
$cache_key = "get_network_ids:$key";
253+
$cache_value = wp_cache_get_salted( $cache_key, 'network-queries', $last_changed );
254254

255255
if ( false === $cache_value ) {
256256
$network_ids = $this->get_network_ids();
@@ -262,7 +262,7 @@ public function get_networks() {
262262
'network_ids' => $network_ids,
263263
'found_networks' => $this->found_networks,
264264
);
265-
wp_cache_add( $cache_key, $cache_value, 'network-queries' );
265+
wp_cache_set_salted( $cache_key, $cache_value, 'network-queries', $last_changed );
266266
} else {
267267
$network_ids = $cache_value['network_ids'];
268268
$this->found_networks = $cache_value['found_networks'];

src/wp-includes/class-wp-query.php

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2882,13 +2882,16 @@ public function get_posts() {
28822882
$comments_request = "SELECT $distinct {$wpdb->comments}.comment_ID FROM {$wpdb->comments} $cjoin $cwhere $cgroupby $corderby $climits";
28832883

28842884
$key = md5( $comments_request );
2885-
$last_changed = wp_cache_get_last_changed( 'comment' ) . ':' . wp_cache_get_last_changed( 'posts' );
2885+
$last_changed = array(
2886+
wp_cache_get_last_changed( 'comment' ),
2887+
wp_cache_get_last_changed( 'posts' ),
2888+
);
28862889

2887-
$cache_key = "comment_feed:$key:$last_changed";
2888-
$comment_ids = wp_cache_get( $cache_key, 'comment-queries' );
2890+
$cache_key = "comment_feed:$key";
2891+
$comment_ids = wp_cache_get_salted( $cache_key, 'comment-queries', $last_changed );
28892892
if ( false === $comment_ids ) {
28902893
$comment_ids = $wpdb->get_col( $comments_request );
2891-
wp_cache_add( $cache_key, $comment_ids, 'comment-queries' );
2894+
wp_cache_set_salted( $cache_key, $comment_ids, 'comment-queries', $last_changed );
28922895
}
28932896
_prime_comment_caches( $comment_ids );
28942897

@@ -3246,15 +3249,21 @@ public function get_posts() {
32463249
$id_query_is_cacheable = false;
32473250
}
32483251

3252+
$last_changed = (array) wp_cache_get_last_changed( 'posts' );
3253+
if ( ! empty( $this->tax_query->queries ) ) {
3254+
$last_changed[] = wp_cache_get_last_changed( 'terms' );
3255+
}
3256+
32493257
if ( $q['cache_results'] && $id_query_is_cacheable ) {
32503258
$new_request = str_replace( $fields, "{$wpdb->posts}.*", $this->request );
32513259
$cache_key = $this->generate_cache_key( $q, $new_request );
32523260

32533261
$cache_found = false;
32543262
if ( null === $this->posts ) {
3255-
$cached_results = wp_cache_get( $cache_key, 'post-queries', false, $cache_found );
3263+
$cached_results = wp_cache_get_salted( $cache_key, 'post-queries', $last_changed );
32563264

32573265
if ( $cached_results ) {
3266+
$cache_found = true;
32583267
/** @var int[] */
32593268
$post_ids = array_map( 'intval', $cached_results['posts'] );
32603269

@@ -3312,7 +3321,7 @@ public function get_posts() {
33123321
'max_num_pages' => $this->max_num_pages,
33133322
);
33143323

3315-
wp_cache_set( $cache_key, $cache_value, 'post-queries' );
3324+
wp_cache_set_salted( $cache_key, $cache_value, 'post-queries', $last_changed );
33163325
}
33173326

33183327
return $this->posts;
@@ -3350,7 +3359,7 @@ public function get_posts() {
33503359
'max_num_pages' => $this->max_num_pages,
33513360
);
33523361

3353-
wp_cache_set( $cache_key, $cache_value, 'post-queries' );
3362+
wp_cache_set_salted( $cache_key, $cache_value, 'post-queries', $last_changed );
33543363
}
33553364

33563365
return $post_parents;
@@ -3448,7 +3457,7 @@ public function get_posts() {
34483457
'max_num_pages' => $this->max_num_pages,
34493458
);
34503459

3451-
wp_cache_set( $cache_key, $cache_value, 'post-queries' );
3460+
wp_cache_set_salted( $cache_key, $cache_value, 'post-queries', $last_changed );
34523461
}
34533462

34543463
if ( ! $q['suppress_filters'] ) {
@@ -3486,11 +3495,11 @@ public function get_posts() {
34863495
$comment_key = md5( $comments_request );
34873496
$comment_last_changed = wp_cache_get_last_changed( 'comment' );
34883497

3489-
$comment_cache_key = "comment_feed:$comment_key:$comment_last_changed";
3490-
$comment_ids = wp_cache_get( $comment_cache_key, 'comment-queries' );
3498+
$comment_cache_key = "comment_feed:$comment_key";
3499+
$comment_ids = wp_cache_get_salted( $comment_cache_key, 'comment-queries', $comment_last_changed );
34913500
if ( false === $comment_ids ) {
34923501
$comment_ids = $wpdb->get_col( $comments_request );
3493-
wp_cache_add( $comment_cache_key, $comment_ids, 'comment-queries' );
3502+
wp_cache_set_salted( $comment_cache_key, $comment_ids, 'comment-queries', $comment_last_changed );
34943503
}
34953504
_prime_comment_caches( $comment_ids );
34963505

@@ -5062,12 +5071,7 @@ static function ( &$value ) use ( $wpdb, $placeholder ) {
50625071
$sql = $wpdb->remove_placeholder_escape( $sql );
50635072
$key = md5( serialize( $args ) . $sql );
50645073

5065-
$last_changed = wp_cache_get_last_changed( 'posts' );
5066-
if ( ! empty( $this->tax_query->queries ) ) {
5067-
$last_changed .= wp_cache_get_last_changed( 'terms' );
5068-
}
5069-
5070-
$this->query_cache_key = "wp_query:$key:$last_changed";
5074+
$this->query_cache_key = "wp_query:$key";
50715075
return $this->query_cache_key;
50725076
}
50735077

src/wp-includes/class-wp-site-query.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -357,8 +357,8 @@ public function get_sites() {
357357
$key = md5( serialize( $_args ) );
358358
$last_changed = wp_cache_get_last_changed( 'sites' );
359359

360-
$cache_key = "get_sites:$key:$last_changed";
361-
$cache_value = wp_cache_get( $cache_key, 'site-queries' );
360+
$cache_key = "get_sites:$key";
361+
$cache_value = wp_cache_get_salted( $cache_key, 'site-queries', $last_changed );
362362

363363
if ( false === $cache_value ) {
364364
$site_ids = $this->get_site_ids();
@@ -370,7 +370,7 @@ public function get_sites() {
370370
'site_ids' => $site_ids,
371371
'found_sites' => $this->found_sites,
372372
);
373-
wp_cache_add( $cache_key, $cache_value, 'site-queries' );
373+
wp_cache_set_salted( $cache_key, $cache_value, 'site-queries', $last_changed );
374374
} else {
375375
$site_ids = $cache_value['site_ids'];
376376
$this->found_sites = $cache_value['found_sites'];

src/wp-includes/class-wp-term-query.php

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -777,8 +777,9 @@ public function get_terms() {
777777
}
778778

779779
if ( $args['cache_results'] ) {
780-
$cache_key = $this->generate_cache_key( $args, $this->request );
781-
$cache = wp_cache_get( $cache_key, 'term-queries' );
780+
$cache_key = $this->generate_cache_key( $args, $this->request );
781+
$last_changed = wp_cache_get_last_changed( 'terms' );
782+
$cache = wp_cache_get_salted( $cache_key, 'term-queries', $last_changed );
782783

783784
if ( false !== $cache ) {
784785
if ( 'ids' === $_fields ) {
@@ -806,7 +807,7 @@ public function get_terms() {
806807
if ( 'count' === $_fields ) {
807808
$count = $wpdb->get_var( $this->request ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
808809
if ( $args['cache_results'] ) {
809-
wp_cache_set( $cache_key, $count, 'term-queries' );
810+
wp_cache_set_salted( $cache_key, $count, 'term-queries', $last_changed );
810811
}
811812
return $count;
812813
}
@@ -815,7 +816,7 @@ public function get_terms() {
815816

816817
if ( empty( $terms ) ) {
817818
if ( $args['cache_results'] ) {
818-
wp_cache_add( $cache_key, array(), 'term-queries' );
819+
wp_cache_set_salted( $cache_key, array(), 'term-queries', $last_changed );
819820
}
820821
return array();
821822
}
@@ -900,7 +901,7 @@ public function get_terms() {
900901
}
901902

902903
if ( $args['cache_results'] ) {
903-
wp_cache_add( $cache_key, $term_cache, 'term-queries' );
904+
wp_cache_set_salted( $cache_key, $term_cache, 'term-queries', $last_changed );
904905
}
905906

906907
$this->terms = $this->format_terms( $term_objects, $_fields );
@@ -1172,8 +1173,8 @@ protected function generate_cache_key( array $args, $sql ) {
11721173
// Replace wpdb placeholder in the SQL statement used by the cache key.
11731174
$sql = $wpdb->remove_placeholder_escape( $sql );
11741175

1175-
$key = md5( serialize( $cache_args ) . $sql );
1176-
$last_changed = wp_cache_get_last_changed( 'terms' );
1177-
return "get_terms:$key:$last_changed";
1176+
$key = md5( serialize( $cache_args ) . $sql );
1177+
1178+
return "get_terms:$key";
11781179
}
11791180
}

0 commit comments

Comments
 (0)