Part of the Distributed Coordination series.
Start here: What Is a Distributed Lock? (With Examples)
The stale-owner problem
A process can continue execution after losing lease ownership and still attempt writes from an old world view.
This often happens during ordinary runtime behavior, not exotic failure: JVM stop-the-world pauses, CPU starvation, container rescheduling, and overloaded event loops can all pause renewal long enough for ownership transfer.
Lock acquisition is not proof of safety. It is only a momentary ownership signal.
Why lease expiry alone fails
Expiry only tells the lock service who should own now. It does not stop delayed work already in flight. Without storage-side rejection, stale work can overwrite newer state.
In production, the stale client frequently still has open DB connections, warmed caches, and queued async tasks. After pause recovery, those operations can complete successfully from the client perspective, even though ownership is already obsolete.
Lease expiry does not stop execution. It only changes who is currently authorized.
Why locks alone are insufficient
A lock service can correctly hand off ownership and still allow corruption if mutation boundaries trust client identity instead of authority ordering. That is the gap between coordination and correctness.
Paused-client scenario: A acquires, pauses, and misses renewal. B acquires and writes new state. A resumes and writes stale state afterward. If storage accepts both writes, the lock service did its job and the system still failed.
Correctness must be enforced at the write boundary.
Token model
Every successful acquisition gets a monotonically increasing token. Storage accepts writes only if token is newer than the last accepted token for that resource.
Monotonicity is the key property. If token issuance is not linearizable for the coordination key, two actors can present incomparable claims and storage cannot reliably reject stale operations.
Example 1: storage write rejection
Worker A writes with token 41, pauses, then lease expires. Worker B acquires token 42 and writes. A resumes with token 41; storage rejects the write because 41 < 42.
Example 2: workflow side effects
A side-effect gateway stores highest accepted token per workflow key. Late retries from stale workers are dropped before partner calls, preserving business ordering.
This is important for non-transactional boundaries such as email, payment capture, and external webhook dispatch where rollback is impossible. Fencing at the gateway prevents “late but validly signed” stale actions from escaping the system.
Where fencing must be enforced
- Storage layer: conditional writes that reject stale fencing tokens.
- Side-effect gateway: token check before irreversible outbound operations.
- Database write boundary: same constraint used by every write path, not only the happy path.
What happens without fencing
Without fencing, stale owners can emit duplicate side effects, lose newer updates, and silently corrupt shared state.
Production story: one scheduler paused for longer than lease TTL during a noisy-neighbor event. A new owner completed reconciliation. When the paused scheduler resumed, it replayed old mutations and rolled resource metadata backward because no token gate existed.
Why this breaks in real systems
GC pauses exceed lease windows. Network delays reorder delivery of “still running” work. Clock drift biases renewal timing near expiry. Process stalls create long tails where clients resume with stale authority.
Two processes can believe they own the lock. Fencing is how you make only one of them able to mutate state.
Common mistakes
- Issuing tokens but not checking them at the mutation boundary.
- Checking tokens in only one write path and forgetting secondary paths.
- Using wall-clock timestamps instead of monotonic authority-issued tokens.
When fencing tokens are unnecessary
If duplicate work is only an efficiency cost and shared correctness-critical state is not mutated, fencing usually adds complexity without material safety gain.
What to Read Next
→ Read next: Redis vs. ZooKeeper vs. etcd for Distributed Locking.