Skip to content

Shard Owner Election

This cookbook shows how to model single-owner work distribution with a lease collection.

When This Pattern Fits

Use this pattern when:

  • exactly one node should actively own a key at a time
  • ownership must be renewed or it expires
  • you need fencing-aware transfer or verification
  • another node must be able to take over after expiry

Typical examples:

  • shard ownership
  • active worker selection
  • lease-based leader election per partition
  • collection type: LEASE
  • locator example: shared/worker/shard-owner
  • entity type: immutable LeaseEntity<E> record
  • persistence default: local-durable

Mental Model

text
shard-17 ownership

worker-a acquire(shard-17)
    |
    +--> success, fencing token = 42
    |
    +--> renew before lease term closes
    |
    +--> keep processing shard-17

if worker-a stops renewing:
    |
    +--> lease term expires
    +--> expiry grace elapses
    +--> worker-b may acquire shard-17

Lease Timeline

text
time ------------------------------------------------------------>

acquire         renew window begins               expiry
  |                      |                           |
  v                      v                           v
[ owned -----------------+------------- ][ expired ][ free ]
  <------ term --------->
         < renew-skew >

Entity Model

java
public record ShardOwner(
        String entryKey,
        EntityMetadata metadata,
        LeaseState leaseState)
        implements LeaseEntity<ShardOwner> {

    public static ShardOwner blank(String entryKey) {
        return ...;
    }

    @Override
    public ShardOwner withMetadata(EntityMetadata metadata) {
        return new ShardOwner(entryKey, metadata, leaseState);
    }

    @Override
    public ShardOwner withLeaseState(LeaseState state) {
        return new ShardOwner(entryKey, metadata, state);
    }
}

Registration Pattern

java
DsmLeaseRegister<ShardOwner> shardOwners = runtime.leaseRegister(
    LeaseCollectionSpecBuilder.<ShardOwner>lease(
            "shared",
            "worker",
            "shard-owner")
        .schemaId("shard-owner/v1")
        .codec(new ShardOwnerCodec())
        .entityFactory(ShardOwner::blank)
        .leaseTerm(Duration.ofSeconds(10))
        .renewSkew(Duration.ofSeconds(3))
        .expiryGrace(Duration.ofMillis(500))
        .build());

Acquisition Pattern

java
LeaseAcquireResult<ShardOwner> acquired = shardOwners.acquire(
        "shard-17",
        LeaseAcquireOptions.defaults())
    .join();

Renewal Pattern

java
LeaseRenewResult<ShardOwner> renewed = shardOwners.renew(
        "shard-17",
        LeaseRenewOptions.defaults())
    .join();

Handoff Pattern

text
worker-a owns shard-17
    |
    +--> transfer(shard-17, worker-b)
    |
    +--> worker-b becomes active owner
    |
    +--> old fencing token must no longer be trusted

Design Rules

  • use one lease key per owned work item
  • keep renewal scheduling comfortably inside the renew-skew window
  • treat fencing tokens as part of the correctness contract for downstream side effects
  • do not use lease state as a generic metadata bag

Common Mistakes

Renewing Too Late

If renewal is scheduled too close to expiry, transient pauses can cause avoidable ownership churn.

Ignoring Fencing

Lease ownership is not just "latest writer wins". Downstream processing should respect the current lease lineage and fencing decision.

Using A Lease For Mergeable Metrics

If multiple nodes should contribute to a shared value, use a CRDT instead.

Spring Boot Shape

yaml
dsm:
  collections:
    - bean-name: shardOwnersCollection
      tenant-id: shared
      application-id: worker
      collection-id: shard-owner
      schema-id: shard-owner/v1
      type: LEASE
      consistency-tier: LEASE
      codec-bean: shardOwnerCodec
      lease:
        mode: AUTONOMOUS
        term: 10s
        renew-skew: 3s
        expiry-grace: 500ms
        entity-factory-bean: shardOwnerEntityFactory