Skip to content

RTC: Transients for awareness and JIT meta cache flushing#11348

Open
peterwilsoncc wants to merge 26 commits intoWordPress:trunkfrom
peterwilsoncc:64696-using-core-apis
Open

RTC: Transients for awareness and JIT meta cache flushing#11348
peterwilsoncc wants to merge 26 commits intoWordPress:trunkfrom
peterwilsoncc:64696-using-core-apis

Conversation

@peterwilsoncc
Copy link
Copy Markdown
Contributor

@peterwilsoncc peterwilsoncc commented Mar 25, 2026

This does a few things:

  • Moves awareness to the transients API: For sites with a persistent cache awareness will be stored in the object cache, the options table for site that don't
  • As the transients have a timeout set, they will not be autoloaded or invalidate the alloptions cache
  • Reduces awareness granularity to 10 seconds (rounded up), this changes the awareness timeout to be anywhere between 30 and 39 seconds which is close enough. It will also reduce database writes/cache invalidation
  • Introduces a filter wp_sync_awareness_timestamp_granularity to allow granularity to be modified by third party devs
  • Reverts r62099 to use the Meta APIs for syncing updates: I think this will be helpful as rooms meta data will no longer be updated following an awareness update. This will allow the cache to be used for rooms that have not had an update since the last polling update (eg, browser open while making lunch)
  • Introduces just in time cache invalidation for meta queries in WP_Query -- modifying the meta data will set a flag that the WP_Query post group will need to be invalidated next time a site makes a meta query
  • Introduces just in time cache invalidation for registered post meta. Modifying the meta data will set a flag to trigger cache invalidation next time a meta query is made in WP_Query.
  • Adds tests for above

Trac ticket: https://core.trac.wordpress.org/ticket/64696

Use of AI Tools


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.

@github-actions
Copy link
Copy Markdown

Test using WordPress Playground

The 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

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

@peterwilsoncc peterwilsoncc changed the title Transients for awareness Transients for awareness and JIT meta cache flushing Mar 26, 2026
@peterwilsoncc peterwilsoncc force-pushed the 64696-using-core-apis branch 2 times, most recently from 2977e90 to f3467c2 Compare March 26, 2026 02:31
@peterwilsoncc peterwilsoncc changed the title Transients for awareness and JIT meta cache flushing RTC: Transients for awareness and JIT meta cache flushing Mar 26, 2026
@peterwilsoncc peterwilsoncc force-pushed the 64696-using-core-apis branch from 0ddf83c to bfb758e Compare March 26, 2026 20:44
@peterwilsoncc peterwilsoncc marked this pull request as ready for review March 26, 2026 20:59
@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 26, 2026

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 props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props peterwilsoncc, jorbin, czarate, desrosj, zieladam.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@peterwilsoncc peterwilsoncc force-pushed the 64696-using-core-apis branch 3 times, most recently from 660c289 to be9e5e4 Compare March 27, 2026 01:07
@peterwilsoncc
Copy link
Copy Markdown
Contributor Author

I've pushed a few changes following reviews:

Following @aaronjorbin's review and suggestions in Slack

  • 8020652 and some follow up commits: Introduces JIT meta invalidation to the register meta API so the JIT approach is only taken for specific meta data rather than all meta data. This is to help avoid unintentional consequences outside of RTC. As part of this change, I added _edit_lock for JIT invalidation. Discuss.
  • 6948cde: Reuse a variable instead of recalling time()

Following @chriszarate's review:

  • 25c571e: Restored a few changes in the original commit that were not related to switching to direct DB calls
  • f7e25c2: expanded docs on granularity filter

Additionally:

@peterwilsoncc peterwilsoncc force-pushed the 64696-using-core-apis branch from be9e5e4 to 74dc594 Compare March 27, 2026 01:16
Copy link
Copy Markdown
Member

@desrosj desrosj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some small improvements for the inline docs, but this is looking promising so far.

* @param int $object_id Object ID for which the meta was updated.
* @param string $meta_key Meta key that was updated.
*/
function wp_cache_set_needs_meta_query_flush( $meta_ids, $object_id, $meta_key ) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit of help with naming things here would be great, same for the name of the cache key.

In an ideal world the cache key would be of positive intent, wp_query_meta_query_needs_flush or similar, but that would be problematic if an object cache's eviction policy drops the value.

Caching false can be problematic with some implementations so whatever the cache key is named, it needs to make sense that true is used to indicate that the cache does not need a JIT flush. Sigh.

Copy link
Copy Markdown
Member

