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
Ordered loader: recover failed ordered requests with a bounded repair instead of a full-source read #2165
When an ordered request fails, the ordered loader recovers by reading the full source. That happens after a failed page, tie, or bounded-repair request, including a synchronous throw or a failed boundary read, and the automatic retries at 250 ms and 500 ms do the same. Full-source reads can be very expensive. Each plan that can continue from a provider prefix (!requiresFullSource) has a cheaper option: a bounded ordered-prefix repair from the start of the order. That repair sets its boundary from its own settled requests, so partial writes from the failed request cannot mislead it.
Rule (maintainer decision): avoid full-source reads whenever a bounded read is available.
PR #2154 applies this rule to an ordering invalidation that overlaps an in-flight page or tie request. It leaves failure recovery unchanged. These paths still read the full source:
recovery after a failed or canceled request;
an ordering invalidation that arrives before any request has settled.
What a first attempt showed
A prototype replaced requireFullSourceRepair() with a bounded repair on failure. It also retried the bounded repair in the automatic retries, and let a newer ordering invalidation retry once. Across five test files, 48 pinned expectations then failed. Some of them were real regressions:
pagination-oracle.property.test.ts, "recovers a failed tie boundary …": the window was [1, 4], expected [1, 2].
ordered-source-loader-oracle.test.ts, "keeps the 'boundary' acquisition lifecycle exact for reject/abort": after the tie request failed, the original page acquisition was not released.
pagination-oracle.property.test.ts, the pending-history campaigns and their captured-history controls: logical settlement did not match the model.
The failure path re-enters the bounded chain in states it was not built for, for example a failed request that overlaps a repair (the abandonOrderedPrefixRepair path).
Work
In the ordered-source-loader, loader-state and pagination oracles, state the bounded failure-recovery law first:
recovery shapes;
the lease release of failed and superseded acquisitions;
the retry budget;
retry after a newer invalidation.
Then change OrderedSourceLoader: fail, failRequest, finishOrderedPrefixRepair, abandonOrderedPrefixRepair, scheduleRepairRetry, and the retry gate in loadMore.
Update "Ordered requests, continuation, and recovery" in packages/db/src/query/live/ARCHITECTURE.md.
Related: #2136, #2154. The coverage map backlog entry is "Bounded recovery after a failed ordered request".
Additional receiving-test evidence on main at 8feab6ea3eaae4456f738da39fa93508ddd5cc01, using the real Query Collection + createCursorPager + live-query setWindow path.
Measured backend work
The source uses syncMode: 'on-demand', autoIndex: 'eager', and BasicIndex. Rows have unique ascending numeric ranks; backend pages contain 10 rows. TanStack Query retries are disabled. The failed page throws before returning or writing any rows.
Each case first loads 10 rows, then requests a 20-row window. Counts below exclude the initial load and, in failure cases, the failed attempt itself. Counts include boundary-equality probes and, for the reload case, reload work.
Backend size
Path to the 20-row window
Backend calls
Rows returned by backend
Final source size
100
No failure
2
11
20
100
Fail expansion, then retry setWindow directly
10
100
100
100
Fail expansion, await live.reload(), then expand again
3
21
20
1,000
No failure
2
11
20
1,000
Fail expansion, then retry setWindow directly
100
1,000
1,000
1,000
Fail expansion, await live.reload(), then expand again
3
21
20
All six cases return the correct first 20 rows. After each failed expansion, both the source and visible result still contain only the original 10 rows. Direct retry emits a main-sequence subset request with no limit and the cursor pager exhausts the backend. The successful and reload-then-expand controls do not emit an unlimited main-sequence request. Equality probes are counted separately from that check.
Reproduction shape
awaitlive.preload()// orderBy(rank).limit(10)failNextPage=trueawaitexpect(live.utils.setWindow({offset: 0,limit: 20}),).rejects.toBe(failure)expect(source.size).toBe(10)expect(live.toArray).toHaveLength(10)// Direct retry: 100 backend calls / 1,000 rows for a 20-row result.awaitlive.utils.setWindow({offset: 0,limit: 20})// In a separate fresh fixture, the alternative is bounded:// await live.reload() // restores the last settled 10-row window// await live.utils.setWindow({ offset: 0, limit: 20 })// => 3 calls / 21 returned rows, source.size === 20
The fixture passes loadSubsetOptions.offset/limit to pager.read(window, context.signal); it serves rank-equality boundary probes exactly. The backend continuation token encodes the next offset. This is an in-memory transport measurement, not a network-latency or database-scan benchmark.
Interpretation and limits
This confirms a practical amplification of the documented full-source recovery contract: a single failed continuation can make subsequent direct retry scale with total dataset size rather than the requested window, even when that particular failure wrote nothing.
It does not establish that bounded repair is safe for generic providers. Partial writes, tied ranks, overlapping invalidations, lease ownership, and automatic repair retries still need the oracle-first work described above. These are fixed receiving examples, not a new recovery law or a production fix.
The reload control is important: awaited reload followed by expansion stays bounded in this fixture. We encountered this while investigating a real app's reload-based pagination retry, but this test does not reproduce that app's timing-sensitive failure or establish that it has the same cause.
Validation: six focused runtime tests pass, file ESLint and pnpm lint:changed pass. No production source changes.
Activity
KyleAMathews commentedon Nov 4, 2026
Additional receiving-test evidence on main at
8feab6ea3eaae4456f738da39fa93508ddd5cc01, using the real Query Collection +createCursorPager+ live-querysetWindowpath.Measured backend work
The source uses
syncMode: 'on-demand',autoIndex: 'eager', andBasicIndex. Rows have unique ascending numeric ranks; backend pages contain 10 rows. TanStack Query retries are disabled. The failed page throws before returning or writing any rows.Each case first loads 10 rows, then requests a 20-row window. Counts below exclude the initial load and, in failure cases, the failed attempt itself. Counts include boundary-equality probes and, for the reload case, reload work.
setWindowdirectlylive.reload(), then expand againsetWindowdirectlylive.reload(), then expand againAll six cases return the correct first 20 rows. After each failed expansion, both the source and visible result still contain only the original 10 rows. Direct retry emits a main-sequence subset request with no limit and the cursor pager exhausts the backend. The successful and reload-then-expand controls do not emit an unlimited main-sequence request. Equality probes are counted separately from that check.
Reproduction shape
The fixture passes
loadSubsetOptions.offset/limittopager.read(window, context.signal); it serves rank-equality boundary probes exactly. The backend continuation token encodes the next offset. This is an in-memory transport measurement, not a network-latency or database-scan benchmark.Interpretation and limits
This confirms a practical amplification of the documented full-source recovery contract: a single failed continuation can make subsequent direct retry scale with total dataset size rather than the requested window, even when that particular failure wrote nothing.
It does not establish that bounded repair is safe for generic providers. Partial writes, tied ranks, overlapping invalidations, lease ownership, and automatic repair retries still need the oracle-first work described above. These are fixed receiving examples, not a new recovery law or a production fix.
The reload control is important: awaited reload followed by expansion stays bounded in this fixture. We encountered this while investigating a real app's reload-based pagination retry, but this test does not reproduce that app's timing-sensitive failure or establish that it has the same cause.
Validation: six focused runtime tests pass, file ESLint and
pnpm lint:changedpass. No production source changes.