Taming Campaign Traffic with Redis Locks
When a marketing campaign goes live nationwide, traffic does not ramp up politely. It spikes. For a voucher-distribution chatbot, that spike is the whole point — and also the thing most likely to hand the same voucher to two people at once.
The race condition
A voucher claim is a read-then-write: check that a code is still available, then mark it as taken. Under heavy concurrency, two requests can both read "available" before either writes "taken". Both succeed. The business loses money and trust.
A lock that expires
The fix is a short-lived distributed lock keyed on the resource. Redis SET with NX and a TTL gives you exactly that: only one caller acquires the key, and if a worker dies mid-claim the lock releases itself.
acquired = redis.set(f"lock:voucher:{code}", worker_id, nx=True, ex=5)
if not acquired:
raise Busy("try again")
try:
claim_voucher(code)
finally:
release_if_owner(code, worker_id)
A lock without a TTL is just a future outage with extra steps.
Rate limiting at the edge
Locks protect correctness; rate limits protect the system. A per-user token bucket in Redis kept individual numbers from hammering the campaign endpoint, which smoothed the load enough that the locks rarely contended in the first place.
- Lock on the smallest resource that needs protection, not the whole request.
- Always set a TTL — assume every worker can crash.
- Release only if you still own the lock, to avoid freeing someone else's.