You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
PostgreSQL: DeleteTable's first catalog select has no lock timeout and waits behind CreateBackup #614
crates/storage-postgres/src/delete_table.rs, the SELECT ... FROM tables WHERE account_id = $1 AND table_name = $2 AND table_status IN ('ACTIVE', 'CREATING') FOR UPDATE that starts every DeleteTable, on main at abf8082f (lines 27 to 41).
What happens
The select runs on a catalog transaction with no lock_timeout. It waits for any transaction that holds the table's row:
CreateBackup holds the row FOR SHARE from its first catalog read until the data snapshot has been taken (crates/storage-postgres/src/backup_engine.rs, the comment above the FOR SHARE select explains why), so a DeleteTable issued during a backup of a large table waits for the whole snapshot.
UpdateTable holds the row FOR UPDATE for the length of its catalog transaction (crates/storage-postgres/src/update_table.rs).
While it waits, the request holds one connection of the catalog pool. With pool_size concurrent DeleteTable calls against tables being backed up, the pool is exhausted for other requests until the backups finish or sqlx's 30 s acquire timeout fires for the later callers.
The same shape was bounded for the control-plane pass in PR #504 (a transaction-local lock_timeout on the catalog transaction, and a defined answer when it fires). This select was left as it was because it is on main and not part of that fix.
Service behaviour
On Amazon DynamoDB, DeleteTable during a CreateBackup of the same table answers 200 with TableStatus: DELETING and the backup completes (the backup is of the table state at request time). This was not re-measured for this issue; the row for it should be measured before the fix is written.
Suggested fix
Set a transaction-local lock_timeout on the DeleteTable catalog transaction before the select (the constant PR #504 introduces in worker_store.rs can move to a shared place). On lock_not_available, decide the answer from the measurement: if the service serves the delete during a backup, the fix is to not block on the backup's FOR SHARE at all (for example by reading the row without the lock and marking it DELETING with a conditional UPDATE, letting the backup's definition read stay consistent through its own snapshot); otherwise answer ResourceInUseException with the measured sentence.
A test: hold the row FOR SHARE from a second connection, call DeleteTable, expect it to return within the timeout rather than wait for the release.
PR #504 now changes only DeleteTable on a separate data database, where the catalog row is committed as DELETING before the data commit and removed after that commit lands. It no longer puts a lock_timeout on the control-plane pass or adds a constant to worker_store.rs, so the paragraph saying the same shape was bounded in PR #504, and the suggestion to move its constant to a shared place, don't apply anymore.
The select has changed on main since I filed this. After #395 it reads table_status IN ('ACTIVE', 'CREATING', 'DELETING') (delete_table.rs lines 44 to 59 on main at 1d0d8f4a2), so a DeleteTable of a table that's already DELETING also waits for any control-plane pass that has the row locked. The pass selects the due DELETING rows FOR UPDATE SKIP LOCKED and holds those locks until it commits at the end of the pass (worker_store.rs lines 94 to 177).
CreateBackup still holds the row FOR SHARE until its data snapshot is taken (backup_engine.rs lines 986 to 1009), and UpdateTable still takes it FOR UPDATE (update_table.rs line 124).
Activity
LeeroyHannigan commentedon Oct 31, 2026
PR #504 now changes only DeleteTable on a separate data database, where the catalog row is committed as DELETING before the data commit and removed after that commit lands. It no longer puts a
lock_timeouton the control-plane pass or adds a constant toworker_store.rs, so the paragraph saying the same shape was bounded in PR #504, and the suggestion to move its constant to a shared place, don't apply anymore.The select has changed on main since I filed this. After #395 it reads
table_status IN ('ACTIVE', 'CREATING', 'DELETING')(delete_table.rslines 44 to 59 on main at1d0d8f4a2), so a DeleteTable of a table that's already DELETING also waits for any control-plane pass that has the row locked. The pass selects the due DELETING rowsFOR UPDATE SKIP LOCKEDand holds those locks until it commits at the end of the pass (worker_store.rslines 94 to 177).CreateBackup still holds the row
FOR SHAREuntil its data snapshot is taken (backup_engine.rslines 986 to 1009), and UpdateTable still takes itFOR UPDATE(update_table.rsline 124).