@@ -79,50 +79,16 @@ public function add_update( string $room, $update ): bool {
7979 return false ;
8080 }
8181
82- // Create an envelope and stamp each update to enable cursor-based filtering.
83- $ envelope = array (
84- 'timestamp ' => $ this ->get_time_marker (),
85- 'value ' => $ update ,
82+ $ meta_id = $ this ->with_suspended_posts_last_changed_update (
83+ fn () => add_post_meta ( $ post_id , self ::SYNC_UPDATE_META_KEY , $ update , false )
8684 );
8785
88- return $ this ->with_suspended_posts_last_changed_update (
89- fn () => (bool ) add_post_meta ( $ post_id , self ::SYNC_UPDATE_META_KEY , $ envelope , false )
90- );
91- }
92-
93- /**
94- * Retrieves all sync updates for a given room.
95- *
96- * @since 7.0.0
97- *
98- * @param string $room Room identifier.
99- * @return array<int, array{ timestamp: int, value: mixed }> Sync updates.
100- */
101- private function get_all_updates ( string $ room ): array {
102- $ this ->room_cursors [ $ room ] = $ this ->get_time_marker () - 100 ; // Small buffer to ensure consistency.
103-
104- $ post_id = $ this ->get_storage_post_id ( $ room );
105- if ( null === $ post_id ) {
106- return array ();
86+ if ( $ meta_id ) {
87+ $ room_hash = md5 ( $ room );
88+ wp_cache_delete ( "sync_room_state_ {$ room_hash }" , 'sync ' );
10789 }
10890
109- $ updates = get_post_meta ( $ post_id , self ::SYNC_UPDATE_META_KEY , false );
110-
111- if ( ! is_array ( $ updates ) ) {
112- $ updates = array ();
113- }
114-
115- // Filter out any updates that don't have the expected structure.
116- $ updates = array_filter (
117- $ updates ,
118- static function ( $ update ): bool {
119- return is_array ( $ update ) && isset ( $ update ['timestamp ' ], $ update ['value ' ] ) && is_int ( $ update ['timestamp ' ] );
120- }
121- );
122-
123- $ this ->room_update_counts [ $ room ] = count ( $ updates );
124-
125- return $ updates ;
91+ return (bool ) $ meta_id ;
12692 }
12793
12894 /**
@@ -134,6 +100,14 @@ static function ( $update ): bool {
134100 * @return array<int, mixed> Awareness state.
135101 */
136102 public function get_awareness_state ( string $ room ): array {
103+ $ room_hash = md5 ( $ room );
104+ $ cache_key = "sync_awareness_ {$ room_hash }" ;
105+ $ cached = wp_cache_get ( $ cache_key , 'sync ' );
106+
107+ if ( is_array ( $ cached ) ) {
108+ return array_values ( $ cached );
109+ }
110+
137111 $ post_id = $ this ->get_storage_post_id ( $ room );
138112 if ( null === $ post_id ) {
139113 return array ();
@@ -145,6 +119,8 @@ public function get_awareness_state( string $room ): array {
145119 return array ();
146120 }
147121
122+ wp_cache_set ( $ cache_key , $ awareness , 'sync ' , MINUTE_IN_SECONDS );
123+
148124 return array_values ( $ awareness );
149125 }
150126
@@ -158,7 +134,25 @@ public function get_awareness_state( string $room ): array {
158134 * @return bool True on success, false on failure.
159135 */
160136 public function set_awareness_state ( string $ room , array $ awareness ): bool {
137+ $ room_hash = md5 ( $ room );
138+ $ cache_key = "sync_awareness_ {$ room_hash }" ;
139+
140+ /**
141+ * Cache the awareness state to reduce post meta reads and writes.
142+ *
143+ * Awareness is inherently ephemeral and is not critical for
144+ * content consistency, so it's acceptable for it to be stale
145+ * or missing for a short period of time.
146+ *
147+ * For sites without object caching, fall back to post meta.
148+ */
149+ wp_cache_set ( $ cache_key , $ awareness , 'sync ' , MINUTE_IN_SECONDS );
150+ if ( wp_using_ext_object_cache () ) {
151+ return true ;
152+ }
153+
161154 $ post_id = $ this ->get_storage_post_id ( $ room );
155+
162156 if ( null === $ post_id ) {
163157 return false ;
164158 }
@@ -174,8 +168,7 @@ public function set_awareness_state( string $room, array $awareness ): bool {
174168 * Gets the current cursor for a given room.
175169 *
176170 * The cursor is set during get_updates_after_cursor() and represents the
177- * point in time just before the updates were retrieved, with a small buffer
178- * to ensure consistency.
171+ * highest meta_id seen for the room's sync updates.
179172 *
180173 * @since 7.0.0
181174 *
@@ -239,17 +232,6 @@ private function get_storage_post_id( string $room ): ?int {
239232 return null ;
240233 }
241234
242- /**
243- * Gets the current time in milliseconds as a comparable time marker.
244- *
245- * @since 7.0.0
246- *
247- * @return int Current time in milliseconds.
248- */
249- private function get_time_marker (): int {
250- return (int ) floor ( microtime ( true ) * 1000 );
251- }
252-
253235 /**
254236 * Gets the number of updates stored for a given room.
255237 *
@@ -263,32 +245,83 @@ public function get_update_count( string $room ): int {
263245 }
264246
265247 /**
266- * Retrieves sync updates from a room for a given client and cursor. Updates
267- * from the specified client should be excluded.
248+ * Retrieves sync updates from a room after the given cursor.
268249 *
269250 * @since 7.0.0
270251 *
271252 * @param string $room Room identifier.
272- * @param int $cursor Return updates after this cursor.
253+ * @param int $cursor Return updates after this cursor (meta_id) .
273254 * @return array<int, mixed> Sync updates.
274255 */
275256 public function get_updates_after_cursor ( string $ room , int $ cursor ): array {
276- $ all_updates = $ this ->get_all_updates ( $ room );
277- $ updates = array ();
257+ global $ wpdb ;
278258
279- foreach ( $ all_updates as $ update ) {
280- if ( $ update ['timestamp ' ] > $ cursor ) {
281- $ updates [] = $ update ;
282- }
259+ $ room_hash = md5 ( $ room );
260+ $ state_cache_key = "sync_room_state_ {$ room_hash }" ;
261+ $ cached = wp_cache_get ( $ state_cache_key , 'sync ' );
262+
263+ if ( is_array ( $ cached ) && $ cached ['cursor ' ] <= $ cursor ) {
264+ $ this ->room_cursors [ $ room ] = $ cached ['cursor ' ];
265+ $ this ->room_update_counts [ $ room ] = $ cached ['count ' ];
266+ return array ();
267+ }
268+
269+ $ post_id = $ this ->get_storage_post_id ( $ room );
270+ if ( null === $ post_id ) {
271+ $ this ->room_cursors [ $ room ] = 0 ;
272+ $ this ->room_update_counts [ $ room ] = 0 ;
273+ return array ();
274+ }
275+
276+ // Capture the current room state first so the returned cursor is race-safe.
277+ $ stats = $ wpdb ->get_row (
278+ $ wpdb ->prepare (
279+ "SELECT COUNT(*) AS total_updates, COALESCE( MAX(meta_id), 0 ) AS max_meta_id FROM {$ wpdb ->postmeta } WHERE post_id = %d AND meta_key = %s " ,
280+ $ post_id ,
281+ self ::SYNC_UPDATE_META_KEY
282+ )
283+ );
284+
285+ $ total_updates = $ stats ? (int ) $ stats ->total_updates : 0 ;
286+ $ max_meta_id = $ stats ? (int ) $ stats ->max_meta_id : 0 ;
287+
288+ $ this ->room_update_counts [ $ room ] = $ total_updates ;
289+ $ this ->room_cursors [ $ room ] = $ max_meta_id ;
290+
291+ wp_cache_set (
292+ $ state_cache_key ,
293+ array (
294+ 'cursor ' => $ max_meta_id ,
295+ 'count ' => $ total_updates ,
296+ ),
297+ 'sync '
298+ );
299+
300+ if ( $ max_meta_id <= $ cursor ) {
301+ return array ();
283302 }
284303
285- // Sort by timestamp to ensure order.
286- usort (
287- $ updates ,
288- fn ( $ a , $ b ) => $ a ['timestamp ' ] <=> $ b ['timestamp ' ]
304+ $ rows = $ wpdb ->get_results (
305+ $ wpdb ->prepare (
306+ "SELECT meta_value FROM {$ wpdb ->postmeta } WHERE post_id = %d AND meta_key = %s AND meta_id > %d AND meta_id <= %d ORDER BY meta_id ASC " ,
307+ $ post_id ,
308+ self ::SYNC_UPDATE_META_KEY ,
309+ $ cursor ,
310+ $ max_meta_id
311+ )
289312 );
290313
291- return wp_list_pluck ( $ updates , 'value ' );
314+ if ( ! $ rows ) {
315+ return array ();
316+ }
317+
318+ $ updates = array ();
319+ foreach ( $ rows as $ row ) {
320+ $ update = maybe_unserialize ( $ row ->meta_value );
321+ $ updates [] = $ update ;
322+ }
323+
324+ return $ updates ;
292325 }
293326
294327 /**
@@ -297,33 +330,37 @@ public function get_updates_after_cursor( string $room, int $cursor ): array {
297330 * @since 7.0.0
298331 *
299332 * @param string $room Room identifier.
300- * @param int $cursor Remove updates with markers < this cursor.
333+ * @param int $cursor Remove updates with meta_id < this cursor.
301334 * @return bool True on success, false on failure.
302335 */
303336 public function remove_updates_before_cursor ( string $ room , int $ cursor ): bool {
337+ global $ wpdb ;
338+
304339 $ post_id = $ this ->get_storage_post_id ( $ room );
305340 if ( null === $ post_id ) {
306341 return false ;
307342 }
308343
309- $ all_updates = $ this ->get_all_updates ( $ room );
344+ $ deleted_rows = $ wpdb ->query (
345+ $ wpdb ->prepare (
346+ "DELETE FROM {$ wpdb ->postmeta } WHERE post_id = %d AND meta_key = %s AND meta_id < %d " ,
347+ $ post_id ,
348+ self ::SYNC_UPDATE_META_KEY ,
349+ $ cursor
350+ )
351+ );
310352
311- // Remove all updates for the room and re-store only those that are newer than the cursor.
312- if ( ! $ this ->with_suspended_posts_last_changed_update ( fn () => delete_post_meta ( $ post_id , self ::SYNC_UPDATE_META_KEY ) ) ) {
353+ if ( false === $ deleted_rows ) {
313354 return false ;
314355 }
315356
316- // Re-store envelopes directly to avoid double-wrapping by add_update().
317- $ add_result = true ;
318- foreach ( $ all_updates as $ envelope ) {
319- if ( $ add_result && $ envelope ['timestamp ' ] >= $ cursor ) {
320- $ add_result = $ this ->with_suspended_posts_last_changed_update (
321- fn () => (bool ) add_post_meta ( $ post_id , self ::SYNC_UPDATE_META_KEY , $ envelope , false )
322- );
323- }
357+ if ( $ deleted_rows > 0 ) {
358+ wp_cache_delete ( $ post_id , 'post_meta ' );
359+ $ room_hash = md5 ( $ room );
360+ wp_cache_delete ( "sync_room_state_ {$ room_hash }" , 'sync ' );
324361 }
325362
326- return $ add_result ;
363+ return true ;
327364 }
328365
329366 /**
0 commit comments