remix-run/remix · error · Error
Postgres migration lock could not be acquired
Error message
Postgres migration lock could not be acquired
What it means
The driver acquires a Postgres advisory lock (pg_advisory_lock on hashtext(name)) with a lock_timeout for migrations. If the lock query fails — typically a timeout because another process holds the lock — this error is thrown with the underlying failure as `cause`.
Source
Thrown at packages/data-table-postgres/src/lib/driver.ts:589
void (client as PostgresClient).end().catch(() => undefined)
}
// Matches the 60 second wait bound used by the MySQL driver's get_lock().
const MIGRATION_LOCK_TIMEOUT_MS = 60_000
async function runWithPostgresMigrationLock<result>(
client: PostgresClient | PostgresPoolClient,
name: string,
driver: PostgresDatabaseDriver,
run: (driver: DatabaseDriver<'postgres'>) => Promise<result>,
): Promise<result> {
await client.query('set lock_timeout to ' + String(MIGRATION_LOCK_TIMEOUT_MS))
try {
await client.query('select pg_advisory_lock(hashtext($1))', [name])
} catch (cause) {
await client.query('set lock_timeout to default').catch(() => undefined)
throw new Error('Postgres migration lock could not be acquired', { cause })
}
await client.query('set lock_timeout to default')
let outcome: { status: 'success'; value: result } | { status: 'failure'; error: unknown }
try {
outcome = { status: 'success', value: await run(driver) }
} catch (error) {
outcome = { status: 'failure', error }
}
let unlockFailed = false
let unlockError: unknown
try {
let result = await client.query('select pg_advisory_unlock(hashtext($1)) as "released"', [name])
let row = result.rows[0] as Record<string, unknown> | undefinedView on GitHub (pinned to 9696913134)
Solutions
- Ensure only one process runs migrations at a time (deploy hook ordering, leader election, or running migrations as a separate step)
- Find and release the stuck advisory lock: select pg_advisory_unlock, or terminate the holding backend via pg_termination_backend after verifying it's safe
- Retry the migration once the competing process finishes; investigate the `cause` for the underlying timeout
Example fix
-- diagnose the holder select pid, state, query from pg_stat_activity where query like '%pg_advisory_lock%'; -- release if orphaned (replaces restart loop) select pg_terminate_backend(pid);
Defensive patterns
Strategy: retry
Validate before calling
// pre-flight: check for existing advisory lock holders select pid, query from pg_stat_activity where query like '%pg_advisory_lock%';
Try / catch
catch (e) { if (e instanceof Error && e.message === 'Postgres migration lock could not be acquired') { await delay(backoff); return runMigrationsWithRetry() } throw e } Prevention
- Run migrations as a single serialized deploy step
- One migrator process at a time (leader election or deploy ordering)
- Monitor pg_locks for stuck advisory locks during deploys
When it happens
Trigger: Two processes migrating simultaneously (deploy + CI, two app instances, a stuck psql session holding the advisory lock) so lock_timeout (MIGRATION_LOCK_TIMEOUT_MS) elapses; Postgres server unreachable or restarting during lock acquisition.
Common situations: Blue-green or multi-replica deployments racing on migrate; a previous migration process killed while holding the advisory lock; long-running migration blocking the next deploy's migrations past the timeout.
Related errors
- Postgres migration lock was not held by the reserved connect
- Postgres migration lock is already held by this database
- Route module ${routesFile} must export a named "routes" valu
- MySQL migration lock is already held by this database
- MySQL migration lock could not be acquired
AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27).
Data as JSON: /api/errors/5bdb9798a89bd8eb.
Report an issue: GitHub.