Distributed Counters
This cookbook shows how to use a CRDT collection for cluster-wide counters and convergent aggregate state.
When This Pattern Fits
Use this pattern when:
- many nodes should update locally
- you do not want a single writer bottleneck
- the state should converge through merge semantics
- the business question is aggregate state, not current owner
Typical examples:
- request counters
- low-volume distributed usage totals
- convergent worker-visible aggregates
Recommended DSM Shape
- collection type:
CRDT - locator example:
shared/worker/request-counter - update type: immutable
DsmEntity<E>record - state type:
MergeableState<S> - persistence default:
local-durable
Mental Model
text
node-a local update: +3
node-b local update: +5
+3 -------------------+
\
+--> merge --> visible cluster state = 8
/
+5 -------------------+Flow
text
apply(update)
|
+--> local state merger runs
|
+--> version vector advances
|
+--> sync layer replicates delta
|
+--> peers merge toward same stateRegistration Pattern
java
DsmCrdtCollection<PnCounterUpdate, PnCounterState> requestCounter = runtime.crdt(
CrdtCollectionSpecBuilder.<PnCounterUpdate, PnCounterState>crdt(
"shared",
"worker",
"request-counter")
.schemaId("request-counter/v1")
.codec(new PnCounterUpdateCodec())
.stateCodec(new PnCounterStateCodec())
.initialState(PnCounterState.empty())
.build(),
new PnCounterStateMerger());Update Pattern
java
requestCounter.apply(PnCounterUpdate.increment("orders", 1));
requestCounter.apply(PnCounterUpdate.decrement("orders", 1));Read Pattern
java
PnCounterState state = requestCounter.state();
VersionVector version = requestCounter.versionVector();
StateDelta<PnCounterState> delta = requestCounter.deltaSince(version);Design Rules
- use a CRDT only when local multi-writer updates are a real requirement
- keep the update entity focused on one mergeable action
- choose a schema ID that reflects the state and update compatibility boundary
- expect eventual convergence, not single-writer overwrite semantics
Common Mistakes
Using A CRDT For Simple Latest-Value Metadata
If one latest route or flag value is enough, a register is simpler.
Expecting Ownership Semantics
CRDTs merge. They do not answer "who is the owner right now".
Forgetting Version Vectors In Repair Reasoning
The version vector is part of how peers understand what delta is needed. It is not just internal bookkeeping.
Spring Boot Shape
yaml
dsm:
collections:
- bean-name: requestCounterCollection
tenant-id: shared
application-id: worker
collection-id: request-counter
schema-id: request-counter/v1
type: CRDT
consistency-tier: CRDT
codec-bean: requestCounterCodec
crdt:
state-codec-bean: requestCounterStateCodec
initial-state-bean: requestCounterInitialState
merger-bean: requestCounterMerger