perf(search): migrate moka::sync::Cache to moka::future::Cache
sync::Cache uses an internal Mutex that blocks the Tokio worker thread during maintenance (eviction, frequency bookkeeping). Under high concurrency this serialises all tasks scheduled on the same worker. future::Cache defers maintenance to an internal async task and never holds a synchronous lock visible to the caller. Changes: - Field type: sync::Cache → future::Cache - get_from_cache / store_in_cache: now async, calls .await - clear_search_cache: added run_pending_tasks().await after invalidate Consistent with the pattern already used in thumbnail_service, image_transcode_service, and file_content_cache.
This commit is contained in:
@@ -41,8 +41,10 @@ pub struct SearchService {
|
|||||||
/// Repository for folder operations
|
/// Repository for folder operations
|
||||||
folder_repository: Arc<dyn FolderStoragePort>,
|
folder_repository: Arc<dyn FolderStoragePort>,
|
||||||
|
|
||||||
/// Lock-free concurrent cache with automatic TTL and LRU eviction (moka)
|
/// Lock-free concurrent cache with automatic TTL and LRU eviction (moka).
|
||||||
search_cache: moka::sync::Cache<u64, SearchResultsDto>,
|
/// Uses `future::Cache` so `.get()` / `.insert()` never block the Tokio
|
||||||
|
/// runtime (maintenance work is deferred to an internal async task).
|
||||||
|
search_cache: moka::future::Cache<u64, SearchResultsDto>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Utility functions (pure, no self — computed on the server) ─────────
|
// ─── Utility functions (pure, no self — computed on the server) ─────────
|
||||||
@@ -111,7 +113,7 @@ impl SearchService {
|
|||||||
cache_ttl: u64,
|
cache_ttl: u64,
|
||||||
max_cache_size: usize,
|
max_cache_size: usize,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let search_cache = moka::sync::Cache::builder()
|
let search_cache = moka::future::Cache::builder()
|
||||||
.max_capacity(max_cache_size as u64)
|
.max_capacity(max_cache_size as u64)
|
||||||
.time_to_live(Duration::from_secs(cache_ttl))
|
.time_to_live(Duration::from_secs(cache_ttl))
|
||||||
.build();
|
.build();
|
||||||
@@ -132,13 +134,13 @@ impl SearchService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Attempts to retrieve results from the cache.
|
/// Attempts to retrieve results from the cache.
|
||||||
fn get_from_cache(&self, key: u64) -> Option<SearchResultsDto> {
|
async fn get_from_cache(&self, key: u64) -> Option<SearchResultsDto> {
|
||||||
self.search_cache.get(&key)
|
self.search_cache.get(&key).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stores results in the cache.
|
/// Stores results in the cache.
|
||||||
fn store_in_cache(&self, key: u64, results: SearchResultsDto) {
|
async fn store_in_cache(&self, key: u64, results: SearchResultsDto) {
|
||||||
self.search_cache.insert(key, results);
|
self.search_cache.insert(key, results).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enrich a FileDto → SearchFileResultDto with server-computed metadata.
|
/// Enrich a FileDto → SearchFileResultDto with server-computed metadata.
|
||||||
@@ -278,7 +280,7 @@ impl SearchUseCase for SearchService {
|
|||||||
|
|
||||||
// Try to get from cache
|
// Try to get from cache
|
||||||
let cache_key = Self::create_cache_key(&criteria, user_id);
|
let cache_key = Self::create_cache_key(&criteria, user_id);
|
||||||
if let Some(cached_results) = self.get_from_cache(cache_key) {
|
if let Some(cached_results) = self.get_from_cache(cache_key).await {
|
||||||
return Ok(cached_results);
|
return Ok(cached_results);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,7 +384,7 @@ impl SearchUseCase for SearchService {
|
|||||||
criteria.sort_by.clone(),
|
criteria.sort_by.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
self.store_in_cache(cache_key, search_results.clone());
|
self.store_in_cache(cache_key, search_results.clone()).await;
|
||||||
return Ok(search_results);
|
return Ok(search_results);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -481,7 +483,7 @@ impl SearchUseCase for SearchService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Store in cache
|
// Store in cache
|
||||||
self.store_in_cache(cache_key, search_results.clone());
|
self.store_in_cache(cache_key, search_results.clone()).await;
|
||||||
|
|
||||||
Ok(search_results)
|
Ok(search_results)
|
||||||
}
|
}
|
||||||
@@ -499,6 +501,7 @@ impl SearchUseCase for SearchService {
|
|||||||
/// Clears the search results cache.
|
/// Clears the search results cache.
|
||||||
async fn clear_search_cache(&self) -> Result<()> {
|
async fn clear_search_cache(&self) -> Result<()> {
|
||||||
self.search_cache.invalidate_all();
|
self.search_cache.invalidate_all();
|
||||||
|
self.search_cache.run_pending_tasks().await;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user