Around Go: MongoDB and Redis · 3 MIN READ

Redis counters and leases need atomic transitions and ownership

Avoid split increment/expiry races and unsafe lock deletion.

A counter with a timeout and a temporary ownership key look small, but concurrent requests and pauses expose their failure windows. Put dependent steps in one short atomic transition and distinguish an expired lease from a durable guarantee that only one worker can ever act.

Around Go: data stores · A MAP OF THE IDEA

  1. Atomic transition
  2. Ownership token
  3. Expiry or result
  4. Downstream invariant

Useful when

  • Avoid split increment/expiry races and unsafe lock deletion.

Watch out

  • Expiry changes the Redis key, not the execution state of an old process. A paused former owner can resume after a new owner starts; the protected resource may need fencing.

Implementation & operation cost: Atomic scripts add protocol and recovery complexity; a lease is weaker than a durable resource-side ownership guarantee.

01

Make a fixed-window counter transition atomic

INCR followed by EXPIRE in separate operations can leave a counter without expiry if the client fails between them. A short script can attach expiry only when the counter is first created. This is an anchored fixed-window example, not a smooth sliding-window limiter.

go
counter := redis.NewScript(`
local n = redis.call("INCR", KEYS[1])
if n == 1 then redis.call("PEXPIRE", KEYS[1], ARGV[1]) end
return n
`)
n, err := counter.Run(ctx, rdb, []string{"demo:rate"}, 1000).Int64()
if err != nil { return err }
if n > 100 { return ErrLimited }
Read the code

The go-redis script API passes one explicitly named development key and a positive window in milliseconds. This example permits 100 events in a window anchored at the first increment; ErrLimited is an application error. Define unavailable-store behavior separately.

02

A lease must be released only by its owner

SET key token NX PX can create a temporary lease. Release by checking the stored token and deleting in the same atomic operation, not by a GET and later DEL. Generate a unique token for each acquisition; a stale owner must not remove a new owner’s lease.

go
release := redis.NewScript(`
local current = redis.call("GET", KEYS[1])
if current == ARGV[1] then
  return redis.call("DEL", KEYS[1])
end
return 0
`)
// token is unique to this successful SET key token NX PX acquisition.
deleted, err := release.Run(ctx, rdb, []string{key}, token).Int64()
if err != nil { return err }
_ = deleted // 0 means the lease no longer belongs to this acquisition.
Read the code

The script compares the acquisition token inside the server before deletion. It solves that release race, but a paused worker may still continue after its lease expires; a protected resource may need fencing or another stronger ownership mechanism.

03

Explain the failure model before calling it a lock

An acknowledgment can be lost, a lease can expire during a pause and failover can change which writes survive. A Redis key alone is not a proof of exclusive business execution across failures. Use resource-side idempotency, version checks or fencing when duplicate work could violate an invariant.

TAKE THIS WITH YOU

Make dependent counter steps atomic and release leases by token. Design the downstream invariant for pauses, ambiguity and expired ownership.

Check your understanding

Why can a resource still need fencing after a lease expires?

Go to the source

Progress stays in this browser. No account needed.