Two gauge houses at one lock keep the day's boats-locked
tally in one shared map cell. Run the race and watch a boat vanish,
then run the fix.
map_kernel · boats-locked Upstream house
Client ADownstream house
Client BOne boat lost. LWW overwrote a read-modify-write; it never added.
Fix one · make the writes commute
Nothing is wrong with the map: a counter just is not a single mutable
cell. Give each replica its own key and sum them, and the two writes
never contend. The same race now converges on the right total,
through the same map_kernel. This layout (one
positive-only column per replica) is the shape of a G-counter. A true
G-counter also merges those columns by pairwise maximum, so duplicate
delivery is idempotent.
map_kernel · boats-locked/a + boats-locked/b Upstream house
Client ADownstream house
Client BBoth boats counted. No shared cell, no lost update.
Fix two · ship the delta
Watershed's SharedCounter goes
further: the op is the delta. A client never reads the tally to
write it back. It sends increment(+1), or
increment(−1) for a correction, and signed deltas sum the
same in any order. This rig runs the compiled
counter_kernel, the SharedCounter engine itself, not a
map at all.
counter_kernel · boats-locked Upstream house
Client ADownstream house
Client B
Every delta counted: two +1s and a −1
correction, in whatever order the sequencer picked.
This demonstration needs JavaScript to run the live kernel. The takeaway: storing a counter in a last-write-wins map loses concurrent increments; use a commutative counter (watershed's SharedCounter) instead.