Distributed Locks Are Not Concurrency Control

A Redis lock prevents parallel execution. It does not guarantee correctness.

Under load, expiry races, GC pauses, and network partitions can still produce duplicate execution. And duplicates are correctness failures.

Table of Contents

1. The Assumption

If I hold the lock, no one else runs this code.

That assumption is false in distributed systems.

A lock is a coordination primitive. Correctness is a data guarantee.

2. The Naive Implementation

var acquired = await redis.StringSetAsync(
    "lock:order:123",
    nodeId,
    TimeSpan.FromSeconds(10),
    When.NotExists
);

if (!acquired)
    return;

await ProcessOrder();

await redis.KeyDeleteAsync("lock:order:123");

Looks safe.

It is not.

3. Expiry Race Condition

Timeline:

T0   Node A acquires lock (TTL = 10s)
T8   Node A still processing
T10  Lock expires
T10.1 Node B acquires lock
T11  Node A completes

→ Double execution

The system behaved exactly as configured. No component failed.

The failure is logical, not technical.

4. GC Pause & Thread Suspension (.NET)

In .NET, stop-the-world garbage collection can pause threads.

During a Gen2 GC:

If GC pause = 400ms–800ms under pressure, expiry windows shrink dramatically.

Reference: .NET GC Fundamentals

5. Network Partitions

Consider temporary network partition:

Node A acquires lock
Network isolates Node A from Redis
TTL expires
Node B acquires lock
Node A continues processing (still alive)

Both nodes believe they are correct.

This is split-brain behavior.

Distributed systems cannot assume perfect connectivity.

6. The Probability Problem

Suppose:

If even 2% of executions exceed TTL due to variance:

5,000 × 2% = 100 potential duplicate windows per second

Over one hour:

100 × 3600 = 360,000 potential duplicate events

Even if only 0.1% materialize:

360,000 × 0.1% = 360 real duplicates per hour

Locks reduce concurrency. They do not eliminate race probability.

7. The RedLock Debate

Redis RedLock attempts to solve this using quorum across multiple nodes.

However, it remains controversial.

Martin Kleppmann critique: How to do distributed locking

Redis author response: Redis Distributed Locks

The debate centers around:

Even RedLock cannot give absolute correctness under asynchronous networks.

8. The Real Solution

The real solution is idempotency at the data layer.

INSERT INTO payments (idempotency_key, amount, ...)
VALUES (@key, @amount, ...)
ON CONFLICT (idempotency_key)
DO NOTHING;

Or enforce uniqueness constraint:

CREATE UNIQUE INDEX ux_payment_idempotency
ON payments(idempotency_key);

Locking prevents parallelism. Idempotency prevents duplication.

They solve different problems.

Concurrency control belongs to the database. Not to Redis.

Redis Production Series (2/8)
View full series →
← Previous Cache Is Not Load Reduction Next → Idempotency Is a Data Problem, Not an Application Problem