perf(grants,favorites): batch resource resolution to kill N+1 and pool fan-out
Three list endpoints resolved each resource with one query per id: - GET /api/grants/incoming and /api/grants/outgoing used join_all(ids.map(get_file)) + join_all(ids.map(get_folder)), so a single page (limit ≤ 200) could demand ~200 concurrent connections from the 20-connection primary pool, causing acquire-timeouts and head-of-line blocking under load. - The NextCloud favorites REPORT (oc:filter-files) fetched get_file/ get_folder once per favorite — up to N serial round-trips per sync. Add by-ids batch reads that mirror the existing get_file/get_folder column mapping and NOT is_trashed filter: - FileBlobReadRepository::get_files_by_ids / FolderDbRepository::get_folders_by_ids (one SELECT ... WHERE id = ANY($1)), exposed as FileRetrievalService:: get_files_by_ids / FolderService::get_folders_by_ids returning DTOs. - Both grant handlers and the favorites REPORT now issue two batch queries total and look results up by id, preserving original order. Missing ids (stale grants whose resource was deleted, or trashed/removed favorites) drop out exactly as before. No auth-semantics change: these paths already resolved ids vetted by the authorization engine / favorites table. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TAzLEQDaLak3dnrEN3YT35
This commit is contained in:
@@ -7,7 +7,7 @@ use quick_xml::{
|
||||
Reader, Writer,
|
||||
events::{BytesEnd, BytesStart, Event},
|
||||
};
|
||||
use std::collections::HashSet;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::application::dtos::display_helpers::{
|
||||
@@ -17,7 +17,6 @@ use crate::application::dtos::file_dto::FileDto;
|
||||
use crate::application::dtos::folder_dto::FolderDto;
|
||||
use crate::application::dtos::search_dto::SearchCriteriaDto;
|
||||
use crate::application::ports::favorites_ports::FavoritesUseCase;
|
||||
use crate::application::ports::file_ports::FileRetrievalUseCase;
|
||||
use crate::application::ports::folder_ports::FolderUseCase;
|
||||
use crate::application::ports::inbound::SearchUseCase;
|
||||
use crate::common::di::AppState;
|
||||
@@ -86,20 +85,47 @@ async fn handle_filter_files(
|
||||
|
||||
let home_prefix = format!("My Folder - {}/", user.username);
|
||||
|
||||
// Pass 1: fetch the favorited DTOs (the per-item fetch is a separate
|
||||
// concern from the oc:fileid resolution batched below).
|
||||
// Pass 1: resolve the favorited DTOs in two batch queries (was one
|
||||
// get_* per favorite — up to N serial round-trips on a sync client's
|
||||
// REPORT). Results are looked up by id so the response keeps favorites
|
||||
// order; missing/trashed favorites simply drop out (as before).
|
||||
let mut file_ids: Vec<String> = Vec::new();
|
||||
let mut folder_ids: Vec<String> = Vec::new();
|
||||
for fav in &favorites {
|
||||
match fav.item_type.as_str() {
|
||||
"file" => file_ids.push(fav.item_id.clone()),
|
||||
"folder" => folder_ids.push(fav.item_id.clone()),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let file_map: HashMap<String, FileDto> = file_service
|
||||
.get_files_by_ids(&file_ids)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to resolve favorite files: {e}")))?
|
||||
.into_iter()
|
||||
.map(|f| (f.id.clone(), f))
|
||||
.collect();
|
||||
let folder_map: HashMap<String, FolderDto> = folder_service
|
||||
.get_folders_by_ids(&folder_ids)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to resolve favorite folders: {e}")))?
|
||||
.into_iter()
|
||||
.map(|f| (f.id.clone(), f))
|
||||
.collect();
|
||||
|
||||
let mut files: Vec<FileDto> = Vec::new();
|
||||
let mut folders: Vec<FolderDto> = Vec::new();
|
||||
for fav in &favorites {
|
||||
match fav.item_type.as_str() {
|
||||
"file" => {
|
||||
if let Ok(f) = file_service.get_file(&fav.item_id).await {
|
||||
files.push(f);
|
||||
if let Some(f) = file_map.get(&fav.item_id) {
|
||||
files.push(f.clone());
|
||||
}
|
||||
}
|
||||
"folder" => {
|
||||
if let Ok(f) = folder_service.get_folder(&fav.item_id).await {
|
||||
folders.push(f);
|
||||
if let Some(f) = folder_map.get(&fav.item_id) {
|
||||
folders.push(f.clone());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
||||
Reference in New Issue
Block a user