Skip to content

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
  • 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 ​

Distributed counter merge flow

Flow ​

apply(update) runs the local state merger, advances the version vector, replicates the delta through the sync layer, and lets peers merge toward the same state.

Registration 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