Collaboration: Add dedicated presence table for awareness storage#11609
Draft
josephfusco wants to merge 82 commits intoWordPress:trunkfrom
Draft
Collaboration: Add dedicated presence table for awareness storage#11609josephfusco wants to merge 82 commits intoWordPress:trunkfrom
josephfusco wants to merge 82 commits intoWordPress:trunkfrom
Conversation
Introduces the wp_collaboration table for storing real-time editing data (document states, awareness info, undo history) and the WP_Collaboration_Table_Storage class that implements all CRUD operations against it. Bumps the database schema version to 61840.
Replaces WP_HTTP_Polling_Sync_Server with WP_HTTP_Polling_Collaboration_Server using the wp-collaboration/v1 REST namespace. Switches to string-based client IDs, fixes the compaction race condition, adds a backward-compatible wp-sync/v1 route alias, and uses UPDATE-then-INSERT for awareness data.
Deletes WP_Sync_Post_Meta_Storage and WP_Sync_Storage interface, and removes the wp_sync_storage post type registration from post.php. These are superseded by the dedicated collaboration table.
Adds wp_is_collaboration_enabled() gate, injects the collaboration setting into the block editor, registers cron event for cleaning up stale collaboration data, and updates require/include paths for the new storage and server classes.
Adds 67 PHPUnit tests for WP_HTTP_Polling_Collaboration_Server covering document sync, awareness, undo/redo, compaction, permissions, cursor mechanics, race conditions, cron cleanup, and the backward-compatible wp-sync/v1 route. Adds E2E tests for 3-user presence, sync, and undo/redo. Removes the old sync server tests. Updates REST schema setup and fixtures for the new collaboration endpoints.
Adds a cache-first read path to get_awareness_state() following the transient pattern: check the persistent object cache, fall back to the database on miss, and prime the cache with the result. set_awareness_state() updates the cached entries in-place after the DB write rather than invalidating, so the cache stays warm for the next reader in the room. This is application-level deduplication: the shared collaboration table cannot carry a UNIQUE KEY on (room, client_id) because sync rows need multiple entries per room+client pair. Sites without a persistent cache see no behavior change — the in-memory WP_Object_Cache provides no cross-request benefit but keeps the code path identical.
Restore the `wp_client_side_media_processing_enabled` filter and the `finalize` route that were accidentally removed from the REST schema test. Add the `collaboration` table to the list of tables expected to be empty after multisite site creation.
The connectors API key entries in wp-api-generated.js were incorrectly carried over during the trunk merge. Trunk does not include them in the generated fixtures since the settings are dynamically registered and not present in the CI test context.
Rename the `update_value` column to `data` in the collaboration table storage class and tests, and fix array arrow alignment to satisfy PHPCS. The shorter name is consistent with WordPress meta tables and avoids confusion with the `update_value()` method in `WP_REST_Meta_Fields`.
Add a composite index on (type, client_id) to the collaboration table to speed up awareness upserts, which filter on both columns. Bump $wp_db_version from 61840 to 61841 so existing installations pick up the schema change via dbDelta on upgrade.
Introduce MAX_BODY_SIZE (16 MB), MAX_ROOMS_PER_REQUEST (50), and MAX_UPDATE_DATA_SIZE (1 MB) constants to cap request payloads. Wire a validate_callback on the route to reject oversized request bodies with a 413, add maxItems to the rooms schema, and replace the hardcoded maxLength with the new constant.
Reject non-numeric object IDs early in can_user_collaborate_on_entity_type(). Verify that a post's actual type matches the room's claimed entity name before granting access. For taxonomy rooms, confirm the term exists in the specified taxonomy and simplify the capability check to use assign_term with the term's object ID.
Cover oversized request body (413), exceeding max rooms (400), non-numeric object ID, post type mismatch, nonexistent taxonomy term, and term in the wrong taxonomy.
…rage Convert consecutive single-line comments to block comment style per WordPress coding standards, replace forward slashes with colons in cache keys to avoid ambiguity, hoist `global $wpdb` above the cache check in `get_awareness_state()`, and clarify the `$cursor` param docblock in `remove_updates_before_cursor()`.
When collaboration is disabled, run both DELETE queries (sync and awareness rows) before unscheduling the cron hook so leftover data is removed. Hoist `global $wpdb` to the top of the function so the disabled branch can use it. Add a comment noting future persistent types may also need exclusion from the sync cleanup query.
The wp-sync/v1 namespace was a transitional alias for the Gutenberg plugin. Remove it so only wp-collaboration/v1 is registered.
The backward-compatible wp-sync/v1 route alias was removed in 24f4fdc, making this test invalid.
This reverts commit 318051f.
…ias" This reverts commit 24f4fdc.
…d expand test coverage
The rooms array schema includes a maxItems constraint of 50, but the committed wp-api-generated.js fixture was missing it, causing git diff --exit-code to fail on every PHPUnit CI job.
…hrough_cursor The previous name was ambiguous — it suggested exclusive semantics, but the query uses inclusive deletion (id <= %d). "through" clearly communicates the inclusive behavior without needing to read the docblock.
Co-authored-by: Peter Wilson <519727+peterwilsoncc@users.noreply.github.com>
…-table # Conflicts: # src/wp-includes/collaboration/class-wp-http-polling-collaboration-server.php # tests/phpunit/tests/rest-api/rest-sync-server.php
…rage.php Co-authored-by: Mukesh Panchal <mukeshpanchal27@users.noreply.github.com>
Add a maxLength constraint to the client_id argument schema so overlong values are rejected at the REST layer rather than being silently truncated (or erroring) at the database. The 32-character limit matches the client_id varchar(32) column in schema.php and mirrors the approach already used for the room argument. Also document why both minimum and minLength are present: client_id has a union type (string|integer), and WordPress REST API validation dispatches string values to minLength/maxLength and integer values to minimum, so both keywords are required to bound each branch of the union.
…table. Add a composite KEY room_type_date (room, type, date_gmt) so the awareness read path in get_awareness_state() can seek directly to a room's live awareness rows instead of relying on the KEY room (room, id) prefix and post-filtering by type and date_gmt. In the single-table design where awareness and update rows share $wpdb->collaboration and are distinguished only by the type column, the previous indexes left no covering path for the hot-path query WHERE room = %s AND type = 'awareness' AND date_gmt >= %s, which runs on every cache-miss awareness poll (~0.5-1s per active editor). EXPLAIN on a populated table (11k rows) confirms MySQL switches from KEY room (rows=55, Using where) to KEY room_type_date (rows=3, Using index condition), with the full WHERE clause pushed down into the index lookup. The set_awareness_state() existence check (WHERE room = %s AND type = 'awareness' AND client_id = %s) also picks up the new index as a side benefit: its plan switches from an index_merge intersection of type_client_id and room to a single-index seek on room_type_date. The old intersection plan wasn't pathological, but a single composite seek is simpler and more predictable. Bump $wp_db_version to 61842 so dbDelta picks up the new index on upgrade.
…ordpress-develop into collaboration/single-table
…table. Introduce wp_presence table with UNIQUE KEY (room, client_id) for atomic upserts via INSERT ... ON DUPLICATE KEY UPDATE. This eliminates the read-modify-write race condition in the object cache approach and removes all cache-backend branching from the awareness path. The collaboration table becomes a clean append-only log for sync updates only — the type column, type_client_id index, and room_type_date index are removed. New file: src/wp-includes/presence.php provides wp_get_presence(), wp_set_presence(), wp_remove_presence(), wp_remove_user_presence(), and wp_delete_expired_presence_data().
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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This builds on #11599 by giving awareness its own table (wp_presence) with a UNIQUE KEY (room, client_id). This resolves the problems the cache layer was solving at the source — one code path, one query per write, no race conditions.
What changes
What ships (API only — no admin UI)
Minimal presence functions used internally by the collaboration server:
wp_get_presence()/wp_set_presence()/wp_remove_presence()wp_remove_user_presence()/wp_delete_expired_presence_data()The schema and API design are informed by the Presence API feature plugin, which explores UI features for a future release.
Relationship to prior PRs
Forked from #11599 → #11256. Full commit history preserved. @peterwilsoncc's non-awareness improvements (test isolation,
wp_recursive_ksort) are carried forward.Props
Use of AI Tools
AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 4
Used for: Code review of #11599, architectural analysis comparing storage approaches, implementation of the presence table integration and test updates. All changes were reviewed and verified by me.