Repository navigation
Conversation
Heartbeat every 5s and treat an orchestrator as stale after 30s, recovering after every heartbeat and once at startup, so a crashed process's executions are released within about 35s instead of 5-9 minutes. Heartbeats, registration, recovery and shutdown run on a connection of the orchestrator's own, so handlers holding every pool connection no longer delay heartbeats past the stale age. After a failed or slow heartbeat an orchestrator waits the stale age before recovering others, so a database outage does not make the first one to reconnect recover all the rest. _private_remove_orchestrators() returns early when it removes no one.
This branch has not been deployed
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.
What
Shorter liveness defaults, so a crashed orchestrator's executions are released in about 35 s instead of 5–9 min. Heartbeats can no longer be delayed by handlers that hold every pool connection.
src/orchestrator.ts): heartbeat every 5 s (was 30 s). An orchestrator is stale after 30 s without a heartbeat (was 300 s). Recovery runs after every heartbeat (was every 8th, about 240 s) and once at startup. Worst case: the last heartbeat comes just before the crash, the row is stale 30 s later, and the next heartbeat of any live orchestrator recovers it within 5 s. A process that comes back after a crash also recovers its predecessor at startup. No new options.DatabaseClient.withOwnConnection(). postgres.js does the same for itslistenconnection (it accepts its parsed options as they are). Pool saturation no longer delays heartbeats._private_remove_orchestrators()returns early when it deletes no orchestrator. This is not settlement SQL, so it does not overlap perf(settlement): plan settlement for one partition #103.Why / numbers
[reported] Pool saturation is real. I wrote the test first: a handler holds the pool's only connection in a transaction. Before the fix,
last_heartbeat_atdid not move for 60 fake seconds, so the orchestrator would have been recovered while it was still running. With the own connection, heartbeats keep advancing.Cost at 50 orchestrators with 52 queue partitions, measured with
EXPLAIN (ANALYZE, BUFFERS):update ... where locked_by = any(null)still visited every partition: 1.9 ms execution, ~29 ms first-call planning, and ~1.3–3.4 ms per call in a loop. With the early return a call costs ~0.3–0.4 ms round trip, against ~0.2–0.3 ms forselect 1. Running recovery after every heartbeat therefore costs about 1 ms of DB time per second across 50 orchestrators, and it no longer grows with the number of queues.Lost-claim grace (#93): already gone. #93 moved the orphan sweep into the claim statement, so nothing there depends on the heartbeat interval.
CLEANUP_TIMEOUT_MS(10 s) and the final flush deadline (10 s) stay below the stale age.Tests
New tests in
orchestrator-cleanup.test.ts:Liveness now runs on its own connection and keeps real time, so tests that made orchestrators stale with the test clock now move
last_heartbeat_atback instead. The own connection opens on a timer, so fake-timer tests use astartWithFakeTimershelper. Tests that inject faults into orchestrator statements onconductor.dbstubwithOwnConnectionto return it. The fixture drops test databasesWITH (FORCE), so one failing test that leaves an orchestrator connected does not make the following tests fail.just lint,just format,pnpm typecheckand the fullpnpm testpass (618/618). One earlier full run had a single failure inwait-deadlines.test.ts > waitForResult with a completion settled after the deadline > times out. It passed 3 of 3 times on its own and in the next full run. It looks like a timing flake under parallel load, and I found no link to this change.Follow-ups / notes
docs/scaling/horizontal.md(Crash Recovery).idx_orchestrators_heartbeatmakes every heartbeat a non-HOT update, and the planner seq-scans the table anyway. Dropping the index would be a cheap follow-up.