DEV Community

myougaTheAxo
myougaTheAxo

Posted on

Design Distributed Locks with Claude Code: Redlock, Deadlock Prevention, Idempotency

Introduction

Multiple servers accidentally processing the same job — use Redis Redlock to implement distributed locks and ensure exclusive access to critical sections. Let Claude Code design the solution.

CLAUDE.md Rules

## Distributed Lock Rules
- Redis: must use odd number (3+)
- Lock TTL: 2x expected processing time
- Acquisition timeout: < 100ms
- Deadlock: auto-release via TTL
- Release: verify lock ID before releasing
Enter fullscreen mode Exit fullscreen mode

Generated Implementation

// src/lock/distributedLock.ts
const redlock = new Redlock(redisClients, {
  driftFactor: 0.01,
  retryCount: 3,
  retryDelay: 200,
  retryJitter: 100,
});

export class DistributedLock {
  // Exclusive lock with guaranteed release
  static async withLock<T>(resource: string, ttlMs: number, fn: () => Promise<T>): Promise<T> {
    const lock = await redlock.acquire([`lock:${resource}`], ttlMs);
    try {
      return await fn();
    } finally {
      await lock.release().catch(() => {}); // TTL auto-releases on failure
    }
  }

  // Try lock, skip if already locked (batch job dedup)
  static async tryWithLock<T>(resource: string, ttlMs: number, fn: () => Promise<T>) {
    try {
      const lock = await redlock.acquire([`lock:${resource}`], ttlMs, { retryCount: 0 });
      try { return { executed: true, result: await fn() }; }
      finally { await lock.release().catch(() => {}); }
    } catch (err) {
      if (err instanceof Redlock.ResourceLockedError) return { executed: false };
      throw err;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode
// Coupon redemption: prevent double-use
export async function redeemCoupon(couponCode: string, userId: string) {
  await DistributedLock.withLock(`coupon:${couponCode}`, 5_000, async () => {
    const coupon = await prisma.coupon.findUniqueOrThrow({ where: { code: couponCode } });
    if (coupon.usedAt) throw new ConflictError('Coupon already used');
    await prisma.coupon.update({ where: { id: coupon.id }, data: { usedAt: new Date(), usedBy: userId } });
  });
}

// Batch job: skip if another instance is running
export async function processDailyReport() {
  const { executed } = await DistributedLock.tryWithLock(`daily-report:${today}`, 10*60*1000, async () => {
    await generateAndSendDailyReport();
  });
  if (!executed) logger.info('Already running, skipping');
}
Enter fullscreen mode Exit fullscreen mode

Summary

  1. Redlock with 3 Redis instances: majority quorum for lock validity
  2. TTL as deadlock safety net — even if process crashes
  3. tryWithLock pattern for batch job deduplication
  4. ExtendableLock: auto-extend TTL for long-running processes

Review with **Code Review Pack (\u00a5980)* at prompt-works.jp*

myouga (@myougatheaxo) — Axolotl VTuber.

Top comments (0)