Repository navigation
fix(aster-srv): serve /healthz while migrations wait for table locks - #164
Merged
Merged
Conversation
Production incident 2026-10-17: release 0.21.0's migration 5 (ALTER TABLE on classifier_state and quarantine) waited behind the previous release's classifier pass. It gave up after 12 attempts and exited, and before that the liveness probe killed the pod (exit 137) because nothing listened during App::start. The rollout restarted the wait 8 times and the apply timed out. Listen first, migrate in the background, report 503 on /readyz and every other route until the schema is applied, and retry migrations without exiting. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
calvin-archastro
marked this pull request as draft
October 18, 2026 17:23
Extract the retry-then-start-loops sequence into App::migrate_until_ready. Table-lock waits warn (error after 10 minutes); any other migration error logs at error immediately. Pause 5s after the first pass, 30s after later ones, so the lock queue stalls the previous release's readers less. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
calvin-archastro
marked this pull request as ready for review
October 18, 2026 17:35
Contributor
Author
|
ArchDev is watching this pull request. State: settled (cycle 2 of 2 for head 243ec42).
Paused by the |
Retries never stop, so a permanent migration failure leaves the pod live and unready forever. /readyz now says whether it is waiting_for_locks or failing, and the runbook covers a deploy that never becomes ready.
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.
Review on ArchCode
Problem
Production incident 2026-10-17 (firstlanding
auto_apply_productionrun 39067438593, rollingaster-srv0.20.0 to 0.21.0):It converged once the old pods' passes stopped holding the tables. Invariant broken: the process listened only after migrations finished, so any lock wait longer than the liveness window killed it, and the retry cap turned a transient wait into an exit.
What changed
Before:
App::startmigrates, then main binds. After:App::connectbuilds the app with no DB schema work, main binds immediately, and migrations run in a background task that retries every 5s without exiting, then starts the retention, classifier and webhook loops./healthzis 200 throughout, so the liveness probe does not kill the pod./readyzand every other route return 503 untilApp::migratesucceeds (App::schema_ready), so the pod receives no traffic against an old schema.App::start*still migrates before returning, so tests and other callers are unchanged.App::migrate_until_readyinlib.rs(main only wires logging and the loops to start). Table-lock waits and deadlocks (db::is_lock_contention) log a warning until they have lasted 10 minutes, then error. Any other migration error logs at error immediately. It never exits.lock_timeout, and a waiting ALTER queues other sessions behind it, so the longer pause cuts how often the previous release's readers stall.lock_timeout, the advisory lock.Scope / risk / impact
crates/aster-srv. Low risk. One behavior change: a persistent migration error (for example a bad SQL migration) now keeps the pod running but not ready and logsmigrations not applied yet; retrying, instead of exiting.Testing
crates/aster-srv/tests/startup.rs,the_server_stays_alive_while_a_migration_waits_for_a_table_lock. It uses real Postgres 16 and real HTTP. A second connection holdsACCESS SHAREontest_results.quarantine(the old release) while migration 5 runs. It asserts/healthz200,/readyz503 and a data route 503, then releases the lock and asserts/readyz200.startup.rsalso has:background_loops_start_only_after_the_schema_is_ready(lock held: warnings only,on_readynot called, then called after release) anda_migration_error_that_is_not_a_lock_wait_logs_an_error_and_stays_unready(no DB listening: error severity on each pass, keeps retrying, unready, loops never start; needs no Postgres).cargo test --locked -p aster-srvwithASTER_SRV_TEST_DATABASE_URLset: all pass.cargo clippy -p aster-srv --all-targets -D warningsandcargo fmtare clean.Follow-ups
classifier_state/quarantine. 0.21.0 already shortens it (4977ffb); this PR does not change it.infra/services/aster-srv: a startupProbe is optional after this change.🤖 Generated with Claude Code