perf: OnceLock for env var, Arc<CurrentUser> in auth, pre-compute query lowercase

- rate_limit: cache OXICLOUD_TRUST_PROXY_HEADERS in OnceLock<bool> to avoid
  syscall on every request (~500ns → ~1ns)
- auth middleware: insert Arc<CurrentUser> instead of bare CurrentUser;
  all 5 extractors now clone Arc (~1ns) instead of 4 Strings (~60-100ns)
- search_service: pre-compute query.to_lowercase() once before loops,
  eliminating N redundant heap allocations per search
This commit is contained in:
Diocrafts
2026-03-07 11:23:56 +01:00
parent e4bcab0488
commit 9f08460027
8 changed files with 796 additions and 42 deletions
+27 -16
View File
@@ -53,15 +53,17 @@ pub struct SearchService {
/// Compute relevance score (0–100) for a name against a query.
/// Exact match = 100, starts-with = 80, contains = 50, no match = 0.
fn compute_relevance(name: &str, query: &str) -> u32 {
///
/// `query_lower` **must** already be lowercased by the caller so that the
/// allocation happens once per search, not once per result.
fn compute_relevance(name: &str, query_lower: &str) -> u32 {
let name_lower = name.to_lowercase();
let query_lower = query.to_lowercase();
if name_lower == query_lower {
100
} else if name_lower.starts_with(&query_lower) {
} else if name_lower.starts_with(query_lower) {
80
} else if name_lower.contains(&query_lower) {
} else if name_lower.contains(query_lower) {
// Bonus for shorter names (more specific match)
let ratio = query_lower.len() as f64 / name_lower.len() as f64;
50 + (ratio * 20.0) as u32
@@ -146,11 +148,13 @@ impl SearchService {
}
/// Enrich a FileDto → SearchFileResultDto with server-computed metadata.
fn enrich_file(file: &FileDto, query: &str) -> SearchFileResultDto {
let relevance = if query.is_empty() {
///
/// `query_lower` must already be lowercased (empty string when no query).
fn enrich_file(file: &FileDto, query_lower: &str) -> SearchFileResultDto {
let relevance = if query_lower.is_empty() {
50
} else {
compute_relevance(&file.name, query)
compute_relevance(&file.name, query_lower)
};
SearchFileResultDto {
@@ -171,11 +175,13 @@ impl SearchService {
}
/// Enrich a FolderDto → SearchFolderResultDto with server-computed metadata.
fn enrich_folder(folder: &FolderDto, query: &str) -> SearchFolderResultDto {
let relevance = if query.is_empty() {
///
/// `query_lower` must already be lowercased (empty string when no query).
fn enrich_folder(folder: &FolderDto, query_lower: &str) -> SearchFolderResultDto {
let relevance = if query_lower.is_empty() {
50
} else {
compute_relevance(&folder.name, query)
compute_relevance(&folder.name, query_lower)
};
SearchFolderResultDto {
@@ -214,9 +220,12 @@ impl SearchService {
let mut suggestions: Vec<SearchSuggestionItem> =
Vec::with_capacity(files.len() + folders.len());
// Pre-compute once — avoids N heap allocations inside the loops.
let query_lower = query.to_lowercase();
for file in &files {
let file_dto = FileDto::from(file.clone());
let score = compute_relevance(&file_dto.name, query);
let score = compute_relevance(&file_dto.name, &query_lower);
suggestions.push(SearchSuggestionItem {
name: file_dto.name.clone(),
item_type: "file".to_string(),
@@ -230,7 +239,7 @@ impl SearchService {
for folder in &folders {
let folder_dto = FolderDto::from(folder.clone());
let score = compute_relevance(&folder_dto.name, query);
let score = compute_relevance(&folder_dto.name, &query_lower);
suggestions.push(SearchSuggestionItem {
name: folder_dto.name.clone(),
item_type: "folder".to_string(),
@@ -287,6 +296,8 @@ impl SearchUseCase for SearchService {
}
let query = criteria.name_contains.as_deref().unwrap_or("");
// Pre-compute once — avoids N heap allocations inside enrich_file/enrich_folder.
let query_lower = query.to_lowercase();
// For non-recursive searches, use efficient database-level pagination
// This avoids loading all files into memory
@@ -301,7 +312,7 @@ impl SearchUseCase for SearchService {
let file_dtos: Vec<FileDto> = files.into_iter().map(FileDto::from).collect();
let enriched_files: Vec<SearchFileResultDto> = file_dtos
.iter()
.map(|f| Self::enrich_file(f, query))
.map(|f| Self::enrich_file(f, &query_lower))
.collect();
// Get folders for this folder (non-recursive, filtered in SQL)
@@ -321,7 +332,7 @@ impl SearchUseCase for SearchService {
// For folders, apply sorting and pagination in memory (usually fewer folders)
let mut enriched_folders: Vec<SearchFolderResultDto> = filtered_folders
.iter()
.map(|f| Self::enrich_folder(f, query))
.map(|f| Self::enrich_folder(f, &query_lower))
.collect();
// Sort folders (cached_key avoids O(N log N) temporary String allocations)
@@ -401,13 +412,13 @@ impl SearchUseCase for SearchService {
let file_dtos: Vec<FileDto> = found_files.into_iter().map(FileDto::from).collect();
let enriched_files: Vec<SearchFileResultDto> = file_dtos
.iter()
.map(|f| Self::enrich_file(f, query))
.map(|f| Self::enrich_file(f, &query_lower))
.collect();
let folder_dtos: Vec<FolderDto> = found_folders.into_iter().map(FolderDto::from).collect();
let mut enriched_folders: Vec<SearchFolderResultDto> = folder_dtos
.iter()
.map(|f| Self::enrich_folder(f, query))
.map(|f| Self::enrich_folder(f, &query_lower))
.collect();
// ── Sort folders (cached_key avoids O(N log N) temporary String allocations) ──