Learn
Lesson · Replication and Consistency · Consistency, counted

Conflict Resolution, Counted

Given concurrent updates to one value, last-writer-wins keeps the register of whichever replica holds the largest timestamp and discards the rest: on a four-update trace worth 10 it returns 7, throwing away 2 updates worth 3. A counter CRDT keeps one component per replica, merges by elementwise maximum and sums, returning 10 and discarding none. Accurate clocks change which update survives, never how many do.

trace A:3:+2 B:5:+3 C:4:+1 B:9:+4 all four concurrent true total, every update applied 10 last-writer-wins: B holds t = 9, so B's register wins 7 2 updates discarded, worth 3 counter CRDT: {A = 2, B = 7, C = 1}, summed 10, discarding 0 merge is elementwise max: commutative, associative, idempotent
Two merges on one trace, and what each throws away

Core ideas

What this lesson establishes

One rule keeps a replica, the other keeps a component per replica, and the difference is not a matter of clock quality.

1

Last-writer-wins keeps one replica's register and drops the rest

The rule picks the update with the largest timestamp and takes the state of the replica that made it. That replica's register contains its own updates and nothing else, so every update made anywhere else during the partition is gone. On the trace here B holds t = 9, B's register is 3 + 4 = 7, and A's +2 and C's +1 are discarded — two updates worth three.

2

Accurate clocks change which update survives, not how many

Make every timestamp perfectly correct and last-writer-wins still keeps one replica's register and still discards the others. All that changes is which replica is chosen. The count of discarded updates is a function of how many replicas were updated concurrently, and clock quality does not appear in it anywhere. This is the misconception the lesson exists to break.

3

The counter keeps one component per replica and merges by maximum

State a counter as a vector of per-replica totals. A replica increments only its own component; merging two states takes the elementwise maximum; the value is the sum. Elementwise maximum is commutative, associative and idempotent, so every replica converges on the same value whatever order gossip arrives in, however often, and however many times a message is redelivered.

The material

Two merges, one trace, and the count each one loses

What last-writer-wins does with concurrent updates, what a counter CRDT does instead, and the three properties that make the second one safe.

Definition · Concurrent updates, last-writer-wins, and a counter CRDT

Updates are concurrent when no chain of messages orders them — the condition “Vector Clocks and Concurrency” makes decidable. Last-writer-wins resolves them by keeping the state of the replica holding the largest timestamp. A counter CRDT holds one count per replica, merges two states by elementwise maximum and reports the sum.

Both rules produce a single answer from conflicting states, which is what a merge has to do. The difference is what each one is willing to throw away to get there, and that is a number rather than a matter of taste.

four concurrent updates; no replica has seen any other's replica timestamp delta under LWW CRDT component A 3 +2 discarded A = 2 B 5 +3 kept B = 3 + 4 = 7 C 4 +1 discarded C = 1 B 9 +4 kept true total 2 + 3 + 1 + 4 = 10 last-writer-wins B holds the largest timestamp, so B's register wins = 7 2 updates discarded, worth 3 counter CRDT {A = 2, B = 7, C = 1}, summed = 10 0 updates discarded

Every one of those four updates was acknowledged to a client. Under last-writer-wins two of them stop existing at merge time, with no error raised anywhere, and the value a reader sees afterwards is 7 for a sequence of increments totalling 10. That is not a bug in the implementation; it is what the rule says to do.

The clocks are not the problem

The standard response to this is that the timestamps must be made accurate. Suppose they are exact to the nanosecond. The rule still keeps one replica's register and still discards every update made elsewhere; the only thing that changes is which replica gets to be the winner. What is being lost is not precision in the ordering, it is the fact that there was no ordering to be precise about — the updates were concurrent, and last-writer-wins is a rule for picking one arbitrarily.

The deeper problem is that last-writer-wins treats an increment as a state to be overwritten rather than an operation to be applied. Once +2 has become “A's register reads 2”, there is no way to combine it with “B's register reads 7” except by choosing. The counter avoids the choice by never merging the two into one slot in the first place.

the merge is elementwise maximum on the component vectors commutative A ∨ B = B ∨ A associative (A ∨ B) ∨ C = A ∨ (B ∨ C) idempotent (A ∨ B) ∨ (A ∨ B) = A ∨ B so every replica converges on the same value regardless of the order gossip arrives in — commutativity how the exchanges are grouped — associativity how often a message is redelivered — idempotence
Example · Why each of the three laws is doing work

Drop commutativity and the value depends on which replica gossiped first, so two replicas that exchanged the same information in opposite orders disagree for ever. Drop associativity and it depends on how the exchanges were grouped, which is a topology detail nobody controls. Drop idempotence and a redelivered message — the thing every network does under retry — double counts. Elementwise maximum has all three, which is why the convergence needs no coordination at all: no ordering, no exactly-once delivery, no leader.

A grow-only counter is the easy case and it should be read as an existence proof rather than a general solution. Sets with removals, registers holding documents, and ordered sequences all need more machinery, and some conflicts are genuinely business decisions that no merge rule should be making. What transfers is the question: for this data type, what does my merge rule discard, and can I count it?

