PartitionOwnership

This node's share of the partitions of one lease group (–), without a leader: a context's log, or another context's published topic that the service's subscriptions read, whose subscriptions runtime then reads the topic instead of the log:

  • Membership is the node's in its service (ServiceMembership), shared by all its groups: one record listing the groups it takes part in, heartbeated every ClusterOptions.heartbeatInterval, and one read of the members per interval, which this group's reconcile and routes reuse. The group's members are those listing it. A reconcile that runs longer than ClusterOptions.reconcileTimeout, stuck stopping or starting a partition, takes the group off the node's record until the reconcile ends, so the others take the partitions it cannot run instead of handing them back to it. One still running after wow.health.liveness.stall-timeout fails the node's liveness, so its orchestrator restarts it: such a reconcile waits on a call that never returns, which only a restart ends.

  • Reconciling: every ClusterOptions.reconcileInterval, and at once when it notices the members changed, the node computes its target, the partitions HRW (Hrw) gives it among the live members, hands off the partitions it holds outside it and acquires those inside it. Members that briefly disagree are arbitrated by the leases and converge on the next round; a partition the old owner still holds is acquired once it is released or expires.

  • Leases are renewed every ClusterOptions.renewInterval, counted from the start of the previous round, all held leases of a round with one LeaseStore.renew, so a round does not grow with the number held times the store's latency and asks the store's pool for one connection, not one per lease. Renewing starts before the first acquisition, and covers a partition from the moment its lease is taken, while its runtimes, intake and timers start: a start slower than the margin keeps its lease. Each round is reported to renewalRounds and logged, as a warning when it took more than half the margin leaseTtl − renewInterval. A lease lost to expiry stops the partition here, and so does one left unrenewed for leaseTtl − renewInterval (the store unreachable, say), without a release; the next reconcile acquires it anew.

  • Hand-off of a partition goes in this order: stop accepting new commands for it, stop its command intake, which settles the commands it took and saves its checkpoint, wait for its other commands in flight for at most ClusterOptions.drainTimeout, stop its subscriptions, which saves their checkpoints (SubscriptionRuntime.stopPartition), and release the lease, so the new owner acquires it on its next round. State caches are not handed over: the new owner loads from the snapshot and the log's tail, and each node drops the partition's cached states whenever it acquires, hands off or loses it (partitionChanged).

  • Routes: every ClusterOptions.routeRefreshInterval, and at once on refreshSoon, the node reads the owners into its routes, beside the members its membership read last. Each other member they show at an address not seen before is handed to peers, apart from the refresh: the node connects to it there (ClusterChannel.connect) before forwarding to it.

Acquiring a partition starts its subscriptions in subscriptions and its other log groups (SubscriptionRuntime.startPartition), then the same partition of the context's command topic in intake, then its timers, before the node accepts commands for it: while they start, the partition is still pending, its lease renewed with the others. A hand-off stops the timers first, once the node stops accepting, so it sends no command it would not run. The partitions the first reconcile of a start acquires read nothing, their entries or their parked retries, until it has started all of them, or until the job start is given completes: a backlog the first of them would read at once (a history migrate-data imported, a long stop) would slow down the starts of the others. Without a job, the hold lasts at most ClusterOptions.reconcileTimeout, so a reconcile stuck starting one partition does not hold the others back. The partitions later reconciles acquire read at once.

stop is a graceful leave: the node leaves the members at once, so the others take its partitions on their next round, then hands off every partition it holds.

stepOut is the leave of a node that met a fatal error (me.ahoo.wow.runtime.health.NodeFatals): for good, within a bound, and whatever the hand-off comes to, renewing no lease from then on, so a partition it could not hand off is the others' once its lease expires. It keeps reading the routes, so a command the node is still given goes to the partition's new owner.

Constructors

Link copied to clipboard
constructor(group: LeaseGroup, node: String, address: String, partitions: Int, leases: LeaseStore, options: ClusterOptions = ClusterOptions(), subscriptions: SubscriptionRuntime? = null, context: CoroutineContext = Dispatchers.Default, timeSource: TimeSource = TimeSource.Monotonic, renewalRounds: (RenewalRound) -> Unit = {}, peers: suspend (Member) -> Unit = {})

Properties

Link copied to clipboard

This node's internal address, as the other members reach it.

Link copied to clipboard
Link copied to clipboard

Whether the node takes part in the group.

Link copied to clipboard
Link copied to clipboard
val owned: Set<Int>

The partitions this node holds and accepts commands for.

Link copied to clipboard

The number of partitions of the context's log, or of the topic the group reads.

Link copied to clipboard

The routes as last read from the lease store.

Link copied to clipboard

The partitions HRW gave this node on the last reconcile.

Functions

Link copied to clipboard
fun accepts(partition: Int): Boolean

Whether this node holds partition and accepts commands for it.

Link copied to clipboard
fun forwardTarget(partition: Int): Member?

Where a command for partition goes when this node does not accept it: the next owner while this node hands it off (HRW among the other members), otherwise the owner of its live lease. Null when that is unknown or stale, or would be this node: the command then runs locally.

Link copied to clipboard
fun pending(partition: Int): Boolean

Whether this node is about to own partition: it is in its target, but not acquired, or not started, yet.

Link copied to clipboard
suspend fun reconcile()

Compares the partitions held with the HRW target once, handing off and acquiring what differs.

Link copied to clipboard
suspend fun refreshRoutes()

Reads the owners into routes, beside the members the membership read last; a change of the members wakes the reconciler. Once the node stepped out (stepOut), the membership reads no members for it, so it reads them here.

Link copied to clipboard

Asks for the routes to be read again soon, such as after a forward failed.

Link copied to clipboard
suspend fun <T : Any> runIfAccepting(partition: Int, command: suspend () -> T): T?

Runs command for partition when this node holds the partition and accepts commands for it, and returns what it returned; null, without running it, otherwise. A hand-off of the partition waits for the commands running here.

Link copied to clipboard
suspend fun start(reads: Job? = null)

Joins the group: heartbeats, reconciles at once and keeps doing so until stop. A start that fails, the lease store unreachable say, undoes what it did before it throws: it leaves the members, hands off what it acquired and stops the runtimes it started, so the ownership can be started again.

Link copied to clipboard
suspend fun stop()

Leaves the group gracefully: leaves the members, hands off every partition this node holds (see the class description for the order), and stops the subscriptions.