@aaronjorbin aaronjorbin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @peterwilsoncc ! Overall, I think this is moving the code in a very positive direction.

)
);

// @todo This should probably go elsewhere.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wp_create_initial_post_meta makes sense to me and has a wp_is_collaboration_enabled() gate if we want WP_Sync_Post_Meta_Storage::SYNC_UPDATE_META_KEY to be conditionally registered.

* array with 'schema' or 'prepare_callback' keys instead of a boolean.
* @type bool $revisions_enabled Whether to enable revisions support for this meta_key. Can only be used when the
* object type is 'post'.
* @type bool $jit_cache_invalidation Whether to enable just-in-time cache invalidation for this meta key. When enabled, the cache
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kittenkamala: This addition to post_meta should likely get a mention in the misc changes devnote.

);

$original_wpdb = $wpdb;
$proxy_wpdb = new class( $original_wpdb, $storage_post_id, $injected_update ) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not blocking and I know this isn't as new as the green makes it look, but I don't love this living inside the test function. It's also copied in multiple places.

// @todo This should probably go elsewhere.
register_post_meta(
'',
'_edit_lock',
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As part of this change, I added _edit_lock for JIT invalidation. Discuss.

I think this makes sense. "Is this only used for writing purposes and not for display purposes" feels like it could be a good rule of thumb.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is storing things in both post-meta and transients, should it (and the class) have it's name changed? It also means that if a new DB table is added later, this class can be continue to be used as the interface for extenders.

It could also be made it clear in a devnote (or inline comment?) that interacting with sync related data should be done through this class, not through any hooks called as a result of how it implements the storage. i.e. don't count on update_postmeta firing forever.

This feels inline with the comment on the class being "Core class that provides an interface for storing and retrieving sync updates and awareness data during a collaborative session."

* transient longer than the awareness can avoid adding new entries in the options
* table unnecessarily.
*/
set_transient( self::AWARENESS_TRANSIENT_PREFIX . ":{$room_hash}", $awareness, DAY_IN_SECONDS );
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

set_transient has a race condition where we might end up calling add_option multiple times. There's probably a million different possible outcomes depending on whether we have object cache, is it persistent, etc. and it's important we don't end up inserting states A and B and then end up reading A but updating B, or reading A on some requests but B on other requests.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the race description you are talking about https://core.trac.wordpress.org/ticket/63450 or something different?

if ( null !== $decoded ) {
$updates[] = $decoded;
}
$updates[] = maybe_unserialize( $row->meta_value );
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any way we can avoid relying on serialize()? I really liked how we dealt with JSON before, it removes an entire class of serialize()-related security vulnerabilities.

}

$awareness = json_decode( $meta_value, true );
$room_hash = md5( $room ); // Not used for cryptographic purposes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any way we can avoid all the hashing? It could add up to substantial load when there's enough users of this feature

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any way we can avoid all the hashing? It could add up to substantial load when there's enough users of this feature

@adamziel There isn't as we can't use the room ids in slugs due to WP sanitization and transients have a maximum lenght we need to avoid. We could aim for a less intensive hashing mechanism if you have a recommendation.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coming from a place of curiosity: Is md5 really that expensive? It seems to be a bit slower than serializing a simple array:

> php -v
PHP 8.5.4 (cli) (built: Mar 10 2026 23:15:23) (NTS)
Copyright (c) The PHP Group
Built by Homebrew
Zend Engine v4.5.4, Copyright (c) Zend Technologies
    with Zend OPcache v8.5.4, Copyright (c), by Zend Technologies
> php -r '$start = microtime(true); for ($i = 0; $i < 1000000; $i++) { md5("test" . $i); } $end = microtime(true); echo "Time: " . ($end - $start) . " seconds\n";'
Time: 0.12329506874084 seconds
> php -r '$start = microtime(true); for ($i = 0; $i < 1000000; $i++) { serialize([["foo" => "test" . $i]]); } $end = microtime(true); echo "Time: " . ($end - $start) . " seconds\n";'
Time: 0.099403858184814 seconds

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, we only call md5 once per room per request.

$join .= $clauses['join'];
$where .= $clauses['where'];

if ( ! wp_cache_get( 'wp_query_meta_query_updated', 'post_meta' ) ) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a thorough writeup in an inline docblock that goes through the entire data flow and the reasoning behind this? Every time I see it it takes a lot of effort to unpack

@peterwilsoncc peterwilsoncc force-pushed the 64696-using-core-apis branch from b57a482 to 59995f5 Compare March 27, 2026 21:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants