Skip to content

[Bug] routing-history index rebuild runs synchronously on the serving event loop (487 s proxy-wide freeze) #6990

Closed

Description

@vibewriter-hand

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

  1. Let usage.jsonl grow to about 2 GB (no usageLedgerMaxBytes).
  2. Trigger a schema/identity mismatch or delete routing-history.sqlite, then send any request (routing calls openRequestHistoryIndexSync() from src/routing/health.ts:236).
  3. 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 s

Source-level analysis (v2.83.0)

  1. refreshLockedSync() (line 432) is synchronous. ensureSchemaAndIdentity() may call destroyAndRecreate(), then
    fullRebuild() (line 412) reads the whole ledger and inserts every row in the same call stack. No await, no time slice.
  2. The sync entry openRequestHistoryIndexSync() is called at routing time from src/routing/health.ts:236.
    The async entry openRequestHistoryIndex() wraps the same sync function in Promise.resolve(...), so it blocks too.
  3. isHealthy() runs PRAGMA quick_check on every refresh (line 394). On a 4.8 GB file it takes 2.3 s per call, and 12 s on a cold cache.
  4. 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 setImmediate for 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.ts already 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_rows and indexed_offset in the same transaction for each batch, so an aborted ingest resumes exactly.
  • Run no quick_check on 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

  1. added
    bugSomething isn't working
    proxyHTTP proxy, routing, reverse-proxy / management auth
    on Oct 12, 2026
  2. Ingwannu commented on Oct 12, 2026

    @Ingwannu
    Owner

    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.

  3. lidge-jun commented on Oct 15, 2026

    @lidge-jun
    Owner

    Fixed by #7070 (request-history index rebuild moved to a worker thread).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingproxyHTTP proxy, routing, reverse-proxy / management auth

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions