Skip to content

fix(postgres,pgx): use pg_try_advisory_lock with exponential backoff in Lock - #1451

Open
amirsalarsafaei wants to merge 5 commits into
golang-migrate:masterfrom
amirsalarsafaei:postgres-try-advisory-lock
Open

amirsalarsafaei wants to merge 5 commits into
golang-migrate:masterfrom
amirsalarsafaei:postgres-try-advisory-lock

Conversation

@amirsalarsafaei

Copy link
Copy Markdown

Fixes #960, picks up #962.

#962 went stale and no longer applied on master, so I rebased it and squashed it into a single commit. @AkuSilvenius is still the author. On top of it:

  • Lock stops retrying and returns ctx.Err() once the ctx passed to WithConnection is done. Open and WithInstance use context.Background(), so nothing changes for them, and the database.Driver interface stays the same.
  • Errors from the database are returned instead of retried. Before, a broken connection would make Lock retry forever.
  • If Config.Locking isn't set, the default retry intervals are used. Before, WithInstance/WithConnection callers got a 0 interval and pg_try_advisory_lock ran in a tight loop.
  • The same fix for pgx and pgx/v5, including x-lock-retry-max-interval. In pgx it only affects the advisory lock strategy.

Tests:

  • Concurrent migration tests for postgres, pgx and pgx/v5. The postgres one fails on master.
  • A test where the WithConnection ctx gets canceled while another connection holds the lock.

The postgres, pgx and pgx/v5 test suites pass locally on postgres 14 to 18.

AkuSilvenius and others added 3 commits October 3, 2026 18:05
pg_advisory_lock blocks until the lock is free, so concurrent migrations
can hang forever (golang-migrate#960). Poll pg_try_advisory_lock instead and retry with
an exponential backoff. The max retry interval can be set with
x-lock-retry-max-interval.

Rebased from golang-migrate#962.

Fixes golang-migrate#960
Also return database errors right away instead of retrying them, and use
the default retry intervals when Config.Locking is not set. Without that,
WithInstance and WithConnection callers retried with no delay at all.
Same change as for the postgres driver, for pgx and pgx/v5. For pgx (v4)
this only affects the advisory lock strategy.
Copilot AI balanced review requested due to automatic review settings October 3, 2026 10:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Retry boundary handling and scheduling-dependent regression tests need correction.

Review effort: Balanced
Findings: 6 Medium severity

Open (6)
What changed in this PR

Reworks PostgreSQL advisory locking to avoid concurrent migration deadlocks.

Changes:

  • Uses nonblocking advisory locks with exponential backoff.
  • Adds retry configuration and documentation across PostgreSQL drivers.
  • Adds concurrent migration and cancellation tests.
File Description
database/​postgres/​README.md Documents retry configuration.
database/​postgres/​postgres.go Adds retrying, context-aware locking.
database/​postgres/​postgres_test.go Adds concurrency and cancellation tests.
database/​pgx/​README.md Documents retry configuration.
database/​pgx/​pgx.go Adds advisory-lock retries.
database/​pgx/​pgx_test.go Adds concurrent migration coverage.
database/​pgx/​v5/​README.md Documents retry configuration.
database/​pgx/​v5/​pgx.go Adds advisory-lock retries.
database/​pgx/​v5/​pgx_test.go Adds concurrent migration coverage.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread database/pgx/pgx.go Outdated
Comment thread database/pgx/pgx_test.go Outdated
Comment thread database/pgx/v5/pgx.go Outdated
Comment thread database/pgx/v5/pgx_test.go Outdated
Comment thread database/postgres/postgres.go Outdated
Comment thread database/postgres/postgres_test.go Outdated
… initial one

Lock retry defaults are now applied in one place. A max interval below the
initial interval is raised to it, otherwise the first retry would already
wait longer than the max. This also applies to x-lock-retry-max-interval,
where such values used to be ignored.
Up and Drop take the lock separately, so a worker could get the lock in
between and fail with ErrNoChange. Run only Up, expect exactly one worker
to apply the migrations, and stop calling t.Fatal from the worker
goroutines.
@coveralls

Copy link
Copy Markdown

Coverage Status

coverage: 55.422%. first build — amirsalarsafaei:postgres-try-advisory-lock into golang-migrate:master

@amirsalarsafaei

Copy link
Copy Markdown
Author

@Fontinalis Hi, Could you take a look at this?

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Postgres deadlocking when multiple processes run CREATE INDEX CONCURRENTLY

4 participants