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
3.x.x: flaky test: a_relaunched_frontend_recovers_one_row_at_the_same_statement_cost_at_both_cardinalities counts one extra candidate-poll transaction (65 vs 61 statements) #729
candidate_storm_restart::a_relaunched_frontend_recovers_one_row_at_the_same_statement_cost_at_both_cardinalities failed once in CI. It counted one extra candidate-poll transaction in its baseline, so it's a timing flake in the measurement, not the server finding its message claims.
Seen: CI on #725's first head 69797f64 (the v3.0.0-rc.4 runtime, differing from the tag only in the rc.4 notes): run 37469847966, job 112290176938 (PostgreSQL shard 2). The binary's other 20 tests passed.
Error:recovering one row cost a relaunched frontend 65 statements with 23 rows behind it and 61 with 99. Per-row recovery cost is not independent of N; this is a finding about the server, not this test.
The two statement lists differ by exactly one transaction. The baseline (23 rows) ran one more of each of these; everything else is identical:
Statement
Baseline (23 rows)
Storm (99 rows)
BEGIN
7
6
SELECT c.fatal_error, EXISTS(… qbit_ledger_writer_lease …) … FROM qbit_prism_cluster c CROSS JOIN qbit_prism_submission_hold h …
6
5
WITH stepped AS (SELECT o.block_hash FROM qbit_block_candidate_outbox …
1
0
COMMIT
7
6
That's one candidate-poll cycle. The extra cost sits at the smaller N, so it isn't per-row scaling: a periodic poll iteration fell inside the baseline's counting window.
Intermittent: the same runtime passed it in CI on #725's final head 4a8e855a (run 37470368719).
Fix (test-only): count only the recovery's own statements. For example, exclude the poll loop's transactions, or compare the two cardinalities' statement multisets after removing whole poll cycles, so a poll that happens to land in one window can't fail the comparison.
Another occurrence on #777 (lockfile-only change), run 39806884637, shard 2: 65 vs 72 statements. This time the extra transaction is the metrics collector's (metrics/collectors.rs:56), not a candidate poll. The storm run's diff from the baseline is exactly these 7 statements:
BEGIN
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY
SELECT set_config('statement_timeout','2000',true),set_config('lock_timeout','500',true)
SELECT count(*), COALESCE(GREATEST(0,extract(epoch FROM transaction_timestamp()-min(created_at))),0)::double precis…
SELECT max(upper_seq)-qbit_prism_share_next_seq() FROM qbit_prism_share_partitions WHERE state='attached'
SELECT block_hash FROM qbit_block_candidate_outbox WHERE block_hash=ANY($1::text[]) AND state='orphaned'
COMMIT
So the comparison picks up any periodic background transaction that falls inside the recovery window, not just the candidate poll. A fix should exclude, or attribute, the background tasks' transactions (the metrics collector's ten-second tick included) rather than special-case one of them.
#788 again (closed and reopened, same head), Oct 27
72 vs 65
Since Oct 22, PostgreSQL shard 2 has failed in 5 of 23 completed CI runs. Every one of those failures was this test, and one of them was a 3.x.x push.
Why the idle windows can never learn this transaction. The collector's snapshot (metrics/collectors.rs) runs its orphan-evidence lookup (… WHERE block_hash=ANY($1::text[]) AND state='orphaned') only when revision_work_terminal_probe() returns hashes. It returns hashes only once the process has registered a block in its landing metrics. coordinator.rs registers one on three events:
a definitive submitblock acceptance;
an active-chain candidate observation;
a reconciled landed block.
A registered block closes when it is proven orphaned, or when the revision work for its landing reaches a miner's socket. No miner connects in this test, so after the first registration the lookup runs on every 10 s tick.
In this test, the relaunched child registers nothing during its two idle windows, so the lookup's text never enters the background set. In the measured window, the child offers the siblings it claims ahead of the recovered row, and the node accepts them. From then on, any collector tick that overlaps the recovered row's marked span contains one statement that was never seen while idle, so foreground counts the whole 7-statement run.
So this is not an idle window that settled too early. It is a background statement that only exists after an acceptance. How likely a measurement is to hit it depends on how long the recovered row's span lasts compared with the 10 s tick.
Fix direction (test-only). Learn the background set in the state the measured window runs in. For example, let the relaunched child offer and land one sibling before the idle windows. Then every background statement that depends on an accepted or landed block, this lookup included, is seen while idle. That covers the class rather than special-casing the collector.
The original Oct 6 failure (61 vs 65): it wasn't an extra transaction. The 99-row run lost four statements of the recovered row's own claim transaction. That poll sent BEGIN and the fence before the measured window's mark, so runs() split the transaction.
#792 also handles a third case found while testing it: a bare ROLLBACK that SQLx sends when the job/blob pruner's COMMIT outlasts its 5 s deadline on a slow disk.
Fixed by #792, merged into 3.x.x as 2e11f1bf (its "Fixes #729" does not fire on a non-default branch). It fixes all three causes in candidate_storm_restart.rs. (1) The metrics collector's orphan lookup, which starts only after the recovered row's own accepted block and was counted as foreground: a sibling now lands before the idle windows, and the measured executions come from the verification window. (2) The claim transaction straddled the mark (the Oct 6 65-vs-61 variant): it is now kept whole. (3) The job/blob pruner's bare ROLLBACK after a slow COMMIT: transaction control is now neutral in the foreground count. Shard 2 passed on both CI heads of #792 (runs 40565379439 and 40569032223). A failure of this test on a tree that contains 2e11f1bf is a new finding.
Activity
kiwidream commentedon Oct 22, 2026
Another occurrence on #777 (lockfile-only change), run 39806884637, shard 2: 65 vs 72 statements. This time the extra transaction is the metrics collector's (
metrics/collectors.rs:56), not a candidate poll. The storm run's diff from the baseline is exactly these 7 statements:So the comparison picks up any periodic background transaction that falls inside the recovery window, not just the candidate poll. A fix should exclude, or attribute, the background tasks' transactions (the metrics collector's ten-second tick included) rather than special-case one of them.
kiwidream commentedon Oct 27, 2026
Four more occurrences since the comment above. Each has the metrics-collector signature: one cardinality counts 7 extra statements.
3.x.xpush at59194d03, Oct 26Since Oct 22, PostgreSQL shard 2 has failed in 5 of 23 completed CI runs. Every one of those failures was this test, and one of them was a
3.x.xpush.Why the idle windows can never learn this transaction. The collector's snapshot (
metrics/collectors.rs) runs its orphan-evidence lookup (… WHERE block_hash=ANY($1::text[]) AND state='orphaned') only whenrevision_work_terminal_probe()returns hashes. It returns hashes only once the process has registered a block in its landing metrics.coordinator.rsregisters one on three events:submitblockacceptance;A registered block closes when it is proven orphaned, or when the revision work for its landing reaches a miner's socket. No miner connects in this test, so after the first registration the lookup runs on every 10 s tick.
In this test, the relaunched child registers nothing during its two idle windows, so the lookup's text never enters the background set. In the measured window, the child offers the siblings it claims ahead of the recovered row, and the node accepts them. From then on, any collector tick that overlaps the recovered row's marked span contains one statement that was never seen while idle, so
foregroundcounts the whole 7-statement run.So this is not an idle window that settled too early. It is a background statement that only exists after an acceptance. How likely a measurement is to hit it depends on how long the recovered row's span lasts compared with the 10 s tick.
Fix direction (test-only). Learn the background set in the state the measured window runs in. For example, let the relaunched child offer and land one sibling before the idle windows. Then every background statement that depends on an accepted or landed block, this lookup included, is seen while idle. That covers the class rather than special-casing the collector.
🤖 Generated with Claude Code
kiwidream commentedon Oct 27, 2026
Fix in #792. Two corrections to my comment above, from the deterministic reproductions made for it:
Metrics::accepted_block) registers the block that switches on the collector's orphan lookup.BEGINand the fence before the measured window's mark, soruns()split the transaction.#792 also handles a third case found while testing it: a bare
ROLLBACKthat SQLx sends when the job/blob pruner'sCOMMIToutlasts its 5 s deadline on a slow disk.🤖 Generated with Claude Code
3.x.x: learn the restart test's background statements after a landing…
3.x.x: learn the restart test's background statements after a landing…
kiwidream commentedon Oct 27, 2026
Fixed by #792, merged into
3.x.xas2e11f1bf(its "Fixes #729" does not fire on a non-default branch). It fixes all three causes incandidate_storm_restart.rs. (1) The metrics collector's orphan lookup, which starts only after the recovered row's own accepted block and was counted as foreground: a sibling now lands before the idle windows, and the measured executions come from the verification window. (2) The claim transaction straddled the mark (the Oct 6 65-vs-61 variant): it is now kept whole. (3) The job/blob pruner's bareROLLBACKafter a slow COMMIT: transaction control is now neutral in the foreground count. Shard 2 passed on both CI heads of #792 (runs 40565379439 and 40569032223). A failure of this test on a tree that contains2e11f1bfis a new finding.