fix(web): Search performance improvements - #615

Merged
brendan-kellam merged 5 commits into
mainfrom
bkellam/search_perf_improvements
Nov 13, 2025
Merged

fix(web): Search performance improvements#615
brendan-kellam merged 5 commits into
mainfrom
bkellam/search_perf_improvements

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Nov 13, 2025

Copy link
Copy Markdown
Contributor

After benchmarking our search api, I noticed that the zod parseAsync call we were doing in searchApi.ts was contributing to a significant portion of the total search time (average 64.6% in my benchmarks), especially for larger queries that return payloads above > 10MB. This issue colinhacks/zod#205 confirms others are hitting this with zod as well.

We don't really need to parse zoekt's response bodies (since we always expect them to be valid), so this PR removes the parseAsync call and instead does a simple cast. The results are pretty dramatic: my initial benchmarks are shoing a increase in search performance by a order of magnitude (89.8% reduction in average search time). Here's the before & after on the benchmark:

Before:
📈 Latency Distribution:
Min (p0): 407.04ms
p50: 2660.49ms
p75: 11770.50ms
p90: 50848.31ms
p95: 54193.05ms
p99: 74902.95ms
Max (p100):84267.76ms
After:
📈 Latency Distribution:
Min (p0): 293.31ms
p50: 569.53ms
p75: 1855.31ms
p90: 3682.20ms
p95: 4733.21ms
p99: 5561.01ms
Max (p100): 5805.22ms

With this change, I figured we can bump the default number of results requested to 100k (up from 5k!). In my testing performance was good and I was able to get 100k results in <5 seconds.

Summary by CodeRabbit

  • New Features

    • Added debug timing information to search responses for performance visibility.
  • Changed

    • Increased default search result count from 5,000 to 100,000.
  • Bug Fixes

    • Resolved a significant performance bottleneck in the search API, yielding substantial performance gains.

@coderabbitai

coderabbitaiBot commented Nov 13, 2025

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Search optimization increases default result limits from 5,000 to 100,000 entries. Schema-based validation in the client API is replaced with type assertions. Performance instrumentation and timing measurements are added to the search flow, with debug timings returned in responses.

Changes

Cohort / File(s)Summary
Search Configuration & Documentation
CHANGELOG.md, packages/web/src/app/[domain]/search/components/searchResultsPage.tsx
Increased default search result count from 5,000 to 100,000 after optimization pass; documented in changelog.
Schema & Type Definitions
packages/web/src/features/search/schemas.ts, packages/web/src/features/search/zoektSchema.ts
Added optional __debug_timings field to search response schema; introduced ZoektSearchResponse type alias.
Search API Implementation
packages/web/src/features/search/searchApi.ts
Instrumented search flow with performance measurements for fetch, parsing, and transformation steps; added timing breakdown in response; introduced ZoektSearchResponse handling and transformZoektSearchResponse function.
Client API
packages/web/src/app/api/(client)/client.ts
Removed schema-based validation; replaced with type assertions for search, fetchFileSource, getRepos, and getVersion functions; broadened error handling return types.

Sequence Diagram

sequenceDiagram
participant Client
participant SearchAPI as Search API
participant Zoekt
participant Response
Client->>SearchAPI: search({ query, matches, ... })
rect rgb(240, 248, 255)
Note over SearchAPI: measure: fetch
SearchAPI->>Zoekt: fetch search results
Zoekt-->>SearchAPI: raw response
end
rect rgb(240, 248, 255)
Note over SearchAPI: measure: parse_json
SearchAPI->>SearchAPI: JSON.parse(response)
end
rect rgb(240, 248, 255)
Note over SearchAPI: measure: transform
SearchAPI->>SearchAPI: transformZoektSearchResponse()
end
SearchAPI->>Response: SearchResponse + __debug_timings
Response-->>Client: { results, __debug_timings }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • packages/web/src/features/search/searchApi.ts: Significant refactoring with performance instrumentation, new response transformation pathway, and error handling updates—requires careful review of timing measurement logic and response shape changes.
  • packages/web/src/app/api/(client)/client.ts: Wholesale removal of schema-based validation across multiple functions in favor of type assertions—needs verification that this doesn't introduce runtime validation gaps.
  • packages/web/src/features/search/zoektSchema.ts: Appears to contain duplicate ZoektSearchResponse type definitions that should be consolidated.

Possibly related PRs

Suggested reviewers

  • msukkari

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title 'fix(web): Search performance improvements' directly relates to the main objective of this PR, which is to optimize search performance by removing expensive zod validation and increasing result counts.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

This comment has been minimized.

@brendan-kellam

Copy link
Copy Markdown
ContributorAuthor

@coderabbitai review

@coderabbitai

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/web/src/app/api/(client)/client.ts (1)

43-52: Restore ServiceError handling when fetching repos.
Line 51 currently casts the raw JSON to GetReposResponse, so a backend ServiceError payload comes back as if it were a repo list and downstream code will explode when it dereferences repo fields. Please propagate the error by updating the signature and checking isServiceError before casting.

Apply this diff:

