One idle transaction. A waiting migration. Then reads stop.
A queued DDL lock stopped eight test readers for 14 seconds. Follow the captured blocking chain and the measured timeout response.
A queued DDL lock stopped eight test readers for 14 seconds. Follow the captured blocking chain and the measured timeout response.
Lab disclosure: Synthetic workload and generated data in an isolated PostgreSQL 17 environment. This is a measured reproduction, not a client incident.
Eight clients ran primary-key reads against a 200,000-row orders table. Five seconds into the run, another session selected from the table and then sat idle inside its transaction. At ten seconds, a deployment issued a catalog-only ALTER TABLE orders ADD COLUMN note text. The operation needed an AccessExclusiveLock and waited behind the open transaction.
Reader throughput fell to zero from seconds 11 through 24. The DDL itself took 14.96 seconds because it waited for the old transaction to commit at second 25. This is the kind of incident where query plans remain healthy while an ordinary endpoint appears to freeze after a migration begins.
| Session | State / statement | Relation lock | Granted? | Blocked by |
|---|---|---|---|---|
| A · pid 252 | Idle in transaction | AccessShareLock | Yes | — |
| B · pid 259 | ALTER TABLE waiting | AccessExclusiveLock | No | A |
| Eight readers | Primary-key SELECT waiting | AccessShareLock | No | B |
This is the snapshot taken four seconds after the DDL was issued. PIDs and exact timings vary by run. The repository's labs/ddl-lock-queue folder contains the setup, workload script and captured output.
The idle reader did not directly block the later readers. The pending AccessExclusiveLock request sat between them in the lock queue. pg_blocking_pids() showed the DDL blocked by the idle transaction, and every reader blocked by the DDL. PostgreSQL's lock-mode documentation explains why an exclusive request conflicts with ordinary reads.
The lab was repeated with lock_timeout = '2s' set only for the migration session. The DDL failed after 2.01 seconds instead of waiting nearly 15 seconds, and reader throughput recovered by second 13. The timed-out migration still needed to be retried later. A bounded timeout reduced the queue's duration; it did not make the schema change unnecessary or guarantee a safe deployment.
lock_timeout with a deliberate retry path.The laptop throughput values are not a production promise. The repeatable result is the blocking chain and the shorter stall when the waiting DDL fails promptly. For the broader diagnostic method, see the PostgreSQL concurrency guide.
Bring the time window, application symptom and available wait or activity snapshots. The investigation connects the queue to a safe next change.
Prefer to talk first? Book a 20-minute triage call.