Skip to content

Commit a3d84ec

Browse files
committed
Use compaction request as signal for full state update
1 parent 6106e72 commit a3d84ec

File tree

4 files changed

+23
-49
lines changed

4 files changed

+23
-49
lines changed

lib/compat/wordpress-7.0/class-wp-http-polling-sync-server.php

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -434,17 +434,14 @@ private function get_updates( string $room, int $client_id, int $cursor, bool $i
434434
}
435435

436436
// Determine if this client should perform compaction.
437-
$compaction_request = null;
438-
if ( $is_compactor && $total_updates > self::COMPACTION_THRESHOLD ) {
439-
$compaction_request = $updates_after_cursor;
440-
}
437+
$should_compact = $is_compactor && $total_updates > self::COMPACTION_THRESHOLD;
441438

442439
return array(
443-
'compaction_request' => $compaction_request,
444-
'end_cursor' => $this->storage->get_cursor( $room ),
445-
'room' => $room,
446-
'total_updates' => $total_updates,
447-
'updates' => $typed_updates,
440+
'end_cursor' => $this->storage->get_cursor( $room ),
441+
'room' => $room,
442+
'should_compact' => $should_compact,
443+
'total_updates' => $total_updates,
444+
'updates' => $typed_updates,
448445
);
449446
}
450447
}

packages/sync/src/providers/http-polling/README.md

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ Updates are tagged with a type to enable different server-side handling:
5555
| `sync_step1` | State vector announcement | Stored, delivered to other clients |
5656
| `sync_step2` | Missing updates response | Stored, delivered to other clients |
5757
| `update` | Regular document change | Stored until compacted |
58-
| `compaction` | Merged updates via Y.mergeUpdates | Clears older updates, then stored |
58+
| `compaction` | Full document state via Y.encodeStateAsUpdate | Clears older updates, then stored |
5959

6060
## Data Flow
6161

@@ -109,13 +109,9 @@ To prevent unbounded message growth, the server coordinates compaction:
109109

110110
1. **Threshold reached**: Server detects >50 stored updates for a room
111111
2. **Client selection**: Server nominates the lowest active client ID
112-
3. **Compaction request**: Server sends all updates to the nominated client via `compaction_request`
113-
4. **Client merges**: Uses `Y.mergeUpdates()` to combine all updates, preserving operation metadata
114-
5. **Client sends compaction**: The merged update replaces older updates on the server
115-
116-
**Why Y.mergeUpdates instead of Y.encodeStateAsUpdate?**
117-
118-
`Y.mergeUpdates()` preserves the original operation metadata (client IDs, logical clocks). This allows Yjs to correctly deduplicate when a compaction is applied to a document that already contains some of those operations. Using `Y.encodeStateAsUpdate()` would create fresh metadata, causing content duplication on clients that already have overlapping state.
112+
3. **Compaction request**: Server sends `should_compact: true` to the nominated client
113+
4. **Client encodes**: Uses `Y.encodeStateAsUpdate()` to capture the full document state
114+
5. **Client sends compaction**: The encoded state replaces older updates on the server
119115

120116
### 5. Awareness
121117

@@ -164,7 +160,7 @@ Single endpoint for bidirectional sync including awareness. Clients send their u
164160
"updates": [
165161
{ "type": "update", "data": "base64-encoded-yjs-update" }
166162
],
167-
"compaction_request": null
163+
"should_compact": false
168164
}
169165
]
170166
}
@@ -177,7 +173,7 @@ Single endpoint for bidirectional sync including awareness. Clients send their u
177173
- `after`: Cursor timestamp; only receive updates newer than this
178174
- `awareness`: Client's awareness state (or null to disconnect)
179175
- `end_cursor`: New cursor to use in next request
180-
- `compaction_request`: Array of all updates if this client should compact (null otherwise)
176+
- `should_compact`: Boolean indicating whether this client should compact
181177
- `updates`: Array of typed updates with base64-encoded Yjs data
182178

183179
## Permissions

packages/sync/src/providers/http-polling/polling-manager.ts

Lines changed: 10 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ interface RegisterRoomOptions {
5050

5151
interface RoomState {
5252
clientId: number;
53+
createCompactionUpdate: () => SyncUpdate;
5354
endCursor: number;
5455
localAwarenessState: LocalAwarenessState;
5556
log: LogFunction;
@@ -62,31 +63,6 @@ interface RoomState {
6263

6364
const roomStates: Map< string, RoomState > = new Map();
6465

65-
/**
66-
* Create a compaction update by merging existing updates. This preserves
67-
* the original operation metadata (client IDs, logical clocks) so that
68-
* Yjs deduplication works correctly when the compaction is applied.
69-
*
70-
* @param updates The updates to merge
71-
*/
72-
function createCompactionUpdate( updates: SyncUpdate[] ): SyncUpdate {
73-
// Extract only compaction and update types for merging (skip sync-step updates).
74-
// Decode base64 updates to Uint8Array for merging.
75-
const mergeable = updates
76-
.filter( ( u ) =>
77-
[ SyncUpdateType.COMPACTION, SyncUpdateType.UPDATE ].includes(
78-
u.type
79-
)
80-
)
81-
.map( ( u ) => base64ToUint8Array( u.data ) );
82-
83-
// Merge all updates while preserving operation metadata.
84-
return createSyncUpdate(
85-
Y.mergeUpdates( mergeable ),
86-
SyncUpdateType.COMPACTION
87-
);
88-
}
89-
9066
/**
9167
* Create sync step 1 update (announce our state vector).
9268
*
@@ -306,11 +282,11 @@ function poll(): void {
306282
roomState.updateQueue.addBulk( responseUpdates );
307283

308284
// Respond to compaction requests from server. The server asks only one
309-
// client at a time to compact (lowest active client ID). We merge the
310-
// received updates (the server has given us everything it has).
311-
if ( room.compaction_request ) {
285+
// client at a time to compact (lowest active client ID). We encode our
286+
// full document state to replace all prior updates on the server.
287+
if ( room.should_compact ) {
312288
roomState.updateQueue.add(
313-
createCompactionUpdate( room.compaction_request )
289+
roomState.createCompactionUpdate()
314290
);
315291
}
316292
} );
@@ -387,6 +363,11 @@ function registerRoom( {
387363

388364
const roomState: RoomState = {
389365
clientId: doc.clientID,
366+
createCompactionUpdate: () =>
367+
createSyncUpdate(
368+
Y.encodeStateAsUpdate( doc ),
369+
SyncUpdateType.COMPACTION
370+
),
390371
endCursor: 0,
391372
localAwarenessState: awareness.getLocalState() ?? {},
392373
log,

packages/sync/src/providers/http-polling/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ interface SyncEnvelopeFromClient {
3131

3232
interface SyncEnvelopeFromServer {
3333
awareness: AwarenessState;
34-
compaction_request?: SyncUpdate[];
3534
end_cursor: number; // use as `after` in next request
35+
should_compact?: boolean;
3636
room: string;
3737
updates: SyncUpdate[];
3838
}

0 commit comments

Comments
 (0)