Report #36161
[bug\_fix] canceling statement due to lock timeout \(DDL blocking\)
Set an explicit lock\_timeout shorter than the statement\_timeout \(e.g., 'SET lock\_timeout = '5s';'\) before executing DDL, so the statement fails fast if it cannot acquire the necessary lock, allowing the deployment to retry or alert rather than hanging indefinitely. Alternatively, identify and gracefully terminate long-running blocking queries using pg\_terminate\_backend\(\), or schedule DDL during low-traffic maintenance windows. The root cause is that DDL statements \(e.g., ALTER TABLE, CREATE INDEX\) require an ACCESS EXCLUSIVE lock which conflicts with any other lock, including long-running SELECT statements or uncommitted transactions.
Journey Context:
An Alembic migration to add a column to a large, heavily-queried table hangs indefinitely during deployment. The migration uses 'ALTER TABLE large\_table ADD COLUMN new\_col INT;'. Checking pg\_stat\_activity shows the ALTER statement in 'active' state with wait\_event\_type='Lock' and wait\_event='relation'. Another session shows a BI query 'SELECT \* FROM large\_table WHERE...' in 'idle in transaction' state holding a row lock. Realizing that ALTER TABLE requires an ACCESS EXCLUSIVE lock which waits for any other access to finish, but the BI query is taking hours. The initial thought is to increase statement\_timeout, but that doesn't help because the lock acquisition happens first. Reading the Postgres docs reveals the lock\_timeout parameter. Modifying the migration to execute 'SET lock\_timeout = '30s';' before the ALTER causes it to fail fast with 'canceling statement due to lock timeout' instead of hanging. The deployment script catches this specific error, sends an alert to kill the long-running BI query using pg\_terminate\_backend\(\), and then retries the migration successfully during a quieter window.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-18T15:10:21.440889+00:00— report_created — created