When a client acquires (A) a key, the server responds in one of three ways.
| Key state | Server action | Response |
|---|---|---|
| Not registered | Register immediately (expiry = now + lease), issue a token | A + token + key |
| Registered + expired | Renew (now + lease), issue a new token | A + token + key |
| Registered + valid (held) | Enqueue, hold the response | A on release/expiry, T if wait is exceeded |
R), the key is handed directly to the front of the queue — it moves on immediately with a new token, with no re-contention.lease is a safety net. Even if a client dies or forgets to release, once the lease time elapses the server can automatically reclaim the key and hand it to the next waiter. So independent of the normal release flow, it's good practice to set lease generously, but not so long that a dead client ties up the key for too long.wait is the upper bound on how long a client waits to acquire. 0 means wait indefinitely; otherwise, if the client isn't granted the key within that many seconds, the server gives up and responds with T (timeout) — since it ends without a grant, no lock ever leaks.The token carried in an A response is a monotonically increasing u64 for that grant. Every acquisition is issued a value greater than any previous token. Even across a failover, the newly active node continues from a value greater than the highest replicated token it received (succession) — so the token keeps increasing across the entire cluster.
If the resource you're protecting (an account, a file, an order, etc.) only verifies that "the token I hold now is greater than the last one I saw," it can refuse access from a client that wakes up late after lease expiry and tries to use an already-invalidated, lower token. This means the lock server doesn't need to be perfectly consistent at every instant to keep things safe.
The official clients share the following behavior in common (see Libraries for language-specific API details).
lease anyway, but this hands it to the next waiter faster).peers list is promotion priority (front = highest priority). Among the nodes that are alive, the one with the highest priority becomes active; the rest become standbys replicating its state in real time.M) to the active address and moves there.Ctrl+C), the active hands off promotion to its successor first, then redirects clients, minimizing the gap with no active node.This promotion scheme is not quorum (majority-vote) based. That means the node count doesn't need to be odd or even, and service is maintained as long as even a single node is alive.
| Node count | Zero-downtime | Concurrent failures tolerated | Notes |
|---|---|---|---|
| 1 | No | — | Fastest. Brief blip on restart |
| 2 | Yes | 1 | Minimum configuration for zero-downtime. Sufficient for most cases |
| 3 | Yes | 2 | Redundancy maintained even while one node is under maintenance |
| 4+ | Yes | N−1 | Only propagation cost increases — not recommended |
Because priority-based promotion isn't consensus, both nodes can briefly be active at the same time during a network partition, and due to asynchronous replication, some grants can be lost at the moment of failover. In other words, mutual exclusion isn't 100% guaranteed during failover/partition windows. If you need strong guarantees, implement verification of the fencing tokens described above on the protected resource — rejecting stale (lower) tokens keeps you safe even when the lock server isn't perfectly consistent.