fix(e2e): harden ObsessionDB live e2e against replica lag (supersedes #220) - #221
Merged
Merged
Conversation
…iffs Co-authored-by: Marc Höffl <marc.hoeffl@gmail.com>
…ites - ensure() waits for the journal table after CREATE - run_started/run_finished get the same bounded retry as stream facts - terminal facts are bounded by the live run's deadline, with the fixed 5s grace only after interruption; a slow run_finished insert no longer turns a successful run into a TimeoutError Co-authored-by: Marc Höffl <marc.hoeffl@gmail.com>
…ooks Co-authored-by: Marc Höffl <marc.hoeffl@gmail.com>
…nsistently Co-authored-by: Marc Höffl <marc.hoeffl@gmail.com>
…rget check Co-authored-by: Marc Höffl <marc.hoeffl@gmail.com>
Co-authored-by: Marc Höffl <marc.hoeffl@gmail.com>
vesperships
marked this pull request as ready for review
September 28, 2026 08:41
This was referenced Sep 28, 2026
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.
Summary
The
obsessiondbjob on main has failed on four consecutive SHAs (256ec62,3cc768d,fedbf56,7fe44e5). This PR replaces #220. It keeps #220's sound parts, fixes a second ingest failure mode that #220 misses, and closes gaps that would let the dictionary and mv_replay tests keep failing. It targetsmainand touches the same lines as #220 and #218, so it is meant to land instead of them, not stacked on them.Root pattern: the tests that fail are the ones where a request can reach a different replica than the previous one. That happens with the stateless executor, the dictionary's HTTP source, and fire-and-forget chunk inserts. A single
waitForTable/waitForRowssuccess only shows that one replica caught up.Review of #220
waitForTableafter journalCREATEinensure()UNKNOWN_TABLEin run 36370187217. But the ingest failures onfedbf56and3cc768dwere aDOMException TimeoutError, notUNKNOWN_TABLE. That error comes fromrun_finishedbeing hard-capped atAbortSignal.timeout(5000)even on a healthy, uncancelled run. So #220 would still leave ingest red on the run that is red most often. Also,run_startedhad no retry at all, so one request to a lagging replica fails the whole run even afterensure()saw the table on another replica.SYSTEM RELOAD DICTIONARY, then polldictGet""for its 300s lifetime. PollingdictGetfor 10s can't recover from that.rows.length === BUCKETSselect_sequential_consistency = 1. Buckets 0 and 1 missing is at least as consistent with chunkINSERT…SELECTs reading the source on a lagging replica and writing 0 rows. In that case the data is permanently missing, and the poll just times out. It fails correctly, but the flake stays. Waiting on length, not values, also loses the diff.What this PR changes
packages/clickhouse/src/e2e-testkit.ts: newpollUntil(read, predicate). It repeats the whole observation until the predicate holds, then returns the last value so the caller'sexpectstill shows a real diff. It retries reads that throw, such as a transientUNKNOWN_TABLE. It is re-exported from the CLI testkit.packages/plugin-ingest/src/journal.ts: keeps fix(e2e): stabilize ObsessionDB live e2e flakes on main #220'swaitForTableafterCREATE.packages/plugin-ingest/src/executor.ts(product change; changeset included):run_started/run_finishednow use the same bounded retry as stream facts (4 attempts, 250ms base). Run facts are deterministic and appends useinsert_deduplication_token, so retrying is safe.work_finished,run_finished) are bounded by the live run's deadline. The fixed 5s grace now applies only after cancellation or budget exhaustion; once the run is interrupted, behavior is unchanged. This is what fixes theTimeoutError.main; one reproduces the exact CITimeoutError: The operation timed out.packages/plugin-ingest/src/ingest.e2e.test.ts: the journal is created throughensure()inbeforeAll, like the raw and destination tables, so its propagation overlaps setup instead of racing the first run. Hooks get 60s timeouts; bun's default is 5s.packages/cli/src/test/migrate-dictionary.e2e.test.ts:select_sequential_consistency=1, so whichever replica serves a load catches up with the seed INSERT before reading.SYSTEM RELOAD DICTIONARY+dictGet, both after seeding and afterCREATE OR REPLACE.packages/plugin-backfill/src/mv-replay-plan.e2e.test.ts:select_sequential_consistency = 1, so no chunk replays an empty source partition.writtenRows, so a future failure shows whether data never landed (0) or was only slow to appear.packages/cli/src/test/text-index.e2e.test.ts: the escape checks are batched into oneUNION ALLquery (the same approach as test(cli): batch text-index escape checks in one query #218), and so are the quoted-literal checks. Both tests get the 60s timeout the other live tests use.plugins/ingest.mdnow describes the terminal-append limit accurately.Confidence
TimeoutError(reproduced in a unit test, gone after the fix), dictionary (the stale-cache path is removed).UNKNOWN_TABLEand mv_replay. These are layered defences against replica lag; nothing can prove a replica is up to date from the client side. If mv_replay still fails, the message now shows which of the two causes it is.How tested
bunx turbo run typecheck lint --filter=@chkit/clickhouse --filter=@chkit/plugin-ingest --filter=@chkit/plugin-backfill --filter=chkit: 14/14 tasks pass.bunx turbo run testfor the same packages against a local single-node ClickHouse 26.10 with the CI config and seeded fixtures: clickhouse 47, plugin-backfill 131, chkit 310, plugin-ingest 61 pass, 0 fail. The batched escape test now takes about 140ms.rows written per chunk: {…: 1, …}.obsessiondbjob is the real check.Needs a human
ensure,run_started, andbatch_committedare already bounded, and the post-interruption grace is unchanged.readCheckpointis still not retried. A replica that lags after bothensure()andrun_startedwould still fail that stream. I left it out to keep the product change small.--concurrentdefault. Only text-index hit it in the logs.