Skip to content

perf: HTTP latency optimizations — diagnostics, cache warming, concurrency limits#490

Merged
BYK merged 1 commit intomainfrom
perf/http-latency-optimizations
Mar 20, 2026
Merged

perf: HTTP latency optimizations — diagnostics, cache warming, concurrency limits#490
BYK merged 1 commit intomainfrom
perf/http-latency-optimizations

Conversation

@BYK
Copy link
Copy Markdown
Member

@BYK BYK commented Mar 20, 2026

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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 20, 2026

Semver Impact of This PR

🟢 Patch (bug fixes)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

  • (telemetry) Track TTY vs non-TTY invocations via metric by betegon in #482
  • Dynamic cache-backed shell completions with fuzzy matching by BYK in #465

Bug Fixes 🐛

  • (project) Fallback to org listing when bare slug matches an organization by betegon in #475
  • Add actionable suggestions for 400 Bad Request on issue list (CLI-BM, CLI-7B) by BYK in #489
  • Detect issue short IDs passed to issue list (CLI-C3) by BYK in #488
  • Add Glob.match() polyfill + improve auto-detect diagnostics (CLI-7T) by BYK in #487
  • Add org-slug pre-check to dispatchOrgScopedList (CLI-9A) by BYK in #485

Internal Changes 🔧

  • (issue) Skip getProject round-trip in project-search resolution by betegon in #473
  • (resolve) Carry project data through resolution to eliminate redundant getProject calls by BYK in #486
  • HTTP latency optimizations — diagnostics, cache warming, concurrency limits by BYK in #490
  • Switch from @sentry/bun to @sentry/node-core/light (~170ms startup savings) by BYK in #474
  • Regenerate skill files by github-actions[bot] in b7b240ec

🤖 This preview updates automatically when you update the PR.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 20, 2026

Codecov Results 📊

126 passed | Total: 126 | Pass Rate: 100% | Execution Time: 0ms

📊 Comparison with Base Branch

Metric Change
Total Tests
Passed Tests
Failed Tests
Skipped Tests

✨ No test changes detected

All tests are passing successfully.

✅ Patch coverage is 95.65%. Project has 1042 uncovered lines.
❌ Project coverage is 95.77%. Comparing base (base) to head (head).

Files with missing lines (5)
File Patch % Lines
sentry-client.ts 91.80% ⚠️ 20 Missing
projects.ts 92.57% ⚠️ 15 Missing
login.ts 98.52% ⚠️ 2 Missing
events.ts 98.70% ⚠️ 1 Missing
infrastructure.ts 99.50% ⚠️ 1 Missing
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

@BYK BYK marked this pull request as ready for review March 20, 2026 00:26
Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

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
@BYK BYK force-pushed the perf/http-latency-optimizations branch from addb36a to 13d1606 Compare March 20, 2026 00:37
@BYK
Copy link
Copy Markdown
Member Author

BYK commented Mar 20, 2026

Addressed both review comments: extracted the duplicated concurrency constant into a single ORG_FANOUT_CONCURRENCY = 5 export in infrastructure.ts (where API_MAX_PER_PAGE and MAX_PAGINATION_PAGES already live). All three consumers (projects.ts, events.ts, issue/utils.ts) now import from the shared source.

@BYK BYK merged commit 8983ddb into main Mar 20, 2026
22 checks passed
@BYK BYK deleted the perf/http-latency-optimizations branch March 20, 2026 00:45
BYK added a commit that referenced this pull request Mar 20, 2026
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.
BYK added a commit that referenced this pull request Mar 20, 2026
## 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>
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