mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-25 17:03:52 +00:00
fix(docs): correct late-joiner replay section to describe UUIDv7 filtering
The documentation incorrectly described "vector clock dominance" filtering, but the actual implementation uses UUIDv7 timestamp comparison. Updated Section 2c to accurately reflect the implementation and explain why UUIDv7 is used (SYNC_IMPORT operations break vector clock causality detection).
This commit is contained in:
parent
9f1d68ea40
commit
f89feed8b7
1 changed files with 31 additions and 27 deletions
|
|
@ -411,9 +411,9 @@ flowchart TB
|
|||
2. **Efficiency**: Snapshot endpoint is designed for large payloads and stores state directly
|
||||
3. **Server-Side Handling**: Server creates a synthetic operation record for audit purposes
|
||||
|
||||
## 2c. Late-Joiner Replay with Vector Clock Dominance ✅ IMPLEMENTED
|
||||
## 2c. Late-Joiner Replay with UUIDv7 Timestamp Filter ✅ IMPLEMENTED
|
||||
|
||||
When a client receives a SYNC_IMPORT (full state from another client), it must replay any local synced operations that happened "after" the import's vector clock. This ensures local work isn't lost when receiving a full state snapshot.
|
||||
When a client receives a SYNC_IMPORT (full state from another client), it must replay any local synced operations created **after** the import (by UUIDv7 timestamp). This ensures local work isn't lost when receiving a full state snapshot.
|
||||
|
||||
**Implementation Status:** Complete. See `OperationLogSyncService._replayLocalSyncedOpsAfterImport()`.
|
||||
|
||||
|
|
@ -442,35 +442,35 @@ sequenceDiagram
|
|||
Note over B: Problem: Op3, Op4 were already synced!<br/>If we just apply SYNC_IMPORT, we lose B's work
|
||||
```
|
||||
|
||||
### The Solution: Vector Clock Dominance Filter
|
||||
### The Solution: UUIDv7 Timestamp Filter
|
||||
|
||||
Before replaying local synced ops after a SYNC_IMPORT, we filter out ops that are "dominated" by the SYNC_IMPORT's vector clock. An op is dominated if its vector clock is `LESS_THAN` the SYNC_IMPORT's clock - meaning the op's state is already captured in the imported snapshot.
|
||||
Before replaying local synced ops after a SYNC_IMPORT, we filter out ops that were created **before** the SYNC_IMPORT (by comparing UUIDv7 IDs, which embed timestamps). Ops created before the import are already captured in the imported snapshot.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph Input["SYNC_IMPORT Received"]
|
||||
SI["SYNC_IMPORT<br/>vectorClock: A=10, B=5"]
|
||||
SI["SYNC_IMPORT<br/>id: 019abc... (T=1702900000)"]
|
||||
end
|
||||
|
||||
subgraph LocalOps["Local Synced Operations"]
|
||||
Op1["Op1: B=1<br/>LESS_THAN → dominated"]
|
||||
Op2["Op2: A=5, B=3<br/>LESS_THAN → dominated"]
|
||||
Op3["Op3: B=6<br/>GREATER_THAN → NOT dominated"]
|
||||
Op4["Op4: A=10, B=5, C=1<br/>CONCURRENT → NOT dominated"]
|
||||
Op1["Op1: id=019aa...<br/>T=1702890000 → BEFORE"]
|
||||
Op2["Op2: id=019ab...<br/>T=1702895000 → BEFORE"]
|
||||
Op3["Op3: id=019abd...<br/>T=1702905000 → AFTER"]
|
||||
Op4["Op4: id=019abe...<br/>T=1702910000 → AFTER"]
|
||||
end
|
||||
|
||||
subgraph Filter["Vector Clock Comparison"]
|
||||
Check["Compare each op's clock<br/>with SYNC_IMPORT clock"]
|
||||
subgraph Filter["UUIDv7 Timestamp Comparison"]
|
||||
Check["Compare each op.id<br/>with SYNC_IMPORT.id"]
|
||||
end
|
||||
|
||||
subgraph Result["Ops to Replay"]
|
||||
Replay["Only Op3 and Op4<br/>not dominated"]
|
||||
Replay["Only Op3 and Op4<br/>created after import"]
|
||||
end
|
||||
|
||||
SI --> Check
|
||||
LocalOps --> Check
|
||||
Check --> |"LESS_THAN"| Skip["Skip - already in snapshot"]
|
||||
Check --> |"Otherwise"| Replay
|
||||
Check --> |"op.id < import.id"| Skip["Skip - created before import"]
|
||||
Check --> |"op.id >= import.id"| Replay
|
||||
|
||||
style Op1 fill:#ffcdd2,stroke:#c62828
|
||||
style Op2 fill:#ffcdd2,stroke:#c62828
|
||||
|
|
@ -480,14 +480,12 @@ flowchart TD
|
|||
style Replay fill:#e8f5e9,stroke:#2e7d32
|
||||
```
|
||||
|
||||
### Vector Clock Comparison Results
|
||||
### UUIDv7 Comparison Results
|
||||
|
||||
| Comparison | Meaning | Action |
|
||||
| -------------- | ------------------------------ | ---------------------------------- |
|
||||
| `LESS_THAN` | Op happened-before SYNC_IMPORT | Skip (state already captured) |
|
||||
| `EQUAL` | Same causal history | Replay (edge case, safe to replay) |
|
||||
| `GREATER_THAN` | Op happened-after SYNC_IMPORT | Replay (newer than snapshot) |
|
||||
| `CONCURRENT` | Independent changes | Replay (may have unique changes) |
|
||||
| Comparison | Meaning | Action |
|
||||
| -------------------- | -------------------------------- | ------------------------------------- |
|
||||
| `op.id < import.id` | Op created before SYNC_IMPORT | Skip (state already captured) |
|
||||
| `op.id >= import.id` | Op created at same time or after | Replay (newer than or equal snapshot) |
|
||||
|
||||
### Implementation Details
|
||||
|
||||
|
|
@ -502,10 +500,12 @@ const localSyncedOps = allEntries.filter((entry) => {
|
|||
if (entry.op.opType === OpType.SyncImport || entry.op.opType === OpType.BackupImport)
|
||||
return false;
|
||||
|
||||
// Must NOT be dominated by the SYNC_IMPORT's vector clock
|
||||
const comparison = compareVectorClocks(entry.op.vectorClock, syncImportClock);
|
||||
if (comparison === VectorClockComparison.LESS_THAN) {
|
||||
return false; // Skip - state already captured in SYNC_IMPORT
|
||||
// Must be created AFTER the SYNC_IMPORT (by UUIDv7 timestamp)
|
||||
// NOTE: We use UUIDv7 comparison instead of vector clock comparison
|
||||
// because imports with isForceConflict=true create a fresh vector clock
|
||||
// with a new client ID, breaking vector clock causality detection.
|
||||
if (entry.op.id < syncImportOp.id) {
|
||||
return false; // Skip - created before SYNC_IMPORT
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
|
@ -515,8 +515,12 @@ const localSyncedOps = allEntries.filter((entry) => {
|
|||
|
||||
- Only filters local ops (created by this client)
|
||||
- Only considers synced ops (accepted by server)
|
||||
- Uses vector clock comparison to determine dominance
|
||||
- `LESS_THAN` means dominated (skip), all other results mean not dominated (replay)
|
||||
- Uses **UUIDv7 ID comparison** (not vector clocks) to determine temporal order
|
||||
- Ops with ID < import ID are skipped (created before import)
|
||||
|
||||
**Why UUIDv7 Instead of Vector Clocks?**
|
||||
|
||||
SYNC_IMPORT operations with `isForceConflict=true` create a fresh vector clock with a new client ID. This breaks vector clock causality detection because the import's clock has no causal relationship with existing local operations. UUIDv7 IDs embed millisecond-precision timestamps, providing reliable "created before/after" comparison that works regardless of vector clock semantics.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue