home / skills / yonatangross / orchestkit / distributed-locks

distributed-locks skill

/plugins/ork/skills/distributed-locks

This skill helps coordinate exclusive access across service instances using Redis and PostgreSQL locks to prevent race conditions.

npx playbooks add skill yonatangross/orchestkit --skill distributed-locks

Review the files below or copy the command above to add this skill to your agents.

Files (6)
SKILL.md
4.4 KB
---
name: distributed-locks
description: Distributed locking patterns with Redis and PostgreSQL for coordination across instances. Use when implementing exclusive access, preventing race conditions, or coordinating distributed resources.
context: fork
agent: backend-system-architect
version: 1.0.0
tags: [distributed, locks, redis, postgresql, concurrency, coordination, 2026]
author: OrchestKit
user-invocable: false
---

# Distributed Locks

Coordinate exclusive access to resources across multiple service instances.

## Overview

- Preventing duplicate processing of jobs/events
- Coordinating singleton processes (cron, leaders)
- Protecting critical sections across instances
- Implementing leader election
- Rate limiting at distributed level

## Lock Types Comparison

| Lock Type | Durability | Latency | Use Case |
|-----------|-----------|---------|----------|
| Redis (single) | Low | ~1ms | Fast, non-critical |
| Redlock (multi) | High | ~5ms | Critical, HA required |
| PostgreSQL advisory | High | ~2ms | Already using PG, ACID |

## Quick Reference

### Redis Lock (Single Node)

```python
from uuid_utils import uuid7
import redis.asyncio as redis

class RedisLock:
    """Redis lock with Lua scripts for atomicity."""

    ACQUIRE = "if redis.call('set',KEYS[1],ARGV[1],'NX','PX',ARGV[2]) then return 1 end return 0"
    RELEASE = "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) end return 0"

    def __init__(self, client: redis.Redis, name: str, ttl_ms: int = 30000):
        self._client = client
        self._name = f"lock:{name}"
        self._owner = str(uuid7())
        self._ttl = ttl_ms

    async def acquire(self) -> bool:
        return await self._client.eval(self.ACQUIRE, 1, self._name, self._owner, self._ttl) == 1

    async def release(self) -> bool:
        return await self._client.eval(self.RELEASE, 1, self._name, self._owner) == 1

    async def __aenter__(self):
        if not await self.acquire():
            raise LockError(f"Failed to acquire {self._name}")
        return self

    async def __aexit__(self, *_):
        await self.release()
```

See [redis-locks.md](references/redis-locks.md) for complete implementation with retry/extend.

### PostgreSQL Advisory Lock

```python
from sqlalchemy import text

async def with_advisory_lock(session, lock_id: int):
    """PostgreSQL advisory lock (session-level)."""
    await session.execute(text("SELECT pg_advisory_lock(:id)"), {"id": lock_id})
    try:
        yield
    finally:
        await session.execute(text("SELECT pg_advisory_unlock(:id)"), {"id": lock_id})
```

See [postgres-advisory-locks.md](references/postgres-advisory-locks.md) for transaction-level and monitoring.

## Key Decisions

| Decision | Recommendation |
|----------|----------------|
| Backend | Redis for speed, PG if already using it |
| TTL | 2-3x expected operation time |
| Retry | Exponential backoff with jitter |
| Fencing | Include owner ID for safety |

## Anti-Patterns (FORBIDDEN)

```python
# NEVER forget TTL (causes deadlocks)
await redis.set(f"lock:{name}", "1")  # WRONG - no expiry!

# NEVER release without owner check
await redis.delete(f"lock:{name}")  # WRONG - might release others' lock

# NEVER use single Redis for critical operations
lock = RedisLock(single_redis, "payment")  # Use Redlock for HA

# NEVER hold locks across await points without heartbeat
async with lock:
    await slow_external_api()  # Lock may expire!
```

## Related Skills

- `idempotency-patterns` - Complement locks with idempotency
- `caching-strategies` - Redis patterns
- `background-jobs` - Job deduplication

## References

- [Redis Locks](references/redis-locks.md) - Lua scripts, retry, extend
- [Redlock Algorithm](references/redlock-algorithm.md) - Multi-node HA
- [PostgreSQL Advisory](references/postgres-advisory-locks.md) - Session/transaction

## Capability Details

### redis-locks
**Keywords:** Redis, Lua, SET NX, atomic, TTL
**Solves:** Fast distributed locks, atomic acquire/release, auto-expiry

### redlock
**Keywords:** Redlock, multi-node, quorum, HA, fault-tolerant
**Solves:** High-availability locking, survive node failures

### advisory-locks
**Keywords:** PostgreSQL, advisory, pg_advisory_lock, session, transaction
**Solves:** Lock with existing PG, ACID integration, no extra infra

### leader-election
**Keywords:** leader, election, singleton, coordinator
**Solves:** Single active instance, coordinator pattern

Overview

This skill implements distributed locking patterns using Redis and PostgreSQL to coordinate exclusive access across service instances. It provides fast single-node Redis locks, multi-node Redlock for high availability, and PostgreSQL advisory locks for ACID-integrated coordination. Use it to prevent duplicate work, enforce singleton processes, and protect critical sections in distributed systems.

How this skill works

The skill offers small, battle-tested primitives: a Redis lock with atomic Lua scripts and TTL management, a Redlock implementation that acquires locks across multiple Redis nodes for quorum, and PostgreSQL advisory locks that leverage pg_advisory_lock/unlock. Each primitive includes owner fencing and recommended retry/backoff strategies. Guidance covers TTL sizing, heartbeat/extend patterns, and anti-patterns to avoid deadlocks or accidental releases.

When to use it

  • Prevent duplicate processing of jobs or events across workers
  • Elect or maintain a single active leader or cron job in a cluster
  • Protect critical sections where concurrent access can corrupt state
  • Implement distributed rate limiting or resource throttling
  • Prefer PG advisory locks when you already rely on PostgreSQL and need ACID integration

Best practices

  • Set TTL to 2–3x the expected operation time and extend with a heartbeat if needed
  • Include a unique owner ID (fencing token) to avoid unsafe releases
  • Use exponential backoff with jitter for retries to reduce contention
  • Avoid holding locks across long awaits or slow external calls; release or renew before blocking
  • For critical HA scenarios, prefer Redlock (multi-node quorum) over single-node Redis

Example use cases

  • Deduplicating event handlers so each message is processed exactly once
  • Leader election for scheduled tasks where only one instance should run a job
  • Protecting a database migration step or external payment processing critical section
  • Distributed rate limit enforcement for shared APIs or resources
  • Using PostgreSQL advisory locks to coordinate tasks within an existing PG-backed service

FAQ

When should I choose Redis vs PostgreSQL locks?

Use Redis for low-latency, fast locks; choose PostgreSQL advisory locks if you already depend on PG and want ACID semantics and simpler operational surface.

How do I prevent a lock from accidentally releasing another instance's lock?

Always store an owner token with the lock and perform owner-checked release via atomic script or server-side check (fencing). Avoid raw delete operations.