perf: HTTP latency optimizations — diagnostics, cache warming, concurrency limits#490
perf: HTTP latency optimizations — diagnostics, cache warming, concurrency limits#490
Conversation
Semver Impact of This PR🟢 Patch (bug fixes) 📋 Changelog PreviewThis is how your changes will appear in the changelog. New Features ✨
Bug Fixes 🐛
Internal Changes 🔧
🤖 This preview updates automatically when you update the PR. |
Codecov Results 📊✅ 126 passed | Total: 126 | Pass Rate: 100% | Execution Time: 0ms 📊 Comparison with Base Branch
✨ No test changes detected All tests are passing successfully. ✅ Patch coverage is 95.65%. Project has 1042 uncovered lines. Files with missing lines (5)
Coverage diff@@ Coverage Diff @@
## main #PR +/-##
==========================================
- Coverage 95.78% 95.77% -0.01%
==========================================
Files 180 180 —
Lines 24566 24609 +43
Branches 0 0 —
==========================================
+ Hits 23529 23567 +38
- Misses 1037 1042 +5
- Partials 0 0 —Generated by Codecov Action |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
…rency limits ## Summary Four targeted optimizations based on investigation showing Bun already uses HTTP/2 multiplexing and keep-alive (transport is not the bottleneck), but sequential resolution chains and unbounded fan-outs cause observable slowness. ## Changes ### 1. HTTP timing diagnostics (`sentry-client.ts`) Debug-level logging in the authenticated fetch showing method, URL path, status, timing (ms), cache hits, and retry attempts. Visible with `SENTRY_LOG_LEVEL=debug` or `--verbose`. ### 2. Post-login cache warming (`auth/login.ts`) After successful login, fire-and-forget `listOrganizationsUncached()` to pre-populate the org + region SQLite cache, eliminating the ~800ms cold-start on the first command. ### 3. Concurrency limits on org fan-outs Add `p-limit(5)` to all unbounded `Promise.all()` patterns that fan out across organizations or regions: - `findProjectsBySlug()`, `findProjectsByPattern()`, `findProjectByDsnKey()` in `api/projects.ts` - `findEventAcrossOrgs()` in `api/events.ts` - `resolveProjectSearch()` in `commands/issue/utils.ts` ### 4. Faster org resolution for normal slugs (`region.ts`) `resolveEffectiveOrg()` now uses `resolveOrgRegion()` (1 API call) for normal slugs instead of `listOrganizationsUncached()` (1+N requests). The expensive fan-out is reserved for DSN numeric IDs (`oNNNNN`) that need ID→slug mapping. ## Test plan - `bun run typecheck` — clean - `bun run lint` — clean - 293 tests across affected modules pass
addb36a to
13d1606
Compare
|
Addressed both review comments: extracted the duplicated concurrency constant into a single |
Since PR #446 (listOrganizations SQLite caching) and PR #490 (post-login cache warming), listOrganizations() returns from cache instantly on warm starts. The separate listOrgsPrefetched() wrapper no longer adds value. Keep only the resolveOrg prefetch (DSN scanning, 2-5s on cold starts), which is the actual latency win of this PR.
## Summary - Starts DSN scanning (`resolveOrg`) **concurrently** with the `sentry init` preamble phase (experimental confirm + git status check), so the result is ready when the wizard later needs the org for project creation. - On a cold start (no DSN cache), this eliminates **2-5 seconds** of blocking latency that previously occurred deep inside the wizard's suspend/resume loop at `createSentryProject` → `resolveOrgSlug`. - When the org is already explicit (e.g., `sentry init acme/my-app`), background detection is skipped entirely. - `listOrganizations()` is **not** prefetched because it already has its own SQLite cache layer (PR #446) populated after `sentry login` (PR #490), so it returns instantly on warm starts. ## How it works ``` Before: init → preamble (user confirms) → wizard starts → ... → create-sentry-project → resolveOrgSlug → resolveOrg (DSN scan, 2-5s) → listOrganizations (300ms) After: init → [bg: resolveOrg (DSN scan)] → preamble (user confirms) → wizard starts → ... → create-sentry-project → resolveOrgSlug → await bg result (already settled, ~0ms) → listOrganizations → SQLite cache hit (~0ms) ``` ## Changes | File | Change | |------|--------| | `src/lib/init/prefetch.ts` | New warm/consume module for background DSN scanning | | `src/commands/init.ts` | Fire `warmOrgDetection()` before `runWizard()` when org is unknown | | `src/lib/init/local-ops.ts` | `resolveOrgSlug()` uses prefetch-aware `resolveOrgPrefetched()` for DSN scanning; uses `listOrganizations()` directly (SQLite-cached) | | `test/commands/init.test.ts` | 7 new tests verifying background detection is passed/omitted correctly | ## Risk Low. Background promise is `.catch()`-wrapped — if it fails, the existing synchronous path runs as a fallback. No behavioral change for the user; only latency improvement. --------- Co-authored-by: Burak Yigit Kaya <byk@sentry.io>

Summary
Four targeted optimizations based on investigation showing Bun already uses HTTP/2 multiplexing and keep-alive (transport is not the bottleneck), but sequential resolution chains and unbounded fan-outs cause observable slowness.
Changes
1. HTTP timing diagnostics (
sentry-client.ts)Debug-level logging in the authenticated fetch showing method, URL path, status, timing (ms), cache hits, and retry attempts. Visible with
SENTRY_LOG_LEVEL=debugor--verbose.2. Post-login cache warming (
auth/login.ts)After successful login, fire-and-forget
listOrganizationsUncached()to pre-populate the org + region SQLite cache, eliminating the ~800ms cold-start on the first command.3. Concurrency limits on org fan-outs
Add
p-limit(5)to all unboundedPromise.all()patterns that fan out across organizations or regions:findProjectsBySlug(),findProjectsByPattern(),findProjectByDsnKey()inapi/projects.tsfindEventAcrossOrgs()inapi/events.tsresolveProjectSearch()incommands/issue/utils.ts4. Faster org resolution for normal slugs (
region.ts)resolveEffectiveOrg()now usesresolveOrgRegion()(1 API call) for normal slugs instead oflistOrganizationsUncached()(1+N requests). The expensive fan-out is reserved for DSN numeric IDs (oNNNNN) that need ID→slug mapping.Test plan
bun run typecheck— cleanbun run lint— clean