Repository navigation
fix: bound boot migration lock waits on PostgreSQL (TC-1176) - #316
Merged
Merged
Conversation
On PostgreSQL, m20261015_000000_delegation_cursor_index (#296) dropped and recreated idx_delegation_delegator with no lock_timeout, so a boot blocked indefinitely if any session held a lock on delegation. It now sets SET LOCAL lock_timeout = '30s' for its index rebuild and resets it after. sea-orm-migration runs all PostgreSQL migrations in one transaction, so a lock-wait failure rolls back the index change and the migration row; boot fails with an error naming the lock wait. SQLite and MySQL are unchanged.
Contributor
Author
|
APPROVE: see TC-1176 (head bddcabf) |
Review F1: on the first boot after 1.20.6, #291's CREATE TABLE delegation_cid_alias ... REFERENCES delegation runs before #296 and takes SHARE ROW EXCLUSIVE on delegation, so it still waited forever behind an open write transaction. Set SET LOCAL lock_timeout = '30s' once on the outer migrate() transaction, next to the TC-1054 statement_timeout exemption, so it covers every boot migration. Remove the per-migration SET LOCAL / TO DEFAULT from #296, which would have reset the outer setting for later migrations. Map 55P03 from anywhere in the migrations to a DbErr::Migration naming the lock wait; sea-orm-migration does not expose the failing migration's name in the error (its 'Applying migration' log line names it). Tests: from the 1.20.6 schema, an open write on delegation makes the boot fail at 30s with no migration applied or recorded and no lock_timeout left on the pooled session; after release the boot applies everything.
Contributor
Author
|
APPROVE: see TC-1176 (head be06fdf) |
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.
Refs TC-1176. Follow-up to #296. Review F1 is addressed in be06fdf.
Problem
On PostgreSQL, boot migrations take strong table locks with no
lock_timeout, so a boot can block indefinitely behind another session. While it waits, every later query on that table queues behind it. Two paths reach this on the first boot after 1.20.6:m20261014_000000_delegation_cid_aliasrunsCREATE TABLE delegation_cid_alias … REFERENCES delegation. That takes SHARE ROW EXCLUSIVE ondelegation, so it waits behind any open write transaction on it. This was review F1.m20261015_000000_delegation_cursor_indexdrops and recreatesidx_delegation_delegator. The drop takes ACCESS EXCLUSIVE, so it waits behind any open transaction that has toucheddelegation.Transaction behaviour (verified)
src/migrator.rs:262-271: on PostgreSQL every pending migration runs in onebegin()…commit(). MySQL and SQLite get no transaction.src/migrator.rs:386-397: each migration'sup()and itsseaql_migrationsinsert run on that same connection.tinycloud-core/src/db.rsmigrate()(called at boot fromSpaceDatabase::new_with_encryption) wraps this on PostgreSQL in an outer transaction, so the migrator'sBEGINbecomes a savepoint. SQLite and MySQL callMigrator::updirectly.Change
db::migrate(), PostgreSQL only: runsSET LOCAL lock_timeout = '30s'once on the outer transaction, next to the TC-1054statement_timeoutexemption. This covers every boot migration, including Normalize delegation identifiers #291, db: page account session discovery #296 and future ones.SET LOCAL, it ends with that transaction and never reaches pooled sessions.DbErr::Migration: "a migration gave up after waiting 30s (lock_timeout) for a table lock; another session holds a conflicting lock (SQLSTATE 55P03: …). Nothing was applied; end that session and restart." Other errors pass through unchanged.migrator.rs:386). ItsApplying migration '<name>'log line, printed just before, names it.SET LOCAL/TO DEFAULTwas redundant, andTO DEFAULTwould have reset the outer setting for later migrations.CREATE INDEX CONCURRENTLYis deliberately not used.Why 30s: deploys run with the writer stopped, so anything legitimately holding a lock is a short transaction that 30s outlasts. A stuck session fails the boot clearly instead of hanging it.
Evidence (native PostgreSQL 16.15, no Docker)
Unit tests
postgres_boot_from_1_20_6_fails_fast_behind_an_open_delegation_write(new, F1)postgres_cursor_index_fails_fast_while_delegation_is_locked(#296)UPDATE delegation …transaction whiledb::migrateruns.delegation_cid_alias, andSHOW lock_timeout=0on the single-connection pool that ran the migrations.lock_timeoutis still0after the commit.SETmakes the leak assertion fail ("30s"vs"0").Real-binary boot. The database was first booted with a v1.20.6 binary, which left 25 rows. A second session then held an open write (
RowExclusiveLock) ondelegation.Lock/relation: CREATE TABLE IF NOT EXISTS "delegation_cid_alias"Applying migration 'm20261014_000000_delegation_cid_alias', then exits 1 at 30s with the error above(delegator), 0 invalid indexes.(delegator, id).Existing tests
cargo test -p tinycloud-coreagainst PG passes all targets (417 lib tests), including everypostgres_*and migration test.postgres_pool_timeoutspass, including migrations running with no statement timeout. Its 4 black-hole tests need iptables, so they run in CI only.cargo fmt --all -- --checkandRUSTFLAGS=-Dwarnings cargo clippy -p tinycloud-core --all-targetsare clean.Each lock test takes about 30s because it uses the real limit.