From a147ec07304d31c0a491ba7e418a83accb5596a1 Mon Sep 17 00:00:00 2001
From: robonen
- Why one rule for everything?
- LwwRegister uses
- compareOpId to pick the surviving value;
- Rga uses it to break ties between concurrent inserts at
- the same position; MarkStore uses it to decide which
+
+ Why one rule for everything?
+ LwwRegister uses
+ compareOpId to pick the surviving value;
+ Rga uses it to break ties between concurrent inserts at
+ the same position; MarkStore uses it to decide which
formatting wins per character. One total order, applied consistently, is what turns a pile of
independent primitives into a coherent, converging system.
+
Density matters.
- VersionVector only works because clocks arrive without
- gaps. If you generate ids with a raw LamportClock, deliver
- them in order per site (the Replica's causal buffer does
+ VersionVector only works because clocks arrive without
+ gaps. If you generate ids with a raw LamportClock, deliver
+ them in order per site (the Replica's causal buffer does
this for you) so a single high-water mark per site can stand in for the full set of seen ops.
+
Order of application doesn't change the result. A replica can integrate operations as they arrive, in whatever sequence the network delivers them.
+
Applying the same operation twice is the same as applying it once. Redelivery and retries are safe; version vectors make them free.
+
Same set of operations, same final state — full stop. Two replicas that have seen the same ops are byte-for-byte identical.
diff --git a/core/crdt/docs/02-primitives.vue b/core/crdt/docs/02-primitives.vue index 77e29a4..d9b21f0 100644 --- a/core/crdt/docs/02-primitives.vue +++ b/core/crdt/docs/02-primitives.vue @@ -198,33 +198,33 @@ store.resolve(order).map(m => m.get('strong')); // [true, true, true, true]`;
- LwwRegister and
- LwwMap — single values and keyed maps where the
+
+ LwwRegister and
+ LwwMap — single values and keyed maps where the
write with the highest op id wins.
- keyBetween /
- keysBetween — fractional indexing to place or move
+
+ keyBetween /
+ keysBetween — fractional indexing to place or move
an item with a single string key.
- Rga — a replicated growable array: an ordered
+
+ Rga — a replicated growable array: an ordered
sequence CRDT with tombstones and a deterministic insert tie-break.
- MarkStore — lightweight Peritext formatting spans
+
+ MarkStore — lightweight Peritext formatting spans
anchored to character op ids, resolved per character by highest op id.
- Why keep tombstones? If a delete simply dropped the entry,
- a concurrent set arriving afterward would resurrect
+
+ Why keep tombstones? If a delete simply dropped the entry,
+ a concurrent set arriving afterward would resurrect
the key — the two replicas would disagree on whether it exists. Retaining the delete as a
- timestamped tombstone lets compareOpId decide the
+ timestamped tombstone lets compareOpId decide the
winner deterministically, the same way it does for live values.
+
Heads up:
- keyBetween requires lower < upper
+ keyBetween requires lower < upper
and throws otherwise. Two replicas independently generating a key between the
same neighbors can produce identical keys; pair the key with the item's op id as a
secondary sort to keep ordering deterministic, or let
@@ -366,14 +366,14 @@ store.resolve(order).map(m => m.get('strong')); // [true, true, true, true]`;
- Garbage collection. Tombstones accumulate. When every
- replica has fully synced and nothing is in flight, gc(stable, keep?)
+
+ Garbage collection. Tombstones accumulate. When every
+ replica has fully synced and nothing is in flight, gc(stable, keep?)
drops deleted nodes whose insert is covered by a stable
keep to protect ids still
+ integrate — and pass keep to protect ids still
referenced elsewhere, such as mark span endpoints.
- You could swap the two receive lines, run them
+
+ You could swap the two receive lines, run them
repeatedly, or interleave them with more edits — the result is the same. Each side only ever
- adds ops it hasn't seen, and compareOpId places
+ adds ops it hasn't seen, and compareOpId places
each op in its deterministic position regardless of arrival order. That is convergence,
and the property tests assert it across randomized schedules.
+
Version vectors assume each site's clocks are dense (1, 2, 3, …). That holds automatically
- when ids come from Replica.nextId(). If you mint
+ when ids come from Replica.nextId(). If you mint
ids yourself, never skip a value for a site — a gap would make
- delta believe a missing op was already delivered.
+ delta believe a missing op was already delivered.
Spin up two fresh replicas to start editing.
+Spin up two fresh replicas to start editing.