Collection Types
DSM exposes three collection models. Choose based on the coordination problem, not by habit.
This page explains the semantic difference between the models. For method-level details and field-level defaults, use the Reference section.
Quick Mental Model
| Model | Core shape | Best when |
|---|---|---|
| Register | One key maps to one replicated value. | Metadata ordering is enough. |
| Lease | One key has one active owner at a time. | Fencing and ownership matter. |
| CRDT | Many nodes update locally and merge state. | Convergence matters more than single-writer order. |
Decision Tree
Side-By-Side View
| Model | Write pattern | Use when |
|---|---|---|
| Register | One value per key. | Route hints, service-local metadata, and latest-value coordination. |
| Lease | One active owner with renewal. | Shard ownership, leader-style work assignment, and fencing. |
| CRDT | Concurrent local updates with merge. | Counters and mergeable state. |
Register Collections
Use registers when you need replicated metadata keyed by an entry ID.
Typical workloads:
- route hints
- service-local feature state
- cluster-visible operational metadata
Register collections are created with CollectionSpecBuilder.register(...).
CollectionSpecBuilder.<RouteHint>register(
"shared",
"gateway",
"route-hints")
.schemaId("route-hints/v1")
.codec(new RouteHintCodec())
.build();Default builder behavior:
- consistency tier:
REGISTER - replication profile:
embeddedRegister() - QoS profile:
bestEffortMeta() - persistence profile:
ephemeral()
Think of a register as: "I want the cluster to agree on the latest metadata value for this key." For example, edge-eu-west-1 may resolve to 10.1.0.8:8443; after node-a puts the route hint, the delta replicates and peers resolve the same key the same way.
Lease Collections
Use leases when a single owner must hold a responsibility at a point in time.
Typical workloads:
- shard ownership
- leader election
- ownership handoff with fencing tokens
Lease collections are created with LeaseCollectionSpecBuilder.lease(...). They add lease term, renewal skew, expiry grace, and entity factory options.
LeaseCollectionSpecBuilder.<ShardOwner>lease(
"shared",
"worker",
"shard-owner")
.schemaId("shard-owner/v1")
.codec(new ShardOwnerCodec())
.entityFactory(ShardOwner::blank)
.build();Default builder behavior:
- replication profile:
embeddedLease() - QoS profile:
controlCritical() - persistence profile:
localDurable() - lease mode:
AUTONOMOUS - lease term:
10s - renew skew:
3s - expiry grace:
500ms
Use a lease when "who owns this key right now?" is the core question.
Lease Modes
| Mode | Behavior | Use when |
|---|---|---|
AUTONOMOUS | Local runtime can acquire, renew, transfer, and release based on its current view. | Availability matters and downstream fencing can tolerate partition-local decisions. |
QUORUM | Lease mutations are rejected while membership is unstable or visible members cannot prove majority. | Duplicate ownership during partitions is unacceptable. |
In QUORUM mode the runtime waits for a stable membership size before calculating majority. While membership is settling, operations fail with membership-unstable; after the stable size is known, insufficient visible members fail with quorum-unavailable.
CRDT Collections
Use CRDTs when every node should be able to update locally and the cluster should converge through a merge function.
Typical workloads:
- counters
- monotonic aggregates
- mergeable control-plane views
CRDT collections are created with CrdtCollectionSpecBuilder.crdt(...). They require an update codec, state codec, initial state, and merger.
CrdtCollectionSpecBuilder.<PnCounterUpdate, PnCounterState>crdt(
"shared",
"worker",
"request-counter")
.schemaId("request-counter/v1")
.codec(new PnCounterUpdateCodec())
.stateCodec(new PnCounterStateCodec())
.initialState(PnCounterState.empty())
.build();Default builder behavior:
- replication profile:
embeddedCrdt() - QoS profile:
standard() - persistence profile:
localDurable()
Use a CRDT when local writes on multiple nodes should merge instead of fighting for one winning write.
Change Streams
Every collection handle exposes change streams for local observation and integration hooks. Treat them as an operational/event-observation surface, not as a durable event log.
| Use ChangeStream for | Do not use ChangeStream for |
|---|---|
| local audit hooks | guaranteed message delivery |
| UI/devtool observation | replaying historical business events |
| federation live forwarding | replacing Kafka, Pulsar, or an outbox |
| cache invalidation | source-of-truth persistence |
Bounded streams have overflow policies. If you cannot drop or fail fast safely, the consuming workflow probably needs a durable queue outside DSM.
How To Choose
Use this rule of thumb:
| Need | Choose |
|---|---|
| Latest metadata value. | Register |
| One active owner with fencing. | Lease |
| Concurrent local updates. | CRDT |
If you are unsure, start with a register. Move to a lease only when ownership matters, and move to a CRDT only when multiple nodes truly need local writes.
Default Operational Profiles
- Registers default to embedded register replication, best-effort metadata QoS, and ephemeral persistence.
- Leases default to embedded lease replication, control-critical QoS, and local durable persistence.
- CRDTs default to embedded CRDT replication, standard QoS, and local durable persistence.