pglease is a Python library for distributed task coordination using PostgreSQL. Zero additional infrastructure. It ensures singleton execution of tasks across multiple workers, pods, or processes — one worker wins, the rest wait or skip.
Architecture
Layered design with a pluggable backend:
src/pglease/
pglease.py Public API: acquire, try_acquire, release,
wait_for_lease, singleton_task decorator
async_pglease.py AsyncPGLease — dispatches to thread-pool executor
heartbeats.py Background daemon threads per lease
(exponential backoff retry, zombie detection)
models.py Lease frozen dataclass, AcquisitionResult,
UTC normalization
exceptions.py BackendError, LeaseLost
backends/
postgres.py PostgresBackend: lease table + FOR UPDATE
hybrid_postgres.py HybridPostgresBackend: advisory locks + table
(instant failover on connection loss)
Zero runtime dependencies beyond psycopg2-binary. Version comes from git tags via setuptools-scm with no-guess-dev and no-local-version.
API
Three usage patterns:
# Context manager
with pglease.acquire("batch-job", ttl_seconds=30) as lease:
do_work()
# Decorator
@pglease.singleton_task("batch-job", ttl_seconds=30, skip_if_taken=True)
def do_work():
...
# Explicit
lease = pglease.try_acquire("batch-job", ttl_seconds=30)
if lease:
do_work()
lease.release()
And async:
async with async_pglease.acquire("batch-job", ttl_seconds=30) as lease:
await do_work()
Heartbeat manager
One daemon thread per active lease. Retries transient database errors with exponential backoff (maximum 3 attempts). On genuine lease loss, invokes an on_lease_lost callback for graceful shutdown. Threads that fail to exit within 30 seconds are flagged as zombies, surfaced via get_zombie_threads() for operator inspection.
Hybrid backend
The HybridPostgresBackend combines PostgreSQL advisory locks with a lease table for sub-second failover:
- Advisory lock on the lease name guarantees mutual exclusion at the session level. If the connection drops, PostgreSQL releases the lock instantly.
- Lease table with
FOR UPDATErow locking provides the heartbeat TTL, holder identity, and acquisition timestamps.
The advisory lock gives instant failover. The lease table gives observability. Combined, they handle both the fast path (connection loss) and the slow path (process hang).
Credential safety
Database passwords in connection strings are redacted from all error messages and log output via _scrub_exc() before logging. Passwords never appear in stack traces.
UTC normalization
All datetimes are forced to UTC-aware, handling both naive and aware returns from psycopg2 consistently. Lease expiry comparisons are always in UTC.
Use cases
- Cron deduplication.
pg.acquire("daily-report")at job start. Skip if another instance runs it. - Worker pools. Workers claim tasks by lease name. One worker per task.
- Migration guard. Acquire a lock during schema changes to prevent concurrent DDL.
- Rate limiting. Transaction-level locks for short-lived exclusivity windows.
When not to use it
PostgreSQL advisory locks run on a single node. If your instance fails over to a standby, session locks are lost. Use HybridPostgresBackend with a short heartbeat TTL to minimize this window. For multi-primary clusters or sub-second watch semantics, use etcd or Consul.
Published on PyPI: pip install pglease. MIT license on GitHub.