fix(usage): stop per-append 64MiB usage reparses - #1620
Conversation
Incident: local ocx RSS 9.5 GiB from repeated 64 MiB /api/usage parses on a 243 MB usage.jsonl. Docs-only WP0: one owner flight, compact artifacts, 60s generation freshness, GUI stampede stop, WS deferred to PR 1608, release via clone of origin URL.
Keep the 64 MiB window parse, but key the in-flight read and compact summary cache on ledger identity rather than size/mtime. Appends within 60s reuse the cached summaries; replacements that shrink the file still supersede. Dashboard and provider surfaces share one 30d resource and no longer poll /api/usage every minute.
A sequential /api/keys read after /api/usage no longer starts a second 64 MiB parse. The usage owner installs the compact attribution map from the same entries it just summarized.
Move the 30-day usage cache key into a helper so dashboard, providers, and the workspace subscribe to the same store. Defer workspace usage state updates so the GUI lint gate stays green.
|
✅ Deterministic PR hygiene checks passed. |
⏳ DRAFT
What to do
This pull request was already a draft. Its draft status will be preserved after every issue above is resolved. |
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (20)
📝 WalkthroughWalkthroughThe change adds usage-retention plans and implements stable usage-log read coalescing, metadata-aware server caches, snapshot-based API-key rollups, shared GUI resource keys, reduced polling, and updated cache and dashboard contract tests. ChangesUsage retention
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant UsageRoute
participant UsageLogReader
participant UsageSummaryCache
participant ApiKeyUsageCache
UsageRoute->>UsageSummaryCache: Validate cache metadata
UsageRoute->>UsageLogReader: Read one usage snapshot
UsageLogReader-->>UsageRoute: Return entries and revision metadata
UsageRoute->>UsageSummaryCache: Populate summaries for ranges and surfaces
UsageRoute->>ApiKeyUsageCache: Cache API-key usage from the snapshot
UsageRoute-->>UsageRoute: Return the usage response
Possibly related PRs
Suggested reviewers: ✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 678974a445
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| for (const nextRange of ranges) { | ||
| for (const nextSurface of surfaces) { | ||
| const nextSummary = nextRange === range && nextSurface === surface ? summary : { | ||
| ...summarizeUsage(snapshot.entries, nextRange, now, nextSurface), |
There was a problem hiding this comment.
Aggregate all usage buckets in one pass
On a cold /api/usage request with a large ledger, handleLogsUsageRoutes computes the requested summary and then invokes summarizeUsage for the other eleven buckets; each invocation filters the entire snapshot and performs several additional passes for totals, models, providers, accounts, and costs. With the supported 500,000-entry/64 MiB window this substantially increases transient allocation and CPU, and concurrent requests that joined the same read each repeat this whole loop because the cache is not rechecked after the await. Build and install the twelve summaries through one shared single-pass generation, or avoid eagerly computing unrequested buckets.
Useful? React with 👍 / 👎.
| if (cached | ||
| && cached.revisionKey === observedRevisionKey | ||
| && cached.identityKey === identityKey | ||
| && cached.maxReadBytes === effectiveReadLimit | ||
| && cached.overlayVersion === userCostOverlayVersion() | ||
| && now < cached.expiresAt) { | ||
| && now < cached.freshUntil | ||
| && observedSize >= cached.lastSeenSize) { | ||
| return jsonResponse(refreshedUsageSummary(cached.summary, range, now)); |
There was a problem hiding this comment.
Detect non-shrinking in-place ledger rewrites
When usage.jsonl is rewritten in place within the 60-second freshness window to different content whose size is equal to or greater than lastSeenSize, the inode/birthtime identity remains unchanged and this condition serves the old summary. This is a regression from the prior revision key, which included mtime/ctime, and it affects the explicitly supported hand-edited ledger case; distinguish verified appends from rewrites rather than treating every nondecreasing size as an append, and add an equal/larger in-place rewrite regression test.
AGENTS.md reference: src/AGENTS.md:L24-L26
Useful? React with 👍 / 👎.
Summary\n\n- Stop the local proxy memory blow-up caused by repeated 64 MiB parses of a growing .\n- Cache compact usage summaries for 60s by ledger identity (path/dev/ino/birthtime), not size/mtime, so appends no longer invalidate the cache.\n- Share one in-flight 64 MiB window parse across dashboard tabs and prime the API-key rollup from that same snapshot.\n- Remove the 60s dashboard/Codex usage polls and subscribe provider surfaces to one shared 30-day resource.\n\n## Verification\n\n- bun test v1.3.14 (0d9b296a)
🚀 opencodex proxy running on http://localhost:55536
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55538
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55540
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55542
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55544
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55546
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55548
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55550
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55552
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55554
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55557
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55559
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55561
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55563
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55565
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55567
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55569
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55571
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55573
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55575
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55577
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55579
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55581
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55583
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55640
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55642
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55644
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55646
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55648
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard — 100 pass, 0 fail.\n- bun test v1.3.14 (0d9b296a) — 13 pass, 0 fail.\n- — pass.\n- — pass.\n- Independent leak review: Opus PASS; Sol FAIL on sequential API-key reread, folded in .\n\n## Checklist\n\n- [x] Scope stays focused and avoids unrelated cleanup.\n- [x] Docs or release notes were updated when needed.\n- [x] Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.\n
Summary by CodeRabbit