-export const getRepos = async (): Promise<GetReposResponse> => {+export const getRepos = async (): Promise<GetReposResponse | ServiceError> => {
const result = await fetch("/api/repos", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
}).then(response => response.json());
- return result as GetReposResponse | ServiceError;+ if (isServiceError(result)) {+ return result;+ }++ return result as GetReposResponse;
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 06c84f0 and d4ae3ff.

📒 Files selected for processing (6)
  • CHANGELOG.md (1 hunks)
  • packages/web/src/app/[domain]/search/components/searchResultsPage.tsx (1 hunks)
  • packages/web/src/app/api/(client)/client.ts (4 hunks)
  • packages/web/src/features/search/schemas.ts (1 hunks)
  • packages/web/src/features/search/searchApi.ts (4 hunks)
  • packages/web/src/features/search/zoektSchema.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

Filenames should always be camelCase. Exception: if there are filenames in the same directory with a format other than camelCase, use that format to keep things consistent.

Files:

  • packages/web/src/features/search/schemas.ts
  • CHANGELOG.md
  • packages/web/src/app/api/(client)/client.ts
  • packages/web/src/app/[domain]/search/components/searchResultsPage.tsx
  • packages/web/src/features/search/zoektSchema.ts
  • packages/web/src/features/search/searchApi.ts
🪛 LanguageTool
CHANGELOG.md

[grammar] ~18-~18: Ensure spelling is correct
Context: ... in search api, resulting in a order of magnitutde performance improvement. [#615](https:/...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build

Comment threadCHANGELOG.md Outdated
@brendan-kellam
brendan-kellam merged commit a814bd6 into mainNov 13, 2025
9 checks passed
@brendan-kellam
brendan-kellam deleted the bkellam/search_perf_improvements branch November 13, 2025 07:20
@github-actionsgithub-actionsBot mentioned this pull request Nov 13, 2025
Sign up for freeto 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

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(web): Search performance improvements - #615

Merged
brendan-kellam merged 5 commits into
mainfrom
bkellam/search_perf_improvements
Nov 13, 2025
Merged

fix(web): Search performance improvements#615
brendan-kellam merged 5 commits into
mainfrom
bkellam/search_perf_improvements

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Nov 13, 2025

Copy link
Copy Markdown
Contributor

After benchmarking our search api, I noticed that the zod parseAsync call we were doing in searchApi.ts was contributing to a significant portion of the total search time (average 64.6% in my benchmarks), especially for larger queries that return payloads above > 10MB. This issue colinhacks/zod#205 confirms others are hitting this with zod as well.

We don't really need to parse zoekt's response bodies (since we always expect them to be valid), so this PR removes the parseAsync call and instead does a simple cast. The results are pretty dramatic: my initial benchmarks are shoing a increase in search performance by a order of magnitude (89.8% reduction in average search time). Here's the before & after on the benchmark:

Before:
📈 Latency Distribution:
Min (p0): 407.04ms
p50: 2660.49ms
p75: 11770.50ms
p90: 50848.31ms
p95: 54193.05ms
p99: 74902.95ms
Max (p100):84267.76ms
After:
📈 Latency Distribution:
Min (p0): 293.31ms
p50: 569.53ms
p75: 1855.31ms
p90: 3682.20ms
p95: 4733.21ms
p99: 5561.01ms
Max (p100): 5805.22ms

With this change, I figured we can bump the default number of results requested to 100k (up from 5k!). In my testing performance was good and I was able to get 100k results in <5 seconds.

Summary by CodeRabbit

  • New Features

    • Added debug timing information to search responses for performance visibility.
  • Changed

    • Increased default search result count from 5,000 to 100,000.
  • Bug Fixes

    • Resolved a significant performance bottleneck in the search API, yielding substantial performance gains.

@coderabbitai

coderabbitaiBot commented Nov 13, 2025

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Search optimization increases default result limits from 5,000 to 100,000 entries. Schema-based validation in the client API is replaced with type assertions. Performance instrumentation and timing measurements are added to the search flow, with debug timings returned in responses.

Changes

Cohort / File(s)Summary
Search Configuration & Documentation
CHANGELOG.md, packages/web/src/app/[domain]/search/components/searchResultsPage.tsx
Increased default search result count from 5,000 to 100,000 after optimization pass; documented in changelog.
Schema & Type Definitions
packages/web/src/features/search/schemas.ts, packages/web/src/features/search/zoektSchema.ts
Added optional __debug_timings field to search response schema; introduced ZoektSearchResponse type alias.
Search API Implementation
packages/web/src/features/search/searchApi.ts
Instrumented search flow with performance measurements for fetch, parsing, and transformation steps; added timing breakdown in response; introduced ZoektSearchResponse handling and transformZoektSearchResponse function.
Client API
packages/web/src/app/api/(client)/client.ts
Removed schema-based validation; replaced with type assertions for search, fetchFileSource, getRepos, and getVersion functions; broadened error handling return types.

Sequence Diagram

sequenceDiagram
participant Client
participant SearchAPI as Search API
participant Zoekt
participant Response
Client->>SearchAPI: search({ query, matches, ... })
rect rgb(240, 248, 255)
Note over SearchAPI: measure: fetch
SearchAPI->>Zoekt: fetch search results
Zoekt-->>SearchAPI: raw response
end
rect rgb(240, 248, 255)
Note over SearchAPI: measure: parse_json
SearchAPI->>SearchAPI: JSON.parse(response)
end
rect rgb(240, 248, 255)
Note over SearchAPI: measure: transform
SearchAPI->>SearchAPI: transformZoektSearchResponse()
end
SearchAPI->>Response: SearchResponse + __debug_timings
Response-->>Client: { results, __debug_timings }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • packages/web/src/features/search/searchApi.ts: Significant refactoring with performance instrumentation, new response transformation pathway, and error handling updates—requires careful review of timing measurement logic and response shape changes.
  • packages/web/src/app/api/(client)/client.ts: Wholesale removal of schema-based validation across multiple functions in favor of type assertions—needs verification that this doesn't introduce runtime validation gaps.
  • packages/web/src/features/search/zoektSchema.ts: Appears to contain duplicate ZoektSearchResponse type definitions that should be consolidated.

Possibly related PRs

Suggested reviewers

  • msukkari

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title 'fix(web): Search performance improvements' directly relates to the main objective of this PR, which is to optimize search performance by removing expensive zod validation and increasing result counts.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

This comment has been minimized.

@brendan-kellam

Copy link
Copy Markdown
ContributorAuthor

@coderabbitai review

@coderabbitai

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/web/src/app/api/(client)/client.ts (1)

43-52: Restore ServiceError handling when fetching repos.
Line 51 currently casts the raw JSON to GetReposResponse, so a backend ServiceError payload comes back as if it were a repo list and downstream code will explode when it dereferences repo fields. Please propagate the error by updating the signature and checking isServiceError before casting.

Apply this diff:

-export const getRepos = async (): Promise<GetReposResponse> => {+export const getRepos = async (): Promise<GetReposResponse | ServiceError> => {
const result = await fetch("/api/repos", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
}).then(response => response.json());
- return result as GetReposResponse | ServiceError;+ if (isServiceError(result)) {+ return result;+ }++ return result as GetReposResponse;
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 06c84f0 and d4ae3ff.

📒 Files selected for processing (6)
  • CHANGELOG.md (1 hunks)
  • packages/web/src/app/[domain]/search/components/searchResultsPage.tsx (1 hunks)
  • packages/web/src/app/api/(client)/client.ts (4 hunks)
  • packages/web/src/features/search/schemas.ts (1 hunks)
  • packages/web/src/features/search/searchApi.ts (4 hunks)
  • packages/web/src/features/search/zoektSchema.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

Filenames should always be camelCase. Exception: if there are filenames in the same directory with a format other than camelCase, use that format to keep things consistent.

Files:

  • packages/web/src/features/search/schemas.ts
  • CHANGELOG.md
  • packages/web/src/app/api/(client)/client.ts
  • packages/web/src/app/[domain]/search/components/searchResultsPage.tsx
  • packages/web/src/features/search/zoektSchema.ts
  • packages/web/src/features/search/searchApi.ts
🪛 LanguageTool
CHANGELOG.md

[grammar] ~18-~18: Ensure spelling is correct
Context: ... in search api, resulting in a order of magnitutde performance improvement. [#615](https:/...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build

Comment threadCHANGELOG.md Outdated
@brendan-kellam
brendan-kellam merged commit a814bd6 into mainNov 13, 2025
9 checks passed
@brendan-kellam
brendan-kellam deleted the bkellam/search_perf_improvements branch November 13, 2025 07:20
@github-actionsgithub-actionsBot mentioned this pull request Nov 13, 2025
Sign up for freeto 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

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(web): Search performance improvements - #615

Merged
brendan-kellam merged 5 commits into
mainfrom
bkellam/search_perf_improvements
Nov 13, 2025
Merged

fix(web): Search performance improvements#615
brendan-kellam merged 5 commits into
mainfrom
bkellam/search_perf_improvements

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Nov 13, 2025

Copy link
Copy Markdown
Contributor

After benchmarking our search api, I noticed that the zod parseAsync call we were doing in searchApi.ts was contributing to a significant portion of the total search time (average 64.6% in my benchmarks), especially for larger queries that return payloads above > 10MB. This issue colinhacks/zod#205 confirms others are hitting this with zod as well.

We don't really need to parse zoekt's response bodies (since we always expect them to be valid), so this PR removes the parseAsync call and instead does a simple cast. The results are pretty dramatic: my initial benchmarks are shoing a increase in search performance by a order of magnitude (89.8% reduction in average search time). Here's the before & after on the benchmark:

Before:
📈 Latency Distribution:
Min (p0): 407.04ms
p50: 2660.49ms
p75: 11770.50ms
p90: 50848.31ms
p95: 54193.05ms
p99: 74902.95ms
Max (p100):84267.76ms
After:
📈 Latency Distribution:
Min (p0): 293.31ms
p50: 569.53ms
p75: 1855.31ms
p90: 3682.20ms
p95: 4733.21ms
p99: 5561.01ms
Max (p100): 5805.22ms

With this change, I figured we can bump the default number of results requested to 100k (up from 5k!). In my testing performance was good and I was able to get 100k results in <5 seconds.

Summary by CodeRabbit

  • New Features

    • Added debug timing information to search responses for performance visibility.
  • Changed

    • Increased default search result count from 5,000 to 100,000.
  • Bug Fixes

    • Resolved a significant performance bottleneck in the search API, yielding substantial performance gains.

@coderabbitai

coderabbitaiBot commented Nov 13, 2025

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Search optimization increases default result limits from 5,000 to 100,000 entries. Schema-based validation in the client API is replaced with type assertions. Performance instrumentation and timing measurements are added to the search flow, with debug timings returned in responses.

Changes

Cohort / File(s)Summary
Search Configuration & Documentation
CHANGELOG.md, packages/web/src/app/[domain]/search/components/searchResultsPage.tsx
Increased default search result count from 5,000 to 100,000 after optimization pass; documented in changelog.
Schema & Type Definitions
packages/web/src/features/search/schemas.ts, packages/web/src/features/search/zoektSchema.ts
Added optional __debug_timings field to search response schema; introduced ZoektSearchResponse type alias.
Search API Implementation
packages/web/src/features/search/searchApi.ts
Instrumented search flow with performance measurements for fetch, parsing, and transformation steps; added timing breakdown in response; introduced ZoektSearchResponse handling and transformZoektSearchResponse function.
Client API
packages/web/src/app/api/(client)/client.ts
Removed schema-based validation; replaced with type assertions for search, fetchFileSource, getRepos, and getVersion functions; broadened error handling return types.

Sequence Diagram

sequenceDiagram
participant Client
participant SearchAPI as Search API
participant Zoekt
participant Response
Client->>SearchAPI: search({ query, matches, ... })
rect rgb(240, 248, 255)
Note over SearchAPI: measure: fetch
SearchAPI->>Zoekt: fetch search results
Zoekt-->>SearchAPI: raw response
end
rect rgb(240, 248, 255)
Note over SearchAPI: measure: parse_json
SearchAPI->>SearchAPI: JSON.parse(response)
end
rect rgb(240, 248, 255)
Note over SearchAPI: measure: transform
SearchAPI->>SearchAPI: transformZoektSearchResponse()
end
SearchAPI->>Response: SearchResponse + __debug_timings
Response-->>Client: { results, __debug_timings }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • packages/web/src/features/search/searchApi.ts: Significant refactoring with performance instrumentation, new response transformation pathway, and error handling updates—requires careful review of timing measurement logic and response shape changes.
  • packages/web/src/app/api/(client)/client.ts: Wholesale removal of schema-based validation across multiple functions in favor of type assertions—needs verification that this doesn't introduce runtime validation gaps.
  • packages/web/src/features/search/zoektSchema.ts: Appears to contain duplicate ZoektSearchResponse type definitions that should be consolidated.

Possibly related PRs

Suggested reviewers

  • msukkari

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title 'fix(web): Search performance improvements' directly relates to the main objective of this PR, which is to optimize search performance by removing expensive zod validation and increasing result counts.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

This comment has been minimized.

@brendan-kellam

Copy link
Copy Markdown
ContributorAuthor

@coderabbitai review

@coderabbitai

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/web/src/app/api/(client)/client.ts (1)

43-52: Restore ServiceError handling when fetching repos.
Line 51 currently casts the raw JSON to GetReposResponse, so a backend ServiceError payload comes back as if it were a repo list and downstream code will explode when it dereferences repo fields. Please propagate the error by updating the signature and checking isServiceError before casting.

Apply this diff:

-export const getRepos = async (): Promise<GetReposResponse> => {+export const getRepos = async (): Promise<GetReposResponse | ServiceError> => {
const result = await fetch("/api/repos", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
}).then(response => response.json());
- return result as GetReposResponse | ServiceError;+ if (isServiceError(result)) {+ return result;+ }++ return result as GetReposResponse;
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 06c84f0 and d4ae3ff.

📒 Files selected for processing (6)
  • CHANGELOG.md (1 hunks)
  • packages/web/src/app/[domain]/search/components/searchResultsPage.tsx (1 hunks)
  • packages/web/src/app/api/(client)/client.ts (4 hunks)
  • packages/web/src/features/search/schemas.ts (1 hunks)
  • packages/web/src/features/search/searchApi.ts (4 hunks)
  • packages/web/src/features/search/zoektSchema.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

Filenames should always be camelCase. Exception: if there are filenames in the same directory with a format other than camelCase, use that format to keep things consistent.

Files:

  • packages/web/src/features/search/schemas.ts
  • CHANGELOG.md
  • packages/web/src/app/api/(client)/client.ts
  • packages/web/src/app/[domain]/search/components/searchResultsPage.tsx
  • packages/web/src/features/search/zoektSchema.ts
  • packages/web/src/features/search/searchApi.ts
🪛 LanguageTool
CHANGELOG.md

[grammar] ~18-~18: Ensure spelling is correct
Context: ... in search api, resulting in a order of magnitutde performance improvement. [#615](https:/...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build

Comment threadCHANGELOG.md Outdated
@brendan-kellam
brendan-kellam merged commit a814bd6 into mainNov 13, 2025
9 checks passed
@brendan-kellam
brendan-kellam deleted the bkellam/search_perf_improvements branch November 13, 2025 07:20
@github-actionsgithub-actionsBot mentioned this pull request Nov 13, 2025
Sign up for freeto 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

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(web): Search performance improvements - #615

Merged
brendan-kellam merged 5 commits into
mainfrom
bkellam/search_perf_improvements
Nov 13, 2025
Merged

fix(web): Search performance improvements#615
brendan-kellam merged 5 commits into
mainfrom
bkellam/search_perf_improvements

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Nov 13, 2025

Copy link
Copy Markdown
Contributor

After benchmarking our search api, I noticed that the zod parseAsync call we were doing in searchApi.ts was contributing to a significant portion of the total search time (average 64.6% in my benchmarks), especially for larger queries that return payloads above > 10MB. This issue colinhacks/zod#205 confirms others are hitting this with zod as well.

We don't really need to parse zoekt's response bodies (since we always expect them to be valid), so this PR removes the parseAsync call and instead does a simple cast. The results are pretty dramatic: my initial benchmarks are shoing a increase in search performance by a order of magnitude (89.8% reduction in average search time). Here's the before & after on the benchmark:

Before:
📈 Latency Distribution:
Min (p0): 407.04ms
p50: 2660.49ms
p75: 11770.50ms
p90: 50848.31ms
p95: 54193.05ms
p99: 74902.95ms
Max (p100):84267.76ms
After:
📈 Latency Distribution:
Min (p0): 293.31ms
p50: 569.53ms
p75: 1855.31ms
p90: 3682.20ms
p95: 4733.21ms
p99: 5561.01ms
Max (p100): 5805.22ms

With this change, I figured we can bump the default number of results requested to 100k (up from 5k!). In my testing performance was good and I was able to get 100k results in <5 seconds.

Summary by CodeRabbit

  • New Features

    • Added debug timing information to search responses for performance visibility.
  • Changed

    • Increased default search result count from 5,000 to 100,000.
  • Bug Fixes

    • Resolved a significant performance bottleneck in the search API, yielding substantial performance gains.

@coderabbitai

coderabbitaiBot commented Nov 13, 2025

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Search optimization increases default result limits from 5,000 to 100,000 entries. Schema-based validation in the client API is replaced with type assertions. Performance instrumentation and timing measurements are added to the search flow, with debug timings returned in responses.

Changes

Cohort / File(s)Summary
Search Configuration & Documentation
CHANGELOG.md, packages/web/src/app/[domain]/search/components/searchResultsPage.tsx
Increased default search result count from 5,000 to 100,000 after optimization pass; documented in changelog.
Schema & Type Definitions
packages/web/src/features/search/schemas.ts, packages/web/src/features/search/zoektSchema.ts
Added optional __debug_timings field to search response schema; introduced ZoektSearchResponse type alias.
Search API Implementation
packages/web/src/features/search/searchApi.ts
Instrumented search flow with performance measurements for fetch, parsing, and transformation steps; added timing breakdown in response; introduced ZoektSearchResponse handling and transformZoektSearchResponse function.
Client API
packages/web/src/app/api/(client)/client.ts
Removed schema-based validation; replaced with type assertions for search, fetchFileSource, getRepos, and getVersion functions; broadened error handling return types.

Sequence Diagram

sequenceDiagram
participant Client
participant SearchAPI as Search API
participant Zoekt
participant Response
Client->>SearchAPI: search({ query, matches, ... })
rect rgb(240, 248, 255)
Note over SearchAPI: measure: fetch
SearchAPI->>Zoekt: fetch search results
Zoekt-->>SearchAPI: raw response
end
rect rgb(240, 248, 255)
Note over SearchAPI: measure: parse_json
SearchAPI->>SearchAPI: JSON.parse(response)
end
rect rgb(240, 248, 255)
Note over SearchAPI: measure: transform
SearchAPI->>SearchAPI: transformZoektSearchResponse()
end
SearchAPI->>Response: SearchResponse + __debug_timings
Response-->>Client: { results, __debug_timings }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • packages/web/src/features/search/searchApi.ts: Significant refactoring with performance instrumentation, new response transformation pathway, and error handling updates—requires careful review of timing measurement logic and response shape changes.
  • packages/web/src/app/api/(client)/client.ts: Wholesale removal of schema-based validation across multiple functions in favor of type assertions—needs verification that this doesn't introduce runtime validation gaps.
  • packages/web/src/features/search/zoektSchema.ts: Appears to contain duplicate ZoektSearchResponse type definitions that should be consolidated.

Possibly related PRs

Suggested reviewers

  • msukkari

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title 'fix(web): Search performance improvements' directly relates to the main objective of this PR, which is to optimize search performance by removing expensive zod validation and increasing result counts.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

This comment has been minimized.

@brendan-kellam

Copy link
Copy Markdown
ContributorAuthor

@coderabbitai review

@coderabbitai

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/web/src/app/api/(client)/client.ts (1)

43-52: Restore ServiceError handling when fetching repos.
Line 51 currently casts the raw JSON to GetReposResponse, so a backend ServiceError payload comes back as if it were a repo list and downstream code will explode when it dereferences repo fields. Please propagate the error by updating the signature and checking isServiceError before casting.

Apply this diff:

-export const getRepos = async (): Promise<GetReposResponse> => {+export const getRepos = async (): Promise<GetReposResponse | ServiceError> => {
const result = await fetch("/api/repos", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
}).then(response => response.json());
- return result as GetReposResponse | ServiceError;+ if (isServiceError(result)) {+ return result;+ }++ return result as GetReposResponse;
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 06c84f0 and d4ae3ff.

📒 Files selected for processing (6)
  • CHANGELOG.md (1 hunks)
  • packages/web/src/app/[domain]/search/components/searchResultsPage.tsx (1 hunks)
  • packages/web/src/app/api/(client)/client.ts (4 hunks)
  • packages/web/src/features/search/schemas.ts (1 hunks)
  • packages/web/src/features/search/searchApi.ts (4 hunks)
  • packages/web/src/features/search/zoektSchema.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

Filenames should always be camelCase. Exception: if there are filenames in the same directory with a format other than camelCase, use that format to keep things consistent.

Files:

  • packages/web/src/features/search/schemas.ts
  • CHANGELOG.md
  • packages/web/src/app/api/(client)/client.ts
  • packages/web/src/app/[domain]/search/components/searchResultsPage.tsx
  • packages/web/src/features/search/zoektSchema.ts
  • packages/web/src/features/search/searchApi.ts
🪛 LanguageTool
CHANGELOG.md

[grammar] ~18-~18: Ensure spelling is correct
Context: ... in search api, resulting in a order of magnitutde performance improvement. [#615](https:/...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build

Comment threadCHANGELOG.md Outdated
@brendan-kellam
brendan-kellam merged commit a814bd6 into mainNov 13, 2025
9 checks passed
@brendan-kellam
brendan-kellam deleted the bkellam/search_perf_improvements branch November 13, 2025 07:20
@github-actionsgithub-actionsBot mentioned this pull request Nov 13, 2025
Sign up for freeto 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

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(web): Search performance improvements - #615

Merged
brendan-kellam merged 5 commits into
mainfrom
bkellam/search_perf_improvements
Nov 13, 2025
Merged

fix(web): Search performance improvements#615
brendan-kellam merged 5 commits into
mainfrom
bkellam/search_perf_improvements

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Nov 13, 2025

Copy link
Copy Markdown
Contributor

After benchmarking our search api, I noticed that the zod parseAsync call we were doing in searchApi.ts was contributing to a significant portion of the total search time (average 64.6% in my benchmarks), especially for larger queries that return payloads above > 10MB. This issue colinhacks/zod#205 confirms others are hitting this with zod as well.

We don't really need to parse zoekt's response bodies (since we always expect them to be valid), so this PR removes the parseAsync call and instead does a simple cast. The results are pretty dramatic: my initial benchmarks are shoing a increase in search performance by a order of magnitude (89.8% reduction in average search time). Here's the before & after on the benchmark:

Before:
📈 Latency Distribution:
Min (p0): 407.04ms
p50: 2660.49ms
p75: 11770.50ms
p90: 50848.31ms
p95: 54193.05ms
p99: 74902.95ms
Max (p100):84267.76ms
After:
📈 Latency Distribution:
Min (p0): 293.31ms
p50: 569.53ms
p75: 1855.31ms
p90: 3682.20ms
p95: 4733.21ms
p99: 5561.01ms
Max (p100): 5805.22ms

With this change, I figured we can bump the default number of results requested to 100k (up from 5k!). In my testing performance was good and I was able to get 100k results in <5 seconds.

Summary by CodeRabbit

  • New Features

    • Added debug timing information to search responses for performance visibility.
  • Changed

    • Increased default search result count from 5,000 to 100,000.
  • Bug Fixes

    • Resolved a significant performance bottleneck in the search API, yielding substantial performance gains.

@coderabbitai

coderabbitaiBot commented Nov 13, 2025

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Search optimization increases default result limits from 5,000 to 100,000 entries. Schema-based validation in the client API is replaced with type assertions. Performance instrumentation and timing measurements are added to the search flow, with debug timings returned in responses.

Changes

Cohort / File(s)Summary
Search Configuration & Documentation
CHANGELOG.md, packages/web/src/app/[domain]/search/components/searchResultsPage.tsx
Increased default search result count from 5,000 to 100,000 after optimization pass; documented in changelog.
Schema & Type Definitions
packages/web/src/features/search/schemas.ts, packages/web/src/features/search/zoektSchema.ts
Added optional __debug_timings field to search response schema; introduced ZoektSearchResponse type alias.
Search API Implementation
packages/web/src/features/search/searchApi.ts
Instrumented search flow with performance measurements for fetch, parsing, and transformation steps; added timing breakdown in response; introduced ZoektSearchResponse handling and transformZoektSearchResponse function.
Client API
packages/web/src/app/api/(client)/client.ts
Removed schema-based validation; replaced with type assertions for search, fetchFileSource, getRepos, and getVersion functions; broadened error handling return types.

Sequence Diagram

sequenceDiagram
participant Client
participant SearchAPI as Search API
participant Zoekt
participant Response
Client->>SearchAPI: search({ query, matches, ... })
rect rgb(240, 248, 255)
Note over SearchAPI: measure: fetch
SearchAPI->>Zoekt: fetch search results
Zoekt-->>SearchAPI: raw response
end
rect rgb(240, 248, 255)
Note over SearchAPI: measure: parse_json
SearchAPI->>SearchAPI: JSON.parse(response)
end
rect rgb(240, 248, 255)
Note over SearchAPI: measure: transform
SearchAPI->>SearchAPI: transformZoektSearchResponse()
end
SearchAPI->>Response: SearchResponse + __debug_timings
Response-->>Client: { results, __debug_timings }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • packages/web/src/features/search/searchApi.ts: Significant refactoring with performance instrumentation, new response transformation pathway, and error handling updates—requires careful review of timing measurement logic and response shape changes.
  • packages/web/src/app/api/(client)/client.ts: Wholesale removal of schema-based validation across multiple functions in favor of type assertions—needs verification that this doesn't introduce runtime validation gaps.
  • packages/web/src/features/search/zoektSchema.ts: Appears to contain duplicate ZoektSearchResponse type definitions that should be consolidated.

Possibly related PRs

Suggested reviewers

  • msukkari

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title 'fix(web): Search performance improvements' directly relates to the main objective of this PR, which is to optimize search performance by removing expensive zod validation and increasing result counts.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

This comment has been minimized.

@brendan-kellam

Copy link
Copy Markdown
ContributorAuthor

@coderabbitai review

@coderabbitai

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/web/src/app/api/(client)/client.ts (1)

43-52: Restore ServiceError handling when fetching repos.
Line 51 currently casts the raw JSON to GetReposResponse, so a backend ServiceError payload comes back as if it were a repo list and downstream code will explode when it dereferences repo fields. Please propagate the error by updating the signature and checking isServiceError before casting.

Apply this diff:

-export const getRepos = async (): Promise<GetReposResponse> => {+export const getRepos = async (): Promise<GetReposResponse | ServiceError> => {
const result = await fetch("/api/repos", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
}).then(response => response.json());
- return result as GetReposResponse | ServiceError;+ if (isServiceError(result)) {+ return result;+ }++ return result as GetReposResponse;
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 06c84f0 and d4ae3ff.

📒 Files selected for processing (6)
  • CHANGELOG.md (1 hunks)
  • packages/web/src/app/[domain]/search/components/searchResultsPage.tsx (1 hunks)
  • packages/web/src/app/api/(client)/client.ts (4 hunks)
  • packages/web/src/features/search/schemas.ts (1 hunks)
  • packages/web/src/features/search/searchApi.ts (4 hunks)
  • packages/web/src/features/search/zoektSchema.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

Filenames should always be camelCase. Exception: if there are filenames in the same directory with a format other than camelCase, use that format to keep things consistent.

Files:

  • packages/web/src/features/search/schemas.ts
  • CHANGELOG.md
  • packages/web/src/app/api/(client)/client.ts
  • packages/web/src/app/[domain]/search/components/searchResultsPage.tsx
  • packages/web/src/features/search/zoektSchema.ts
  • packages/web/src/features/search/searchApi.ts
🪛 LanguageTool
CHANGELOG.md

[grammar] ~18-~18: Ensure spelling is correct
Context: ... in search api, resulting in a order of magnitutde performance improvement. [#615](https:/...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build

Comment threadCHANGELOG.md Outdated
@brendan-kellam
brendan-kellam merged commit a814bd6 into mainNov 13, 2025
9 checks passed
@brendan-kellam
brendan-kellam deleted the bkellam/search_perf_improvements branch November 13, 2025 07:20
@github-actionsgithub-actionsBot mentioned this pull request Nov 13, 2025
Sign up for freeto 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

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(web): Search performance improvements - #615

Merged
brendan-kellam merged 5 commits into
mainfrom
bkellam/search_perf_improvements
Nov 13, 2025
Merged

fix(web): Search performance improvements#615
brendan-kellam merged 5 commits into
mainfrom
bkellam/search_perf_improvements

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Nov 13, 2025

Copy link
Copy Markdown
Contributor

After benchmarking our search api, I noticed that the zod parseAsync call we were doing in searchApi.ts was contributing to a significant portion of the total search time (average 64.6% in my benchmarks), especially for larger queries that return payloads above > 10MB. This issue colinhacks/zod#205 confirms others are hitting this with zod as well.

We don't really need to parse zoekt's response bodies (since we always expect them to be valid), so this PR removes the parseAsync call and instead does a simple cast. The results are pretty dramatic: my initial benchmarks are shoing a increase in search performance by a order of magnitude (89.8% reduction in average search time). Here's the before & after on the benchmark:

Before:
📈 Latency Distribution:
Min (p0): 407.04ms
p50: 2660.49ms
p75: 11770.50ms
p90: 50848.31ms
p95: 54193.05ms
p99: 74902.95ms
Max (p100):84267.76ms
After:
📈 Latency Distribution:
Min (p0): 293.31ms
p50: 569.53ms
p75: 1855.31ms
p90: 3682.20ms
p95: 4733.21ms
p99: 5561.01ms
Max (p100): 5805.22ms

With this change, I figured we can bump the default number of results requested to 100k (up from 5k!). In my testing performance was good and I was able to get 100k results in <5 seconds.

Summary by CodeRabbit

  • New Features

    • Added debug timing information to search responses for performance visibility.
  • Changed

    • Increased default search result count from 5,000 to 100,000.
  • Bug Fixes

    • Resolved a significant performance bottleneck in the search API, yielding substantial performance gains.

@coderabbitai

coderabbitaiBot commented Nov 13, 2025

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Search optimization increases default result limits from 5,000 to 100,000 entries. Schema-based validation in the client API is replaced with type assertions. Performance instrumentation and timing measurements are added to the search flow, with debug timings returned in responses.

Changes

Cohort / File(s)Summary
Search Configuration & Documentation
CHANGELOG.md, packages/web/src/app/[domain]/search/components/searchResultsPage.tsx
Increased default search result count from 5,000 to 100,000 after optimization pass; documented in changelog.
Schema & Type Definitions
packages/web/src/features/search/schemas.ts, packages/web/src/features/search/zoektSchema.ts
Added optional __debug_timings field to search response schema; introduced ZoektSearchResponse type alias.
Search API Implementation
packages/web/src/features/search/searchApi.ts
Instrumented search flow with performance measurements for fetch, parsing, and transformation steps; added timing breakdown in response; introduced ZoektSearchResponse handling and transformZoektSearchResponse function.
Client API
packages/web/src/app/api/(client)/client.ts
Removed schema-based validation; replaced with type assertions for search, fetchFileSource, getRepos, and getVersion functions; broadened error handling return types.

Sequence Diagram

sequenceDiagram
participant Client
participant SearchAPI as Search API
participant Zoekt
participant Response
Client->>SearchAPI: search({ query, matches, ... })
rect rgb(240, 248, 255)
Note over SearchAPI: measure: fetch
SearchAPI->>Zoekt: fetch search results
Zoekt-->>SearchAPI: raw response
end
rect rgb(240, 248, 255)
Note over SearchAPI: measure: parse_json
SearchAPI->>SearchAPI: JSON.parse(response)
end
rect rgb(240, 248, 255)
Note over SearchAPI: measure: transform
SearchAPI->>SearchAPI: transformZoektSearchResponse()
end
SearchAPI->>Response: SearchResponse + __debug_timings
Response-->>Client: { results, __debug_timings }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • packages/web/src/features/search/searchApi.ts: Significant refactoring with performance instrumentation, new response transformation pathway, and error handling updates—requires careful review of timing measurement logic and response shape changes.
  • packages/web/src/app/api/(client)/client.ts: Wholesale removal of schema-based validation across multiple functions in favor of type assertions—needs verification that this doesn't introduce runtime validation gaps.
  • packages/web/src/features/search/zoektSchema.ts: Appears to contain duplicate ZoektSearchResponse type definitions that should be consolidated.

Possibly related PRs

Suggested reviewers

  • msukkari

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title 'fix(web): Search performance improvements' directly relates to the main objective of this PR, which is to optimize search performance by removing expensive zod validation and increasing result counts.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

This comment has been minimized.

@brendan-kellam

Copy link
Copy Markdown
ContributorAuthor

@coderabbitai review

@coderabbitai

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/web/src/app/api/(client)/client.ts (1)

43-52: Restore ServiceError handling when fetching repos.
Line 51 currently casts the raw JSON to GetReposResponse, so a backend ServiceError payload comes back as if it were a repo list and downstream code will explode when it dereferences repo fields. Please propagate the error by updating the signature and checking isServiceError before casting.

Apply this diff:

-export const getRepos = async (): Promise<GetReposResponse> => {+export const getRepos = async (): Promise<GetReposResponse | ServiceError> => {
const result = await fetch("/api/repos", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
}).then(response => response.json());
- return result as GetReposResponse | ServiceError;+ if (isServiceError(result)) {+ return result;+ }++ return result as GetReposResponse;
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 06c84f0 and d4ae3ff.

📒 Files selected for processing (6)
  • CHANGELOG.md (1 hunks)
  • packages/web/src/app/[domain]/search/components/searchResultsPage.tsx (1 hunks)
  • packages/web/src/app/api/(client)/client.ts (4 hunks)
  • packages/web/src/features/search/schemas.ts (1 hunks)
  • packages/web/src/features/search/searchApi.ts (4 hunks)
  • packages/web/src/features/search/zoektSchema.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

Filenames should always be camelCase. Exception: if there are filenames in the same directory with a format other than camelCase, use that format to keep things consistent.

Files:

  • packages/web/src/features/search/schemas.ts
  • CHANGELOG.md
  • packages/web/src/app/api/(client)/client.ts
  • packages/web/src/app/[domain]/search/components/searchResultsPage.tsx
  • packages/web/src/features/search/zoektSchema.ts
  • packages/web/src/features/search/searchApi.ts
🪛 LanguageTool
CHANGELOG.md

[grammar] ~18-~18: Ensure spelling is correct
Context: ... in search api, resulting in a order of magnitutde performance improvement. [#615](https:/...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build

Comment threadCHANGELOG.md Outdated
@brendan-kellam
brendan-kellam merged commit a814bd6 into mainNov 13, 2025
9 checks passed
@brendan-kellam
brendan-kellam deleted the bkellam/search_perf_improvements branch November 13, 2025 07:20
@github-actionsgithub-actionsBot mentioned this pull request Nov 13, 2025
Sign up for freeto 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

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(web): Search performance improvements - #615

Merged
brendan-kellam merged 5 commits into
mainfrom
bkellam/search_perf_improvements
Nov 13, 2025
Merged

fix(web): Search performance improvements#615
brendan-kellam merged 5 commits into
mainfrom
bkellam/search_perf_improvements

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Nov 13, 2025

Copy link
Copy Markdown
Contributor

After benchmarking our search api, I noticed that the zod parseAsync call we were doing in searchApi.ts was contributing to a significant portion of the total search time (average 64.6% in my benchmarks), especially for larger queries that return payloads above > 10MB. This issue colinhacks/zod#205 confirms others are hitting this with zod as well.

We don't really need to parse zoekt's response bodies (since we always expect them to be valid), so this PR removes the parseAsync call and instead does a simple cast. The results are pretty dramatic: my initial benchmarks are shoing a increase in search performance by a order of magnitude (89.8% reduction in average search time). Here's the before & after on the benchmark:

Before:
📈 Latency Distribution:
Min (p0): 407.04ms
p50: 2660.49ms
p75: 11770.50ms
p90: 50848.31ms
p95: 54193.05ms
p99: 74902.95ms
Max (p100):84267.76ms
After:
📈 Latency Distribution:
Min (p0): 293.31ms
p50: 569.53ms
p75: 1855.31ms
p90: 3682.20ms
p95: 4733.21ms
p99: 5561.01ms
Max (p100): 5805.22ms

With this change, I figured we can bump the default number of results requested to 100k (up from 5k!). In my testing performance was good and I was able to get 100k results in <5 seconds.

Summary by CodeRabbit

  • New Features

    • Added debug timing information to search responses for performance visibility.
  • Changed

    • Increased default search result count from 5,000 to 100,000.
  • Bug Fixes

    • Resolved a significant performance bottleneck in the search API, yielding substantial performance gains.

@coderabbitai

coderabbitaiBot commented Nov 13, 2025

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Search optimization increases default result limits from 5,000 to 100,000 entries. Schema-based validation in the client API is replaced with type assertions. Performance instrumentation and timing measurements are added to the search flow, with debug timings returned in responses.

Changes

Cohort / File(s)Summary
Search Configuration & Documentation
CHANGELOG.md, packages/web/src/app/[domain]/search/components/searchResultsPage.tsx
Increased default search result count from 5,000 to 100,000 after optimization pass; documented in changelog.
Schema & Type Definitions
packages/web/src/features/search/schemas.ts, packages/web/src/features/search/zoektSchema.ts
Added optional __debug_timings field to search response schema; introduced ZoektSearchResponse type alias.
Search API Implementation
packages/web/src/features/search/searchApi.ts
Instrumented search flow with performance measurements for fetch, parsing, and transformation steps; added timing breakdown in response; introduced ZoektSearchResponse handling and transformZoektSearchResponse function.
Client API
packages/web/src/app/api/(client)/client.ts
Removed schema-based validation; replaced with type assertions for search, fetchFileSource, getRepos, and getVersion functions; broadened error handling return types.

Sequence Diagram

sequenceDiagram
participant Client
participant SearchAPI as Search API
participant Zoekt
participant Response
Client->>SearchAPI: search({ query, matches, ... })
rect rgb(240, 248, 255)
Note over SearchAPI: measure: fetch
SearchAPI->>Zoekt: fetch search results
Zoekt-->>SearchAPI: raw response
end
rect rgb(240, 248, 255)
Note over SearchAPI: measure: parse_json
SearchAPI->>SearchAPI: JSON.parse(response)
end
rect rgb(240, 248, 255)
Note over SearchAPI: measure: transform
SearchAPI->>SearchAPI: transformZoektSearchResponse()
end
SearchAPI->>Response: SearchResponse + __debug_timings
Response-->>Client: { results, __debug_timings }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • packages/web/src/features/search/searchApi.ts: Significant refactoring with performance instrumentation, new response transformation pathway, and error handling updates—requires careful review of timing measurement logic and response shape changes.
  • packages/web/src/app/api/(client)/client.ts: Wholesale removal of schema-based validation across multiple functions in favor of type assertions—needs verification that this doesn't introduce runtime validation gaps.
  • packages/web/src/features/search/zoektSchema.ts: Appears to contain duplicate ZoektSearchResponse type definitions that should be consolidated.

Possibly related PRs

Suggested reviewers

  • msukkari

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title 'fix(web): Search performance improvements' directly relates to the main objective of this PR, which is to optimize search performance by removing expensive zod validation and increasing result counts.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

This comment has been minimized.

@brendan-kellam

Copy link
Copy Markdown
ContributorAuthor

@coderabbitai review

@coderabbitai

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/web/src/app/api/(client)/client.ts (1)

43-52: Restore ServiceError handling when fetching repos.
Line 51 currently casts the raw JSON to GetReposResponse, so a backend ServiceError payload comes back as if it were a repo list and downstream code will explode when it dereferences repo fields. Please propagate the error by updating the signature and checking isServiceError before casting.

Apply this diff:

-export const getRepos = async (): Promise<GetReposResponse> => {+export const getRepos = async (): Promise<GetReposResponse | ServiceError> => {
const result = await fetch("/api/repos", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
}).then(response => response.json());
- return result as GetReposResponse | ServiceError;+ if (isServiceError(result)) {+ return result;+ }++ return result as GetReposResponse;
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 06c84f0 and d4ae3ff.

📒 Files selected for processing (6)
  • CHANGELOG.md (1 hunks)
  • packages/web/src/app/[domain]/search/components/searchResultsPage.tsx (1 hunks)
  • packages/web/src/app/api/(client)/client.ts (4 hunks)
  • packages/web/src/features/search/schemas.ts (1 hunks)
  • packages/web/src/features/search/searchApi.ts (4 hunks)
  • packages/web/src/features/search/zoektSchema.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

Filenames should always be camelCase. Exception: if there are filenames in the same directory with a format other than camelCase, use that format to keep things consistent.

Files:

  • packages/web/src/features/search/schemas.ts
  • CHANGELOG.md
  • packages/web/src/app/api/(client)/client.ts
  • packages/web/src/app/[domain]/search/components/searchResultsPage.tsx
  • packages/web/src/features/search/zoektSchema.ts
  • packages/web/src/features/search/searchApi.ts
🪛 LanguageTool
CHANGELOG.md

[grammar] ~18-~18: Ensure spelling is correct
Context: ... in search api, resulting in a order of magnitutde performance improvement. [#615](https:/...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build

Comment threadCHANGELOG.md Outdated
@brendan-kellam
brendan-kellam merged commit a814bd6 into mainNov 13, 2025
9 checks passed
@brendan-kellam
brendan-kellam deleted the bkellam/search_perf_improvements branch November 13, 2025 07:20
@github-actionsgithub-actionsBot mentioned this pull request Nov 13, 2025
Sign up for freeto 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

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(web): Search performance improvements - #615

Merged
brendan-kellam merged 5 commits into
mainfrom
bkellam/search_perf_improvements
Nov 13, 2025
Merged

fix(web): Search performance improvements#615
brendan-kellam merged 5 commits into
mainfrom
bkellam/search_perf_improvements

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Nov 13, 2025

Copy link
Copy Markdown
Contributor

After benchmarking our search api, I noticed that the zod parseAsync call we were doing in searchApi.ts was contributing to a significant portion of the total search time (average 64.6% in my benchmarks), especially for larger queries that return payloads above > 10MB. This issue colinhacks/zod#205 confirms others are hitting this with zod as well.

We don't really need to parse zoekt's response bodies (since we always expect them to be valid), so this PR removes the parseAsync call and instead does a simple cast. The results are pretty dramatic: my initial benchmarks are shoing a increase in search performance by a order of magnitude (89.8% reduction in average search time). Here's the before & after on the benchmark:

Before:
📈 Latency Distribution:
Min (p0): 407.04ms
p50: 2660.49ms
p75: 11770.50ms
p90: 50848.31ms
p95: 54193.05ms
p99: 74902.95ms
Max (p100):84267.76ms
After:
📈 Latency Distribution:
Min (p0): 293.31ms
p50: 569.53ms
p75: 1855.31ms
p90: 3682.20ms
p95: 4733.21ms
p99: 5561.01ms
Max (p100): 5805.22ms

With this change, I figured we can bump the default number of results requested to 100k (up from 5k!). In my testing performance was good and I was able to get 100k results in <5 seconds.

Summary by CodeRabbit

  • New Features

    • Added debug timing information to search responses for performance visibility.
  • Changed

    • Increased default search result count from 5,000 to 100,000.
  • Bug Fixes

    • Resolved a significant performance bottleneck in the search API, yielding substantial performance gains.

@coderabbitai

coderabbitaiBot commented Nov 13, 2025

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Search optimization increases default result limits from 5,000 to 100,000 entries. Schema-based validation in the client API is replaced with type assertions. Performance instrumentation and timing measurements are added to the search flow, with debug timings returned in responses.

Changes

Cohort / File(s)Summary
Search Configuration & Documentation
CHANGELOG.md, packages/web/src/app/[domain]/search/components/searchResultsPage.tsx
Increased default search result count from 5,000 to 100,000 after optimization pass; documented in changelog.
Schema & Type Definitions
packages/web/src/features/search/schemas.ts, packages/web/src/features/search/zoektSchema.ts
Added optional __debug_timings field to search response schema; introduced ZoektSearchResponse type alias.
Search API Implementation
packages/web/src/features/search/searchApi.ts
Instrumented search flow with performance measurements for fetch, parsing, and transformation steps; added timing breakdown in response; introduced ZoektSearchResponse handling and transformZoektSearchResponse function.
Client API
packages/web/src/app/api/(client)/client.ts
Removed schema-based validation; replaced with type assertions for search, fetchFileSource, getRepos, and getVersion functions; broadened error handling return types.

Sequence Diagram

sequenceDiagram
participant Client
participant SearchAPI as Search API
participant Zoekt
participant Response
Client->>SearchAPI: search({ query, matches, ... })
rect rgb(240, 248, 255)
Note over SearchAPI: measure: fetch
SearchAPI->>Zoekt: fetch search results
Zoekt-->>SearchAPI: raw response
end
rect rgb(240, 248, 255)
Note over SearchAPI: measure: parse_json
SearchAPI->>SearchAPI: JSON.parse(response)
end
rect rgb(240, 248, 255)
Note over SearchAPI: measure: transform
SearchAPI->>SearchAPI: transformZoektSearchResponse()
end
SearchAPI->>Response: SearchResponse + __debug_timings
Response-->>Client: { results, __debug_timings }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • packages/web/src/features/search/searchApi.ts: Significant refactoring with performance instrumentation, new response transformation pathway, and error handling updates—requires careful review of timing measurement logic and response shape changes.
  • packages/web/src/app/api/(client)/client.ts: Wholesale removal of schema-based validation across multiple functions in favor of type assertions—needs verification that this doesn't introduce runtime validation gaps.
  • packages/web/src/features/search/zoektSchema.ts: Appears to contain duplicate ZoektSearchResponse type definitions that should be consolidated.

Possibly related PRs

Suggested reviewers

  • msukkari

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title 'fix(web): Search performance improvements' directly relates to the main objective of this PR, which is to optimize search performance by removing expensive zod validation and increasing result counts.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

This comment has been minimized.

@brendan-kellam

Copy link
Copy Markdown
ContributorAuthor

@coderabbitai review

@coderabbitai

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/web/src/app/api/(client)/client.ts (1)

43-52: Restore ServiceError handling when fetching repos.
Line 51 currently casts the raw JSON to GetReposResponse, so a backend ServiceError payload comes back as if it were a repo list and downstream code will explode when it dereferences repo fields. Please propagate the error by updating the signature and checking isServiceError before casting.

Apply this diff:

-export const getRepos = async (): Promise<GetReposResponse> => {+export const getRepos = async (): Promise<GetReposResponse | ServiceError> => {
const result = await fetch("/api/repos", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
}).then(response => response.json());
- return result as GetReposResponse | ServiceError;+ if (isServiceError(result)) {+ return result;+ }++ return result as GetReposResponse;
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 06c84f0 and d4ae3ff.

📒 Files selected for processing (6)
  • CHANGELOG.md (1 hunks)
  • packages/web/src/app/[domain]/search/components/searchResultsPage.tsx (1 hunks)
  • packages/web/src/app/api/(client)/client.ts (4 hunks)
  • packages/web/src/features/search/schemas.ts (1 hunks)
  • packages/web/src/features/search/searchApi.ts (4 hunks)
  • packages/web/src/features/search/zoektSchema.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

Filenames should always be camelCase. Exception: if there are filenames in the same directory with a format other than camelCase, use that format to keep things consistent.

Files:

  • packages/web/src/features/search/schemas.ts
  • CHANGELOG.md
  • packages/web/src/app/api/(client)/client.ts
  • packages/web/src/app/[domain]/search/components/searchResultsPage.tsx
  • packages/web/src/features/search/zoektSchema.ts
  • packages/web/src/features/search/searchApi.ts
🪛 LanguageTool
CHANGELOG.md

[grammar] ~18-~18: Ensure spelling is correct
Context: ... in search api, resulting in a order of magnitutde performance improvement. [#615](https:/...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build

Comment threadCHANGELOG.md Outdated
@brendan-kellam
brendan-kellam merged commit a814bd6 into mainNov 13, 2025
9 checks passed
@brendan-kellam
brendan-kellam deleted the bkellam/search_perf_improvements branch November 13, 2025 07:20
@github-actionsgithub-actionsBot mentioned this pull request Nov 13, 2025
Sign up for freeto 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

@brendan-kellam