Skip to content

perf: keep OpenRouter and serial KV reads off the status path - #44

Merged
leviFrosty merged 1 commit into
mainfrom
perf/notes-import-status-latency
Oct 15, 2026
Merged

leviFrosty merged 1 commit into
mainfrom
perf/notes-import-status-latency

Conversation

@leviFrosty

Copy link
Copy Markdown
Owner

GET /notes-import/status is about two thirds of prod traffic. Over the last 7 days in Sentry its response time was p50 302 ms, p90 1.16 s, and p99 1.74 s. There were two causes:

  • OpenRouter probe on the request path. The provider-health cache expired after 60 s at each edge, so about 29% of requests (5,620 probe spans out of 19,264) waited on OpenRouter's endpoints API. The gpt-6-luna probe has p95 973 ms, and its p99 hits the 2 s deadline.
  • KV reads in sequence. The kill-switch, then the provider cache, then sometimes the probe plus a KV write, and only then allowances and minimum version. At a quiet edge each read is a round trip to KV's storage.

Change

  • KV notes-import:provider-health now holds {healthy, checkedAt} for 5 minutes. A result under a minute old is served as is. An older one is still served while waitUntil re-probes and rewrites it. Only a missing result is probed inline, and that cache write also moves to waitUntil. The bare up/down values from before this change expire within 60 s, so they're treated as fresh.
  • The kill-switch, provider cache, allowances, and minimum version are now read in parallel. The kill-switch still decides first, and the existing fail-open rules are unchanged.
  • The status, kickoff/legacy gate, and transcribe routes pass the request's waitUntil. Without it (unit tests), a stale result is re-probed before answering, as before.
  • Trade-off: a provider outage now takes a minute plus one request to show, instead of a minute. AGENTS.md documents this.

Verification

  • pnpm test (741 passing) and tsc --noEmit. The new tests cover fresh, stale + background refresh, a failed refresh keeping the stale value, the no-waitUntil path, the deferred write, and all four reads starting before any of them answers. Against the old status.ts, 6 of them fail.
  • Verify loop with a real OpenRouter key: doctor ok and e2e 44/45 (1 skipped). Status route over HTTP:
    • nothing cached: probed inline, 130 ms, stored {healthy:true, checkedAt}
    • fresh: 11 ms, no probe
    • 2-minute-old down: no_provider served in 14 ms, then rewritten to healthy:true in the background; the next request was available
    • old-format up: served without a probe

The Cloudflare wall-time p99 (30 s) and p999 (300 s) are a separate effect: long-lived Buddies live sockets and Notes Import progress streams count their whole lifetime as wall time. This PR doesn't target those.

🤖 Generated with Claude Code

GET /notes-import/status waited on an OpenRouter probe whenever the
60-second provider cache had expired at that edge (about 29% of requests
last week, p99 hitting the 2 s deadline), and read KV in sequence: the
kill-switch, then the provider cache, then allowances.

The provider result is now stored with its check time for 5 minutes.
Under a minute old it is served as is; older, it is served while
waitUntil re-probes and rewrites it. Only a missing result is probed
inline, and its cache write also moves to waitUntil. All KV reads start
at once, and the stages still decide in order.
@leviFrosty
leviFrosty merged commit 80e7f2c into main Oct 15, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant