Singleton with lease
Это содержимое пока не доступно на вашем языке.
The singleton manager by default elects a singleton based on cluster gossip alone. During partitions + insufficient downing, both halves can elect their own leader → two singletons exist.
The single-writer lease prevents this:
import { StartSingletonOptions, KubernetesLease, KubernetesLeaseOptions } from 'actor-ts';
const kubernetesLeaseOptions = KubernetesLeaseOptions.create() .withName('job-scheduler-singleton') .withOwner(process.env.POD_NAME!) .withTtlMs(30_000) .withNamespace(process.env.K8S_NAMESPACE!);const startSingletonOptions = StartSingletonOptions.create() .withTypeName('job-scheduler') .withActor(JobScheduler) .withLease(new KubernetesLease( kubernetesLeaseOptions, ));cluster.singleton.start(startSingletonOptions);Now the manager only spawns the singleton after acquiring the lease. Two managers both claiming leadership simultaneously race on the lease; only one wins.
How it works
Section titled “How it works”The lease backend (K8s API server) provides the atomic exactly-one-holder guarantee — beyond gossip’s eventual-consistency.
Configuration
Section titled “Configuration”const startSingletonOptions = StartSingletonOptions.create() .withTypeName(typeName) .withActor(singletonActor) .withLease(lease) // ← optional Lease .withAcquireRetryIntervalMs(5_000); // default — retry after failed acquirecluster.singleton.start(startSingletonOptions);Lease is the same abstraction as
Coordination — InMemoryLease
for tests, KubernetesLease for production.
Reading the protection level
Section titled “Reading the protection level”| Setup | Singleton-uniqueness guarantee |
|---|---|
| No downing, no lease | Best-effort. Partitions = dual singletons. |
| Downing strategy only | Strong on stable networks. |
| Downing + lease | Paranoid-safe. Both invariants enforced. |
For singletons where dual-execution would cause real damage (double-charging customers, double-publishing events), use both.
Lease loss handling
Section titled “Lease loss handling”// Inside the manager (framework-managed):lease.onLost((reason) => { // Stop the singleton; retry acquire after acquireRetryIntervalMs});When the lease is revoked (TTL expiry, another holder took it):
- The manager immediately stops the singleton — no graceful drain.
- The singleton’s
postStopruns. - The manager retries acquire periodically.
Means: if the lease backend hiccups + revokes briefly, the singleton restarts during the recovery — same effect as a brief actor restart.
Lease backend availability
Section titled “Lease backend availability”K8s API outage → no lease renewals → singleton eventually loses lease → singleton stops everywhere → no singleton available until K8s API recoversThe lease backend becomes a SPOF. For typical clusters, K8s API uptime is much higher than the rest of the system, but this is a real consideration. Plan for the rare case.
Failover window
Section titled “Failover window”A's lease has TTL 30s.A crashes — no renewal happens.After 30s, lease expires.B acquires + spawns singleton.Failover window = lease TTL. Configurable via the
ttlMs field on the Lease.
Shorter TTL = faster failover, but more renewal traffic. Typical: 15-30 seconds.
What happens during the window
Section titled “What happens during the window”seconds 0-30: A's lease still valid (it crashed but TTL hasn't expired) B can't acquire; no singleton anywhere Messages to singleton dead-letter
seconds 30+: B acquires; singleton spawns If singleton is a PersistentActor, recovery runs Messages start processing againDuring the gap, messages to the singleton fail — they route to a stopped manager + dead-letter.
For workloads where this is unacceptable, consider:
- Lower TTL (15 s gives faster failover, more renewals).
- Buffering at the sender — the singleton proxy buffers while
the cluster has no host and drains when one appears. The buffer
is capped (
withBufferSize, default 1000); past the cap messages go to dead letters with a warning, so a gap that never closes cannot grow without bound. - Different architecture — singletons are inherently serial-on-failover.
Comparison with sharding-with-lease
Section titled “Comparison with sharding-with-lease”| Singleton + lease | Sharding + lease | |
|---|---|---|
| What’s protected | Singleton uniqueness | Coordinator uniqueness |
| What stops on lease loss | The singleton actor | Coordinator’s allocations |
| Failover window cost | Singleton unavailable | New shard allocations queue |
| Existing work during failover | Pauses | Continues for already-allocated shards |
Singleton failover is more disruptive — the singleton is the workload. Sharding failover affects only new allocations — existing entities keep processing.
Where to next
Section titled “Where to next”- Singleton overview — the foundation.
- Singleton manager — the per-node election logic.
- Sharding with lease — the same pattern for sharding.
- Coordination — the lease abstraction.
- KubernetesLease — production backend.
