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> | undefined

View on GitHub (pinned to 9696913134)

Solutions

  1. Ensure only one process runs migrations at a time (deploy hook ordering, leader election, or running migrations as a separate step)
  2. 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
  3. 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

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


AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27). Data as JSON: /api/errors/5bdb9798a89bd8eb. Report an issue: GitHub.