{"record":{"id":"5bdb9798a89bd8eb","repo":"remix-run/remix","slug":"postgres-migration-lock-could-not-be-acquired","errorCode":null,"errorMessage":"Postgres migration lock could not be acquired","messagePattern":"Postgres migration lock could not be acquired","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/data-table-postgres/src/lib/driver.ts","lineNumber":589,"sourceCode":"  void (client as PostgresClient).end().catch(() => undefined)\n}\n\n// Matches the 60 second wait bound used by the MySQL driver's get_lock().\nconst MIGRATION_LOCK_TIMEOUT_MS = 60_000\n\nasync function runWithPostgresMigrationLock<result>(\n  client: PostgresClient | PostgresPoolClient,\n  name: string,\n  driver: PostgresDatabaseDriver,\n  run: (driver: DatabaseDriver<'postgres'>) => Promise<result>,\n): Promise<result> {\n  await client.query('set lock_timeout to ' + String(MIGRATION_LOCK_TIMEOUT_MS))\n\n  try {\n    await client.query('select pg_advisory_lock(hashtext($1))', [name])\n  } catch (cause) {\n    await client.query('set lock_timeout to default').catch(() => undefined)\n    throw new Error('Postgres migration lock could not be acquired', { cause })\n  }\n\n  await client.query('set lock_timeout to default')\n\n  let outcome: { status: 'success'; value: result } | { status: 'failure'; error: unknown }\n\n  try {\n    outcome = { status: 'success', value: await run(driver) }\n  } catch (error) {\n    outcome = { status: 'failure', error }\n  }\n\n  let unlockFailed = false\n  let unlockError: unknown\n\n  try {\n    let result = await client.query('select pg_advisory_unlock(hashtext($1)) as \"released\"', [name])\n    let row = result.rows[0] as Record<string, unknown> | undefined","sourceCodeStart":571,"sourceCodeEnd":607,"githubUrl":"https://github.com/remix-run/remix/blob/9696913134be3a4423513d2775f7b31d6917c049/packages/data-table-postgres/src/lib/driver.ts#L571-L607","documentation":"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`.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"-- diagnose the holder\nselect pid, state, query from pg_stat_activity where query like '%pg_advisory_lock%';\n-- release if orphaned (replaces restart loop)\nselect pg_terminate_backend(pid);","handlingStrategy":"retry","validationCode":"// pre-flight: check for existing advisory lock holders\nselect pid, query from pg_stat_activity where query like '%pg_advisory_lock%';","typeGuard":null,"tryCatchPattern":"catch (e) { if (e instanceof Error && e.message === 'Postgres migration lock could not be acquired') { await delay(backoff); return runMigrationsWithRetry() } throw e }","preventionTips":["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"],"tags":["postgres","migration","advisory-lock","timeout"],"backgroundTag":"migration-lock-timeout","analyzedSha":"9696913134be3a4423513d2775f7b31d6917c049","analyzedAt":"2026-08-27T19:55:01.024Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}