Acquire advisory lock around message database migrations#2523
Merged
jeremydmiller merged 1 commit intomainfrom Apr 16, 2026
Merged
Acquire advisory lock around message database migrations#2523jeremydmiller merged 1 commit intomainfrom
jeremydmiller merged 1 commit intomainfrom
Conversation
Concurrent calls to MessageDatabase.MigrateAsync against a fresh schema can race on CREATE SCHEMA IF NOT EXISTS and produce 23505 duplicate-key errors against pg_namespace_nspname_index (or equivalents on other engines). Wolverine's previous behavior was a generic catch-all retry with random backoff — a band-aid that masked the race rather than preventing it. This change mirrors Marten's pattern: acquire a session-scoped advisory lock around migration, run the DDL, then release. New DatabaseSettings.MigrationLockId (default 4006) is configurable so it can be aligned with Marten's StoreOptions.ApplyChangesLockId (default 4004) when both frameworks share a database via IntegrateWithWolverine. - Add DatabaseSettings.MigrationLockId - Add abstract ReleaseLockAsync to MessageDatabase<T> (virtual no-op default) - Implement ReleaseLockAsync on PostgreSQL, SQL Server, MySQL providers - Wrap MigrateAsync with bounded-retry lock acquisition + guaranteed release - Add PostgresqlBackedPersistence.OverrideMigrationLockId fluent override - Add concurrent-migration test plus a direct lock-primitive verification - Document the new setting in postgresql.md Closes #2518 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This was referenced Apr 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #2518 — concurrent calls to
MessageDatabase.MigrateAsyncagainst a fresh schema race onCREATE SCHEMA IF NOT EXISTSand produce 23505 duplicate-key errors. Wolverine's previous behavior was a generic catch-all retry with random backoff that masked the race rather than preventing it.Approach
Mirror Marten's pattern: acquire a session-scoped advisory lock around the migration, run the DDL, release the lock. The new
DatabaseSettings.MigrationLockId(default4006) is configurable so it can be aligned with Marten'sStoreOptions.ApplyChangesLockId(default4004) when both frameworks share a database viaIntegrateWithWolverine.Changes
DatabaseSettings.MigrationLockId— new configurable lock id (default4006)MessageDatabase<T>.ReleaseLockAsync— new virtual method (no-op default for SQLite)ReleaseLockAsyncMigrateAsync— bounded-retry lock acquisition (10 attempts, linear backoff up to 1s each), guaranteed release infinally, existing catch-all retry preserved as a backstopPostgresqlBackedPersistence.OverrideMigrationLockId— fluent override to align with MartenPostgresqlTests/Bugs/Bug_2518_*docs/guide/durability/postgresql.mdwith alignment exampleOut of scope
ApplyChangesLockIdwhenIntegrateWithWolverineis active — would require coordination fromWolverine.Marten. Worth a follow-up issue.OracleMessageStoredoesn't inherit fromMessageDatabase<T>and has its own admin path)Test plan
Bug_2518_concurrent_migration_advisory_lock.concurrent_migrate_async_calls_do_not_race_on_create_schema— 16 concurrent migrations against a fresh schema, all complete without errorsmigration_lock_id_is_actually_held_during_migration— verifies the advisory lock primitive blocks a second session and is released cleanlyPostgresqlMessageStoreTests(38 tests) all still pass🤖 Generated with Claude Code