PostgreSQL locks coordinate conflicting operations, while MVCC handles visibility. Row locks protect selected rows; table locks protect broader operations; advisory locks coordinate application-defined resources. Deadlocks arise when transactions wait in a cycle, so PostgreSQL aborts one. Good designs lock narrowly, in consistent order, for short periods, with timeouts and complete retries.
Locks are queueing contracts. A transaction owns locks until its boundary. The question is not “do locks exist?” but “which resource, mode, order, duration, and failure response?”
For a database-backed job queue:
SKIP LOCKED is suitable for queue-like work where skipped rows may be processed by another worker. It is not a general consistency feature and can produce an intentionally incomplete view.
Inspect blockers with pg_stat_activity and pg_locks; use vetted queries and least privilege. Never terminate sessions until you understand owner, transaction, criticality, and rollback impact.
Deadlock prevention: access shared resources in a consistent order. If two transactions update tickets 1 and 2, both should lock lower ID first.
Model workflows create long and variable work. Claim a short database job, commit, perform the model call, then store the result in another short transaction with lease/version checks. Do not hold a row lock across the call. Advisory locks may deduplicate a bounded operation, but persisted state remains necessary for recovery.
Use two sessions to create a deadlock on disposable rows by locking them in opposite order; capture PostgreSQL’s deadlock error and full rollback. Repeat with consistent ordering. Then run two queue workers and prove they claim different jobs.
Design an ai_jobs lease with locked_by, locked_until, attempts, and terminal state. Explain reclaim after worker crash.
Acceptance criterion: two workers cannot own one live lease, an expired lease is recoverable, and duplicate completion is rejected or idempotent.