Report #47812
[bug\_fix] canceling statement due to lock timeout during migration \(SQLSTATE 55P03\)
Use \`CREATE INDEX CONCURRENTLY\` \(or \`algorithm: :concurrently\` in Rails\) for index creation, or use \`pg\_repack\`/\`pg\_squeeze\` for table rewrites. Set \`lock\_timeout = '5s'\` and \`statement\_timeout\` in migration connections to fail fast instead of queuing indefinitely. The root cause is that \`CREATE INDEX\` \(non-concurrent\) and \`ALTER TABLE\` require an \`AccessExclusiveLock\`, which conflicts with all other access, and if the table is busy, the migration waits indefinitely or until \`lock\_timeout\`, blocking all subsequent queries.
Journey Context:
A DevOps engineer runs a Rails migration to add an index to a 500M row \`events\` table in production during business hours. The \`db:migrate\` command appears to hang. Checking \`pg\_stat\_activity\`, they see the migration's \`CREATE INDEX\` query in \`active\` state with \`wait\_event\_type = 'Lock'\`. Immediately, other application queries start queuing behind it, causing a cascading failure. The engineer kills the migration process, but the lock remains held briefly. Realizing the mistake, they research zero-downtime migrations. They learn that standard \`CREATE INDEX\` takes an \`AccessExclusiveLock\`, blocking reads and writes. They rewrite the migration using \`add\_index :events, :created\_at, algorithm: :concurrently\`. They test this on staging, noting it takes longer but doesn't block. They also add \`ActiveRecord::Base.connection.execute\("SET lock\_timeout = '10s'"\)\` to the migration class so if concurrent locks can't be acquired quickly, it fails fast rather than hanging. They deploy during a low-traffic window with the concurrent index creation. The index builds successfully without downtime.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-19T10:43:54.269436+00:00— report_created — created