How fencing tokens work to prevent stale writes
mainA fencing token is a strictly increasing value used to protect resources from clients that lose their lock (e.g., due to a long GC pause) but attempt to perform writes anyway.
To use them:
- Set
Options.FenceKeyinlocker.Obtain. This key is used to store the monotonic counter in Redis. - Retrieve the token using
lock.FenceToken(). - Include this token in every write operation to your protected resource.
- The resource must atomically check that the incoming token is greater than or equal to the highest token it has already processed. If the token is older, the write must be rejected.
Important Notes:
- Redis Cluster Compatibility: The
FenceKeymust hash to the same slot as the lock key. Use Redis hash tags (e.g., lock key{job}:lockand fence key{job}:fence) to ensure they reside on the same node. - Monotonicity: On a single Redis instance, the token is strictly monotonic. In a Sentinel or Cluster failover scenario, the token might regress if the
INCRoperation is lost during failover. For absolute cross-failover monotonicity, use a linearizable store. - Persistence: The counter persists across lock releases and continues to increment.
// Obtain a lock with a fencing token.
lock, err := locker.Obtain(ctx, "my-key", time.Second, &redislock.Options{FenceKey: "my-key:fence"})
if err != nil {
log.Fatalln(err)
}
defer lock.Release(ctx)
// FenceToken is 0 without a FenceKey. Stamp writes with the token; reject older ones.
if token := lock.FenceToken(); token != 0 {
fmt.Printf("fenced write with token %d\n", token)
}