watershed Collaborative data structures for Gleam

← watershed · Data structures / Counters

Counters

Numbers that many hands move at once.

Counters are the gentlest introduction to convergence, because addition does not care about order. Send each change as a signed delta instead of a new total, and simultaneous edits simply add up; there is nothing to overwrite.

They climb in guarantee: a server-sequenced scalar, then a grow-only lattice that stays correct even when a delta is delivered twice, then a signed counter built from two of those lattices so it can move in both directions offline.

SharedCounter

DDS
counter_kernel

One number that many people can add to at once, without conflicts.

A shared counter holds a single integer. Each client submits a signed delta (+5, −1) rather than a new absolute value. Addition commutes, so the server can sequence deltas in any order and every replica lands on the same total.

Shipping the change instead of the result is the whole trick: two clients incrementing at the same instant never clobber one another. A local delta renders immediately as an unsequenced Δ beside the committed value. When the sequencer stamps it, the pending delta folds into the total.

Best for

  • Live tallies: votes, reactions, attendees, items in a shared cart
  • Running totals where concurrent +/− must never be lost and order does not matter
  • Inventory or quota counters that many clients adjust at once
Merge rule
everyone sends +/− changes instead of overwriting, so simultaneous edits just add up
Optimistic behavior
your change shows next to the confirmed total right away
Summary shape
only one number needs saving, since every change is an add
Model
Distributed data structure: converges by server order

GCounter

CRDT
g_counter_kernel

A count-up-only counter that stays correct even if an update arrives twice.

A grow-only counter keeps one monotone count per replica. Client A only ever raises A's slot; client B only B's. The visible value is the sum of every slot.

Merging two states takes the larger count for each replica. That pairwise maximum is idempotent, so a delta delivered twice (after a reconnect, say) changes nothing the second time. The price: a G-counter can only increase. It has no decrement.

Best for

  • Idempotent counters over at-least-once or unreliable delivery
  • Distributed metrics where the same increment may arrive more than once
  • The building block beneath the PN counter and other lattice counters
Merge rule
each client keeps its own tally; the totals combine safely even if a change arrives twice
Optimistic behavior
your increment overlays the total in magenta until it's confirmed
Summary shape
each client's running tally reloads intact
Model
Conflict-free replicated data type: converges by merge

PnCounter

CRDT
pn_counter_kernel

A counter that goes up and down and survives duplicate or out-of-order updates.

A single G-counter can only grow, which rules out decrements. A PN counter restores them by pairing two G-counters: a positive ledger and a negative ledger, each keyed by replica. The value you read is the positive sum minus the negative sum.

Each half is a max-merge CRDT, so the whole thing is order- and duplicate-independent. A decrement re-delivered after reconnect is absorbed idempotently. The demo frames this as a cut-and-fill earthwork balance and lets you re-send a sequenced delta to watch the merge shrug it off. An op-based counter needs the runtime's sequence-number dedup to survive that; here the merge alone is enough.

Best for

  • Counters that go up and down under unreliable delivery (reserve/release, like/unlike)
  • Collaborative budgets or capacity that both grows and shrinks concurrently
  • Offline-first counters that reconcile on reconnect without dropping edits
Merge rule
keeps separate up and down tallies, so it can move both ways and still shrug off duplicates
Optimistic behavior
adds and subtractions can be re-sent without being counted twice
Summary shape
per-client tallies survive reconnect and repeated delivery
Model
Conflict-free replicated data type: converges by merge