From 175a908e99ada03150378804167426f5c91318be Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 9 Jun 2026 14:59:01 +0000 Subject: [PATCH] perf(search): clamp search page size to bound per-request memory The main search endpoint took `limit: params.limit.unwrap_or(100)` with no ceiling, and that value flows straight into the SQL LIMIT of search_files_paginated / search_files_in_subtree. A client passing ?limit= would make Postgres return that many rows into memory and into the result cache. The suggestions endpoint already clamps (.min(20)); search did not. Cap at MAX_SEARCH_LIMIT (500). total_count still reflects the full match set (COUNT(*) OVER()), so deeper results stay reachable via offset. https://claude.ai/code/session_01UtfkS3nZF1vrF5jNAps6wV --- src/interfaces/api/handlers/search_handler.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/interfaces/api/handlers/search_handler.rs b/src/interfaces/api/handlers/search_handler.rs index 16609a1a..5fba8009 100644 --- a/src/interfaces/api/handlers/search_handler.rs +++ b/src/interfaces/api/handlers/search_handler.rs @@ -21,6 +21,14 @@ use std::sync::Arc; * formatting) is performed server-side. These handlers are thin HTTP * adapters that delegate to the SearchUseCase. */ +/// Hard cap on the search page size. The default is 100; without a ceiling a +/// client could pass `?limit=`, which flows straight into the SQL `LIMIT` +/// and would pull that many rows into memory (and into the result cache). 500 +/// is a generous page for a search UI — `total_count` still reflects the full +/// match set, so deeper results stay reachable via `offset`. Mirrors the +/// suggestions endpoint, which already clamps with `.min(20)`. +const MAX_SEARCH_LIMIT: usize = 500; + pub struct SearchHandler; impl SearchHandler { @@ -62,7 +70,7 @@ impl SearchHandler { max_size: params.max_size, folder_id: params.folder_id, recursive: params.recursive.unwrap_or(true), - limit: params.limit.unwrap_or(100), + limit: params.limit.unwrap_or(100).min(MAX_SEARCH_LIMIT), offset: params.offset.unwrap_or(0), sort_by: params.sort_by.unwrap_or_else(|| "relevance".to_string()), };