Chapter 8 — CRDT: Multi-Node Local Updates and Merge
Chapter objective: After this chapter, a developer can model multi-node local increments and decrements with a PN Counter and explain the algebraic properties required by its merge function.
Learning objectives
- Explain state, update, version vector, and delta.
- Distinguish CRDT merging from Register winner selection.
- Explain why commutativity, associativity, and idempotence matter.
- Identify business quantities that a counter alone cannot represent.
Prerequisites
- The concurrent candidates in Chapter 6 are understood.
- Request counts may differ temporarily, while duplicate merges cannot inflate them.
Case progress
The east node records three requests locally and the west node records two. After recovery, both sides reach five without taking a central lock for every request.
Merge state instead of choosing one winner
The core DsmCrdtCollection operations are
apply(entity) and state().
versionVector() describes observed node progress, and
deltaSince(vector) produces state for a lagging peer.
counter.apply(new PnCounterUpdate("east-1", metadata, "east", 3, 0));
counter.apply(new PnCounterUpdate("west-1", metadata, "west", 2, 0));
long total = counter.state().value();The pinned source defines the exact constructor parameters. The important structure is an independent contribution per node. Merge retains monotonic information for each component and then calculates the total. Receiving the same state twice does not add three twice.
Three required properties
- Commutativity:
merge(A, B) = merge(B, A), so message order does not change the result. - Associativity:
merge(merge(A, B), C) = merge(A, merge(B, C)), so repair batching does not change the result. - Idempotence:
merge(A, A) = A, so retries and duplicate delivery do not amplify state.
These properties describe merge. They do not make every business operation commutative. Balance deductions, inventory bounds, and at-most-once external effects need additional contracts.
Counterexample and fault injection
Suppose merge(left, right) = left.total + right.total
stores the result as new state. Repeated delivery of the same snapshot
doubles the total repeatedly. A correct PN Counter retains per-node
positive and negative components, or an equivalent monotonic structure,
instead of re-adding an already aggregated total.
Experiment
cd submodule/dsm
mvn -q -pl dsm-integration-test -am \
-Dtest=RuntimeIntegrationTest#oneRuntimeMergesCrdtCounterUpdatesThroughTheRuntimeFacade \
-Dsurefire.failIfNoSpecifiedTests=false testThen use crdt-merge-cases.json to calculate the results
for reversed order, regrouping, and duplicate input.
Experiment acceptance card
| Field | Content |
|---|---|
| Command | The Maven command above;
node --test tests/chapter-assets.test.mjs |
| Input or fault | Two node components; order changes and duplicate input |
| Observable result | The Runtime facade returns the merged count; the expected results cover all three properties |
| Evidence level | E2: in-Runtime merge test plus book algebra cases |
| This experiment does not prove | Two-node network propagation, business deduplication, exactly-once billing, or production capacity |
Review
- A CRDT merges contributions instead of retaining only one candidate.
- A version vector records observed progress; a delta can target a lagging peer.
- Merge is commutative, associative, and idempotent.
- Technical mergeability does not establish a business invariant.
Next
Chapter 9 places Register, Lease, and CRDT in one business-invariant decision tree for the first design review.