LeaseStore

interface LeaseStore

Partition ownership within a subscription group, the deployment unit whose nodes share the partitions of a log (documentation/designs/04-subscriptions.md §1, 02-write-path.md §3). Leases live in storage: no ZooKeeper, consumer group or registry.

A lease is live while the store's clock is before its expiry. At most one node holds a live lease of a partition; an expired or released one can be acquired by any node. Each operation is atomic, so concurrent acquisitions of a free partition have exactly one winner.

Membership is separate from ownership because rebalancing needs it: a node that just joined holds no lease, so the owners would never learn they must give partitions up. Membership is recorded per service and ownership per lease group: each node heartbeats one record for its service, listing the lease groups it takes part in, and reads the service's live members once for all its groups; the runtime derives a node's fair share of a group from the members that list it, releases surplus leases and acquires free ones. The store operations of the control plane so grow with the groups only for the leases (renewals and owner reads), not for the membership.

The store's clock decides liveness. A backend that takes the time from each node, not from the database, relies on the nodes' clock skew being small against the TTL. Since Wow does not fence, a short overlap of two owners during a handover is only duplicate work, never a wrong result.

Functions

Link copied to clipboard
abstract suspend fun acquire(group: String, partition: Int, owner: String, ttl: Duration): Lease?

Acquires partition of group for owner for ttl: the lease is free, expired or released, or owner already holds it (then it is extended and keeps its epoch, so a retried acquisition is harmless). Returns the lease, or null when another node holds it.

Link copied to clipboard
abstract suspend fun heartbeat(service: String, node: String, address: String, ttl: Duration, groups: Set<String> = emptySet()): Member

Registers node as a member of service reachable at address, serving the lease groups, or keeps it one, for ttl from now. A later heartbeat replaces the address and the groups, so a node restarted on another address is found there, and one that stops taking part in a group drops it at once.

Link copied to clipboard
abstract suspend fun leave(service: String, node: String)

Removes node from service at once, for a graceful shutdown; it keeps the leases it has not released.

Link copied to clipboard
abstract suspend fun members(service: String): List<Member>

The live members of service, in node order, each with the groups it serves.

Link copied to clipboard
abstract suspend fun owners(group: String): List<Lease>

The live leases of group, in partition order.

Link copied to clipboard
abstract suspend fun release(group: String, partition: Int, owner: String): Boolean

Releases the live lease owner holds on partition, so another node can acquire it at once.

Link copied to clipboard
abstract suspend fun renew(group: String, partitions: Collection<Int>, owner: String, ttl: Duration): List<Lease>

Extends to ttl from now each live lease owner holds on one of partitions, and returns them; a partition owner no longer holds is left out. A node renews all its leases with one call each round, so a backend renews them in one round trip where it can; a failure fails the whole call.