Distributed SystemsSeptember 8, 20267 min read

Mastering Distributed Locks with Redis: When Redlock Fails and How to Fix It

Nguyen Dai Long

Nguyen Dai Long

Backend Lead & Software Engineer

In distributed architectures, coordinating mutual exclusion across independent worker instances is essential to prevent double-spending, duplicate order fulfillment, or race conditions. Redis is frequently selected as a distributed lock manager due to its blazing speed. However, naive implementations introduce dangerous vulnerabilities. In this article, I dissect how to implement safe distributed locking.

1. The Naive SETNX Anti-Pattern

Many developers implement locks using simple SETNX key value followed by an EXPIRE command. If the process crashes between setting the key and establishing the TTL, the lock becomes permanent, causing perpetual deadlock. Redis 2.6.12 resolved this by introducing atomic SET key value NX PX milliseconds.

python
import uuid
import time

def acquire_lock(redis_client, lock_name, acquire_timeout=10, lock_timeout=5000):
    identifier = str(uuid.uuid4())
    end = time.time() + acquire_timeout
    
    while time.time() < end:
        # Atomic SET with NX and PX
        if redis_client.set(f"lock:{lock_name}", identifier, px=lock_timeout, nx=True):
            return identifier
        time.sleep(0.05)
    return False

Key Implementation Takeaways:

  • Always use atomic SET key val NX PX to prevent deadlocks on worker failure.
  • Never release a lock with a simple DEL command—always check owner identity with a Lua script.
  • Store a unique UUID as the lock value to verify ownership.

2. The Danger of GC Pauses and Fencing Tokens

As distributed systems expert Martin Kleppmann highlighted, a worker acquiring a lock may experience a long Garbage Collection pause, OS thread descheduling, or network hiccup. During this pause, the lock TTL expires and another worker acquires it. When Worker 1 resumes, it unknowingly writes stale data. To solve this, storage engines must enforce monotonically increasing Fencing Tokens.

lua
-- Atomic Lock Release Lua Script
if redis.call("get", KEYS[1]) == ARGV[1] then
    return redis.call("del", KEYS[1])
else
    return 0
end

Key Implementation Takeaways:

  • Use Lua scripts to ensure atomicity when validating and releasing locks.
  • Incorporate monotonically increasing fencing tokens validated at the database layer.
  • For mission-critical financial transactions, consider consensus engines like ZooKeeper or etcd.

Summary & Final Thoughts

Distributed locks in Redis require understanding network partition boundaries, atomic Lua scripting for releases, and database fencing tokens to guarantee data consistency.

Engineering Feedback0 likes

Was this technical breakdown helpful for your production workflow?

Nguyen Dai Long

Written by Nguyen Dai Long

Backend Engineer & Backend Lead with 4+ years of hands-on experience building production systems, RESTful APIs, and cloud infrastructure using Python (Django), Laravel, PostgreSQL, and Google Cloud Platform.

Technical Discussion0

Ask questions, challenge architectures, or share your own production insights.

Join the Technical Community Discussion

Sign in via GitHub or Google in 5 seconds to comment, exchange architecture insights, and build your engineering presence.

Loading discussion...
NDL Ecosystem

Explore Free Web Tools & Games

View All Tools & Apps