Two gate flakes: a whole-VM log assertion, and a plan pinned to one of two usable indexes - #932
Merged
Merged
Conversation
… of two usable indexes session_end_test asserted capture_log's output was empty. capture_log collects every process's logs, so an async neighbour's error line (refusal_test walks every advance error through the channel's logging catch-all) failed it at random. It now refutes only this call's own line, found by its unique tenant_id. revoke_expired_dispatches_worker_test asserted the sweep plans on dispatches_expires_at_active_index. dispatches_tenant_id_expires_at_index serves the same expires_at condition, and the planner's pick between them moves with the rows other tests leave behind. The plan assertion now requires an index scan with the expires_at condition and no Seq Scan, and the partial index's match to the sweep's predicate is asserted from its definition in pg_indexes. Mutations (bin/mutate.sh): the busy clause removed so the call logs (exit 0, caught); the definition read from the other index (exit 0, caught). Removing SET LOCAL enable_seqscan survives (exit 1): the shared test tables now hold enough rows that the planner uses an index unprompted, so that pre-existing refute is not load-bearing today.
…rrect what describes it - AC-32.1.2's guard now reads the partial index from pg_index: valid and ready (an interrupted CONCURRENTLY build leaves the same definition text but an INVALID index), keyed on expires_at, predicate (revoked_at IS NULL). The plan must use one of the two indexes that can serve expires_at <, by name, and never a Seq Scan. - The moduledoc, the test name, the migration's comment (which said the composite index cannot be used; the planner reads it whole on a small table) and AC-32.1.2's wording now describe what is actually asserted. - session_end_test's permanent-refusal sibling anchors on its own tenant_id too, and pins the log format the :busy refutation depends on. - The plan regex no longer has a redundant alternative. Not taken: moving plan CHOICE into an async: false scale test. The async test now asserts eligibility deterministically; choice at scale is recorded in the migration. Mutations (bin/mutate.sh, all exit 0): pg_index read from the other index (validity and predicate fail); the allowed index names replaced; the :busy clause removed; the log's tenant_id= reworded (the pinned format fails).
…ery, drop the EXPLAIN - The EXPLAIN passed whenever either index existed (the composite can serve expires_at < too), so it proved nothing about the partial index. It is removed. The worker's sweep query is now RevokeExpiredDispatchesWorker.expired_query/1, and the test asserts its SQL carries revoked_at IS NULL and expires_at <, beside pg_index: valid, one key column expires_at, btree, on dispatches, predicate (revoked_at IS NULL). - The :busy test refutes its own story and dispatch ids rather than one log format. - The migration comment, the story's rationale and AC-32.1.2 no longer say the composite index cannot be used, and the AC names the ~20k-seeded-row capture, not production. Mutations (bin/mutate.sh, all exit 0): revoked_at IS NULL dropped from the worker's query; its < widened to <=; pg_index read from the other index; the :busy clause removed; the permanent log's tenant_id= reworded.
…ry in a sync test - The sweep query is private again. RevokeExpiredDispatchesPlanTest (async: false) seeds a production-shaped dispatches table (20k revoked history, 50 live), ANALYZEs it inside the sandbox transaction, captures the query perform/1 issues through repo telemetry and requires the default planner to use dispatches_expires_at_active_index. Sync modules run after every async one, so no neighbour's rows move the choice. - The async worker test keeps only the pg_index shape check; the SQL substring check, which not and or mutations defeated, is gone. - session_end tests read only error lines carrying a per-test request_id the test process sets. The busy test logs a canary in the same capture and requires it to be the only line, so an unreadable capture cannot pass it. - AC-32.1.2 is back to its EXPLAIN wording and names the test. The migration comment no longer carries a stale copy of the query. Mutations (bin/mutate.sh): worker predicate made non-implying (coalesce), exit 0; index name swapped, exit 0; live fraction raised to 10 percent, exit 0; busy clause logs an error, exit 0; request_id marker unset, exit 0; permanent refusal logged at warning, exit 0.
…tions to a sync module The rewrite's review showed the cause of its findings: asserting the planner's choice in the shared test database. ANALYZE writes pg_class reltuples in place, so the stats it set outlived the sandbox rollback and polluted later plans, the very flake class this PR removes. So the plan test is gone from this PR. The natural-plan guard for AC-32.1.2 goes into the scale job, which commits a seeded table on its own database, as its own change. - budget_escalation_refused/4's tests move to BudgetEscalationRefusedTest, async: false. Sync modules run alone after every async one, so a whole-capture log == "" is this call's capture again, with no marker, format dependence or spawned-process gap. - The worker test keeps the pg_index shape check. Its docs, the migration comment and the epic 35 README now say which index the planner chooses is not asserted in the suite. - AC-32.1.2 keeps master's wording; the story's rationale keeps its SEEK correction. Mutations (bin/mutate.sh, all exit 0): busy clause logs an error; permanent refusal logged at warning; the catalog read pointed at the composite index.
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.
Three different tests failed the commit gate once each on 2026-09-28, and each passed alone. This PR fixes the two whose cause is confirmed. The third is recorded here.
session_end_test(fixed):capture_logoutput was"". Butcapture_logcollects every process's logs.refusal_testdeliberately walks every advance error through the runner channel's logging catch-all. One of those:actor_lineage_requirederror lines landed inside this async test's capture window.tenant_id.revoke_expired_dispatches_worker_test(fixed):dispatches_expires_at_active_index.dispatches_tenant_id_expires_at_indexserves the sameexpires_at <condition, and the planner's choice between the two moves with the rows other tests leave behind.expires_atcondition, and noSeq Scan.pg_indexes.dispatchesand would wait on every async test's open transaction.signup_test"deletes the external secret when a later Multi step fails" (not fixed, cause unconfirmed):57014 canceling statement due to user requestinstead ofStaleEntryError.DELETE FROM tenants, which must check all 83 foreign keys that referencetenants. All 83 are indexed.log_lock_waitson, to name the holder. Then either make that test synchronous, or make this test fail its Multi without deleting the parent row.Evidence:
bin/mutate.sh)::busyclause removed so the call logs: exit 0, caught.SET LOCAL enable_seqscan = offsurvives (exit 1). The shared test tables now hold enough rows that the planner uses an index unprompted, so that pre-existing refute is not load-bearing today.Review round 1 found 9 issues. 8 are fixed and 1 is not taken.
Fixed:
pg_index: it must be valid and ready, keyed onexpires_at, with the predicate(revoked_at IS NULL). An interruptedCONCURRENTLYbuild leaves the same definition text on an invalid index.Seq Scan.session_end_test: the sibling test anchors on its owntenant_idand pins the log format that the:busyrefutation depends on.Not taken: moving the planner-choice check into an
async: falsescale test. This test now proves eligibility deterministically, and the migration's verification note records which index the planner picks at scale.Mutations (
mutate.sh, all exit 0):pg_indexread from the other index;:busyclause removed;tenant_id=reworded.Review round 2 found 9 issues, all fixed.
RevokeExpiredDispatchesWorker.expired_query/1. The test asserts that query's SQL carriesrevoked_at IS NULLandexpires_at <.pg_index, the index must be valid, have one key columnexpires_at, be a btree ondispatches, and have the predicate(revoked_at IS NULL).:busytest: it refutes its own story and dispatch ids instead of depending on one log format.Mutations (
mutate.sh, all exit 0):revoked_at IS NULLdropped from the worker's query<widened to<=pg_indexread from the other index:busyclause removedtenant_id=rewordedReview round 3 found 7 issues and no production bug. All 7 are fixed by rewriting the two tests, not by patching them.
RevokeExpiredDispatchesPlanTest(async: false, so it runs after every async module) seeds a production-shaped table (20k revoked history, 50 live) and ANALYZEs it inside its sandbox transaction. It captures the queryperform/1actually issues and requires the default planner to usedispatches_expires_at_active_index.expired_query/1is gone, and so is the SQL substring check thatnot/ormutations defeated.session_end: each test reads only the error lines carrying a per-testrequest_idits own process sets, with no dependence on the level token. The busy test logs a canary in the same capture and requires it to be the only line.Mutations (
mutate.sh, all exit 0):coalesce)request_idmarker unsetGate: 12003 tests, 0 failures.
The review of that rewrite found 10 issues, and they share one cause: it asserted the planner's choice inside the shared test database.
ANALYZEwritespg_class.reltuplesin place, so the statistics it set outlived the sandbox rollback and skewed later plans. That is the same flake class this PR removes. The rewrite is withdrawn:budget_escalation_refused/4's tests are nowBudgetEscalationRefusedTest(async: false). Sync modules run alone, after every async module, so the whole-capturelog == ""belongs to that one call again. This removes the marker helper and, with it, issues 3, 4 and 9.capture_logassertions acrosstest/. Converting them is a sweep over files this PR doesn't touch, and nothing is blocked on it. The sync-module pattern here is the template for doing it.Mutations (
mutate.sh, all exit 0):