Interactive

Conflict resolution, counted

What last-writer-wins throws away, and what a counter CRDT keeps

Conflict resolution, countedlast-writer-wins discards; the counter CRDT does not
discarded by last-writer-winskept by boththe CRDT’s per-replica countsthe true total

Method

Counting what a merge rule discards

The trace first, the concurrency second, and only then the merge — because a rule applied to updates that were not concurrent discards nothing and proves nothing.

1

Write the trace as replica, timestamp and operation

One line per acknowledged update. If the updates are increments, keep them as increments rather than as the resulting values — the distinction between an operation and a state is where the whole difference lives.

2

Establish that the updates really are concurrent

Use the vector-clock comparison: if one update's vector dominates another's, they are ordered and there is no conflict to resolve. A merge rule is only tested by the pairs that are genuinely incomparable.

3

Apply last-writer-wins and count both quantities

Find the largest timestamp, take that replica's state, and count the updates that are not in it and the value they carried. Report both: “two updates, worth three” says more than either half alone.

4

Apply the type's own merge and check it against the true total

For a counter, elementwise maximum then sum, compared with the sum of all the increments. If they agree, the rule discards nothing; if they do not, the difference is what it discards and you now know its size.

What each rule throws away

Score 0 / 0

One trace, two merges

Three replicas are partitioned from one another and each accepts writes. Four increments are acknowledged during the partition. The partition heals, the replicas gossip, and the question is what the counter reads afterwards — under each of the two rules on offer.

the trace, replica:timestamp:delta A:3:+2 B:5:+3 C:4:+1 B:9:+4 no replica has seen any other's update, so all four are concurrent true total 2 + 3 + 1 + 4 = 10 last-writer-wins largest timestamp is 9, held by B B's register contains B's own updates only: 3 + 4 = 7 value after merge 7 discarded: A's +2 and C's +1 2 updates, worth 3 counter CRDT components A = 2 B = 3 + 4 = 7 C = 1 merge elementwise maximum, in any order value 2 + 7 + 1 = 10 discarded 0 now make every clock exact and re-run last-writer-wins the winner may change from B to whoever truly wrote last the count discarded does not: still 2 updates

Seven against ten, for a rule that raises no error and is the default in a great deal of software. The thirty per cent is not an edge case either — it is what happens whenever three replicas take writes and two of them are not the one holding the largest timestamp.

The last block is the one to argue with somebody about. Improving the clocks is the reflex fix and it changes the identity of the survivor without changing the arithmetic: two updates are still discarded, because two replicas other than the winner accepted writes. Clock accuracy is the answer to a question about ordering, and these updates were not ordered.

For a faded rehearsal, add a fifth update A:11:+5 to the trace. The supplied first move is that the largest timestamp is now 11 and it is held by A, so A's register wins. Work out the new last-writer-wins value, the number and worth of the updates it discards, and the CRDT's value — then say, before checking in the lab, whether the number discarded went up or down and why.

Common mistakes

1
Believing better clocks make last-writer-wins safeThey make it arbitrary in a more defensible way and nothing else. The number of discarded updates is set by how many replicas accepted concurrent writes, not by the precision of the timestamps. If losing acknowledged updates is unacceptable, the fix is a merge that does not choose, not a clock that chooses better.
2
Reporting only one of the two counts“Two updates discarded” and “worth three” answer different questions and both are needed. A rule that drops many tiny updates and one that drops a single large one look identical in one number and completely different in the other, and which of them matters depends on the data rather than on the merge.
3
Generalising the counter to every data typeA grow-only counter merges cleanly because increments commute and the elementwise maximum is a least upper bound. Removals, ordered lists and documents are harder, and some conflicts — two people editing the same sentence, two withdrawals from one balance — are business decisions that no automatic merge should quietly make. The transferable move is to count what your rule discards, not to reach for this particular rule.

Check your understanding

✓
Finish when “we use last-writer-wins” prompts you to ask how many acknowledged updates that discards.

You should be able to establish that a set of updates is concurrent, compute the last-writer-wins value together with the number and worth of what it drops, compute a counter CRDT's value from its components, state the three merge laws and what each one buys, and say why clock accuracy does not change the count.

Note: That closes the course. Replication costs read capacity you get, write capacity you do not, a latency that is an order statistic rather than a sum, and a staleness that is a tail; quorums buy overlap by counting and nothing more than overlap; and “consistent” turns out to be three different countable questions with three different answers. The next course, “Partitioning and Load Balancing”, stops asking whether the copies agree and starts asking how the data was split between them in the first place — which is balls into bins, and skewed.

Kept in this browser only, for you. Nothing is sent anywhere and nothing checks it. Carry it to another device.

Feedback and recommendations

Something to change about this lesson? Write it down, tick it off when it is handled, and export the list when you want to act on it. Kept in this browser only: nothing is sent anywhere, and nobody is notified.

No recommendations yet for this lesson.