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

Counter merge model: node-a applies a local update of +3 and node-b applies a local update of +5; both feed a merge that produces a visible cluster state of 8.

Flow

Counter update flow: apply(update) triggers the local state merger, the version vector advances, the sync layer replicates the delta, and 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