Repository navigation
Description
Client or integration
Codex App
Area
Proxy and routing
Summary
The routing-history index rebuild runs synchronously on the serving event loop. On a large ledger one rebuild froze the whole proxy for 487 s: no request on any provider completed and Codex clients showed "Connection failed: error sending request". I expected the rebuild to run in the background without blocking requests.
One call into the request-history index froze the proxy for 487 s. No request completed on any provider.
Codex clients then showed "Connection failed: error sending request". The ledger usage.jsonl was 2.34 GB (1.12 M rows).
The index file routing-history.sqlite was rebuilt to 4.93 GB.
Reproduction
- Let
usage.jsonlgrow to about 2 GB (nousageLedgerMaxBytes). - Trigger a schema/identity mismatch or delete
routing-history.sqlite, then send any request (routing callsopenRequestHistoryIndexSync()fromsrc/routing/health.ts:236). - Observe: the event loop is blocked for the whole rebuild (scratch measurements below).
Version
2.83.0 (same code in 2.81.0)
Operating system
macOS (Bun 1.4.2)
Provider and model
All providers (proxy-wide)
Logs or error output
rebuilt after identity/schema mismatch
routing-history.sqlite rebuilt to 4.93 GB from usage.jsonl 2.34 GB (1,119,276 rows); proxy unresponsive 487 sSource-level analysis (v2.83.0)
refreshLockedSync()(line 432) is synchronous.ensureSchemaAndIdentity()may calldestroyAndRecreate(), then
fullRebuild()(line 412) reads the whole ledger and inserts every row in the same call stack. Noawait, no time slice.- The sync entry
openRequestHistoryIndexSync()is called at routing time fromsrc/routing/health.ts:236.
The async entryopenRequestHistoryIndex()wraps the same sync function inPromise.resolve(...), so it blocks too. isHealthy()runsPRAGMA quick_checkon every refresh (line 394). On a 4.8 GB file it takes 2.3 s per call, and 12 s on a cold cache.- Line 438 overwrites the specific reason that
destroyAndRecreate()stored (schema version, integrity, identity, unreadable)
with the text "rebuilt after identity/schema mismatch". The operator cannot tell which check fired.
| Ledger | Rows | Cold rebuild, event loop blocked | quick_check per warm call |
|---|---|---|---|
| 490 MB | 228 k | 17 s | 0.54 s |
| 1.1 GB | 511 k | 45 s | 1.0 s |
| 2.4 GB | 1.09 M | 92 s | 2.3 s |
Rebuild time grows faster than the row count (random primary-key inserts). The live freeze took 487 s on a loaded machine.
Proposed change
- Make the ingest a generator that yields after each read chunk. Drive it with a time slice (25 ms) and
setImmediatefor async callers. - Give the sync entry a small budget (50 ms). When work remains, hand the generator to a background flight and throw a typed "busy" error.
health.tsalready treats a throw as "evidence unknown". - Build a full rebuild in a private sidecar file (
routing-history.sqlite.building.<pid>) and publish it with one atomic rename.
No reader, including another process, sees a partial index, and no table is dropped under a reader. - Commit rows,
indexed_rowsandindexed_offsetin the same transaction for each batch, so an aborted ingest resumes exactly. - Run no
quick_checkon the refresh path. Flag the index when any statement returns a corruption error and rebuild on the next refresh. - Keep the specific rebuild reason.
- Keep one refresh in flight across
closeRequestHistoryIndex().
Result on the 2.4 GB ledger: the sync call returns in 0.1 s, the largest event-loop stall is 0.2 s, the build takes 98 s in the background,
and a warm call costs 0 ms. A reference patch and 25 bun tests are available on request.
Also consider a default for usageLedgerMaxBytes. With no limit the ledger and the index grow without bound, and every rebuild is proportional to that size.
Checks
- I searched existing issues and documentation.
- I removed secrets, tokens, account details, request credentials, and personal data.
Activity
Ingwannu commentedon Oct 12, 2026
This remains unresolved. Current PR #6999 still points to8f1c6624; its Windows responsiveness CI failed, and the owner explicitly left it out of the release bundle. My current-head review records the independently inspected1106.8454ms proxied-request delay and545ms loop lag, against1000ms/300ms bounds.
The additional repair/attribution work is preserved separately on follow-up e831e863. It is not the PR head and is not a landed fix. The remaining synchronous SQLite open/first-commit work needs the owner-selected off-loop/publication treatment and exact-head acceptance; no local stress test, runtime change, weaker test bound, merge or release was performed in this pass.
fix(history): rebuild large request-history indexes on a worker thread (
lidge-jun commentedon Oct 15, 2026
Fixed by #7070 (request-history index rebuild moved to a worker thread).