Meta: Allow registering post meta keys that skip query cache invalidation#11290
Meta: Allow registering post meta keys that skip query cache invalidation#11290ellatrix wants to merge 11 commits intoWordPress:trunkfrom
Conversation
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
Test using WordPress PlaygroundThe changes in this pull request can previewed and tested using a WordPress Playground instance. WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser. Some things to be aware of
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
|
Dropping this here rather than on the ticket as it's a implimentation consideration rather than a architectural consideration. One of the risks we'd need to consider is demonstrated by these two tests public function test_wp_query_side_effects_one() {
register_post_meta(
'post',
'uncached_meta_key',
array( 'invalidates_query_cache' => false )
);
add_post_meta( self::$post_id, 'uncached_meta_key', 'uncached_meta_value' );
$query1 = new WP_Query(
array(
'fields' => 'ids',
'meta_value' => 'uncached_meta_value',
)
);
$this->assertEmpty( $query1->posts, 'WP_Query should not query non-cacheable meta data.' );
}
public function test_wp_query_side_effects_two() {
register_post_meta(
'post',
'uncached_meta_key',
array( 'invalidates_query_cache' => false )
);
add_post_meta( self::$post_id, 'uncached_meta_key', 'uncached_meta_value' );
$query1 = new WP_Query(
array(
'fields' => 'ids',
'meta_value' => 'uncached_meta_value',
)
);
delete_post_meta( self::$post_id, 'uncached_meta_key' );
$query2 = new WP_Query(
array(
'fields' => 'ids',
'meta_value' => 'uncached_meta_value',
)
);
$this->assertEmpty( $query2->posts, 'WP_Query should not cache non-cacheable meta queries.' );
}Results: |
Add `invalidates_query_cache` parameter to `register_meta()`. Meta keys registered with this set to `false` will not bump the `last_changed` timestamp for the parent object's cache group when added, updated, or deleted. This prevents high-frequency meta writes from invalidating query caches site-wide. Meta keys registered as non-query-cacheable are also blocked from use in `WP_Meta_Query` clauses, since skipping cache invalidation would lead to stale query results. Registers the real-time collaboration meta keys (`wp_sync_awareness` and `wp_sync_update`) with this flag to resolve the cache invalidation issue described in https://core.trac.wordpress.org/ticket/64696. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use empty string subtype so the keys are found during cache invalidation lookups, which do not have access to the post type. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Search across all registered subtypes when the caller does not provide one, so that keys registered for a specific post type are still found by wp_cache_set_posts_last_changed which only has the meta key. Adds tests for subtype registration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use get_post_type() in wp_cache_set_posts_last_changed to check the exact subtype when deciding whether to skip cache invalidation. Extract post types from the parent WP_Query context in WP_Meta_Query so that a key registered as non-cacheable for one post type is still allowed in queries targeting a different post type. Adds tests for per-subtype behavior: matching post type, different post type, array of post types, and cache invalidation with mismatched post type. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rename wp_meta_key_invalidates_query_cache to wp_post_meta_invalidates_query_cache with a required post_type parameter. Reject the flag for non-post object types in register_meta. In WP_Meta_Query, only refuse non-cacheable keys when the post type is known from the parent WP_Query context. Without context, allow the query through — it is better to allow a query than to incorrectly block a legitimate one. Cache correctness is guaranteed by the invalidation side (wp_cache_set_posts_last_changed), which always knows the post type from the object ID. Add comments explaining this reasoning throughout the code and tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a WP_Query uses meta_value without meta_key, the query matches across all meta keys. Non-cacheable meta keys must be excluded from these results, otherwise stale cached results would be served since cache invalidation is skipped for those keys. Adds a NOT IN clause to exclude registered non-cacheable keys when a meta query clause has a value but no key. Adds tests based on the cases raised in PR review. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add the new invalidates_query_cache default (true) to existing test assertions that check the full shape of registered meta key args. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
74c24a3 to
3189f29
Compare
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@peterwilsoncc Right, there's a decision to be made there for those queries that only provide a meta value to query without a key. Either we alter the query (commit), which tbh I find a little too aggressive and risky, or we are ok with this edge case. Either way, it's probably rare to have queries by meta value only? |
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| $excluded_keys = $this->get_non_cacheable_meta_keys(); | ||
| if ( ! empty( $excluded_keys ) ) { | ||
| $placeholders = implode( ',', array_fill( 0, count( $excluded_keys ), '%s' ) ); | ||
| $sql_chunks['where'][] = $wpdb->prepare( "$alias.meta_key NOT IN ($placeholders)", $excluded_keys ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared |
There was a problem hiding this comment.
@ellatrix and I discussed the complexity addition of a NOT IN query here. It seems like it would be not more complex with it since it would be a query on value only.
There was a problem hiding this comment.
I do lean towards removing the query modification and just leaving it as an edge case with a note. These kinds of meta value only queries should anyway be rare and discouraged (imo).
There was a problem hiding this comment.
Have you checked how plugins are using meta_query to know how much of an edge case this is?
I agree it should be discouraged, but I don't know how prevalent this is.
There was a problem hiding this comment.
How would you search for plugins using meta_value without meta_key?
There was a problem hiding this comment.
I can't think of a specific regex for that, so I would suggest manually checking the code. Otherwise, I think we shoud lean towards being as defensive as possible and not assuming it's an edge case.
There was a problem hiding this comment.
How would you check them manually other than just a few?
What does defensive mean in this case? I guess you're arguing for keeping the query adjustment and excluding these keys?
|
I just tested this and compared against Beforebefore.movAfterafter.mov |
Prevents RTC meta keys (wp_sync_awareness, wp_sync_update) from invalidating the posts cache group's last_changed timestamp on every write. This avoids cache invalidation storms during high-frequency collaborative editing. Uses hook-based interception with a standalone registry since Core's register_meta() cannot be modified. When WordPress 7.0 ships with the native implementation, this backport is automatically disabled via function_exists guard. New file: lib/compat/wordpress-7.0/post-meta-cache.php - gutenberg_register_non_cacheable_post_meta() to register keys - wp_post_meta_invalidates_query_cache() public API - Replaces Core hooks with conditional version that checks registry Modified: lib/compat/wordpress-7.0/collaboration.php - Register RTC meta keys as non-cacheable Modified: lib/load.php - Load post-meta-cache.php before collaboration.php See: https://core.trac.wordpress.org/ticket/64696 See: WordPress/wordpress-develop#11290
Trac ticket: https://core.trac.wordpress.org/ticket/64696
Alternative approach to the cache invalidation problem: instead of a new table, allow post meta keys to be registered as non-query-cacheable via
register_meta()/register_post_meta().What it does
Adds an
invalidates_query_cacheparameter toregister_meta()(post meta only). Keys registered withfalse:last_changedtimestamp in thepostscache group when added, updated, or deletedWP_Meta_Queryclauses (with_doing_it_wrong) when the post type is known, to prevent stale cached resultsget_post_meta()/update_post_meta()/ etc.Design
Cache correctness relies on two complementary mechanisms:
Cache invalidation (
wp_cache_set_posts_last_changed): the authoritative check. Meta write hooks always provide the object ID, so the exact post type is always known. Non-cacheable meta never incorrectly bumpslast_changed.Meta query blocking (
WP_Meta_Query): a conservative safety net. Only refuses non-cacheable keys when the post type is known from the parentWP_Querycontext. Without context, allows the query through — better to allow a query than incorrectly block a legitimate one.Supports per-post-type registration: a key can be non-cacheable for one post type but cacheable for another.
Testing
15 unit tests covering registration, cache invalidation (add/update/delete), meta query blocking (with/without context, per-post-type, multi-post-type), and the _doing_it_wrong notice.
Use of AI Tools
Co-authored with Claude Code (Opus 4.6).
This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.