perf: round 6 backend — CardDAV cursor streaming, borrowed NC id chain, binary UUID decode, one-alloc hex
Benchmark-gated (equivalence + BEFORE/AFTER in examples/bench_*, results and reproduce commands in benches/ROUND6.md): - CardDAV whole-book REPORT + depth-1 PROPFIND stream through a PG cursor (stream_contacts_by_book, 500-contact pages) instead of materialising every vCard twice: 8 000 contacts TTFB 37.4 → 7.6 ms (4.9x), peak heap 19.0 → 7.0 MiB (2.7x), wall -23%; REPORT and PROPFIND byte-identical to the buffered writers. - NC numeric-id chain fully borrowed: get_or_create_file_ids/folder_ids take &[&str] and return HashMap<Uuid, i64>; batch_resolve_ids callers (PROPFIND pages, REPORT, trashbin, OCS search) pass id slices and look up via nc_id_of. 2.006 → 0.006 allocs/child (334x), 1.53x wall per 500-child page. batch_check_favorites binds &[&str] as text[]. - file_blob_read_repository listing SELECTs drop id::text/folder_id::text server casts: rows decode binary Uuid (16 vs 36 bytes on the wire) and render once in row_to_file. A/B on 500-row pages: 1.225 → 1.044 ms mean (1.17x), p95 1.686 → 1.345 (bench_uuid_text_cast; single-row, param and min() sites left as-is deliberately). - IncrementalHasher::finalize_hex renders through common::fmt::hex_lower instead of one format! per digest byte: 18 → 1 (md5) / 35 → 1 (sha256) allocs per chunk finalize, 14-15x wall. - Share landing overlaps the access-count UPDATE with the unlock fetch via tokio::join! (one round-trip off every public link hit). - REJECTED by benchmark and reverted: try_join_all fan-out of the batch-favorites authz pre-check — 42.6 → 56.4 ms cold, 0.15 → 0.23 ms warm against local-socket PG (bench_favorites_authz kept as evidence). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017aJu9ghvuT8WqC31ZEGTBA
This commit is contained in:
@@ -26,7 +26,7 @@ use crate::interfaces::api::handlers::webdav_handler::{
|
||||
};
|
||||
use crate::interfaces::errors::AppError;
|
||||
use crate::interfaces::nextcloud::webdav_handler::{
|
||||
batch_resolve_ids, format_oc_id, nc_href, write_file_response, write_folder_response,
|
||||
batch_resolve_ids, format_oc_id, nc_href, nc_id_of, write_file_response, write_folder_response,
|
||||
};
|
||||
|
||||
/// Handle WebDAV REPORT and SEARCH methods for Nextcloud compatibility.
|
||||
@@ -150,8 +150,8 @@ async fn handle_filter_files(
|
||||
}
|
||||
|
||||
// Pass 2: resolve every oc:fileid in two batch queries (was one per item).
|
||||
let file_uuids: Vec<String> = files.iter().map(|f| f.id.clone()).collect();
|
||||
let folder_uuids: Vec<String> = folders.iter().map(|f| f.id.clone()).collect();
|
||||
let file_uuids: Vec<&str> = files.iter().map(|f| f.id.as_str()).collect();
|
||||
let folder_uuids: Vec<&str> = folders.iter().map(|f| f.id.as_str()).collect();
|
||||
let (file_id_map, folder_id_map) =
|
||||
batch_resolve_ids(file_id_svc, &file_uuids, &folder_uuids).await;
|
||||
|
||||
@@ -184,7 +184,7 @@ async fn handle_filter_files(
|
||||
continue;
|
||||
};
|
||||
let href = nc_href(url_user, subpath);
|
||||
let fid = file_id_map.get(&file.id).copied();
|
||||
let fid = nc_id_of(&file_id_map, &file.id);
|
||||
let oc_id = fid.map(|id| format_oc_id(id, file_id_svc));
|
||||
let dead = dead_props_for(&file.id, &file_deads);
|
||||
write_file_response(
|
||||
@@ -210,7 +210,7 @@ async fn handle_filter_files(
|
||||
continue;
|
||||
};
|
||||
let href = format!("{}/", nc_href(url_user, subpath));
|
||||
let fid = folder_id_map.get(&folder.id).copied();
|
||||
let fid = nc_id_of(&folder_id_map, &folder.id);
|
||||
let oc_id = fid.map(|id| format_oc_id(id, file_id_svc));
|
||||
let dead = dead_props_for(&folder.id, &folder_deads);
|
||||
write_folder_response(
|
||||
@@ -297,8 +297,8 @@ async fn handle_search(
|
||||
// (was one INSERT round-trip per result).
|
||||
let files: Vec<FileDto> = results.files.iter().map(file_dto_from_search).collect();
|
||||
let folders: Vec<FolderDto> = results.folders.iter().map(folder_dto_from_search).collect();
|
||||
let file_uuids: Vec<String> = files.iter().map(|f| f.id.clone()).collect();
|
||||
let folder_uuids: Vec<String> = folders.iter().map(|f| f.id.clone()).collect();
|
||||
let file_uuids: Vec<&str> = files.iter().map(|f| f.id.as_str()).collect();
|
||||
let folder_uuids: Vec<&str> = folders.iter().map(|f| f.id.as_str()).collect();
|
||||
let (file_id_map, folder_id_map) =
|
||||
batch_resolve_ids(file_id_svc, &file_uuids, &folder_uuids).await;
|
||||
|
||||
@@ -325,7 +325,7 @@ async fn handle_search(
|
||||
continue;
|
||||
};
|
||||
let href = nc_href(url_user, subpath);
|
||||
let fid = file_id_map.get(&file.id).copied();
|
||||
let fid = nc_id_of(&file_id_map, &file.id);
|
||||
let oc_id = fid.map(|id| format_oc_id(id, file_id_svc));
|
||||
let dead = dead_props_for(&file.id, &file_deads);
|
||||
write_file_response(
|
||||
@@ -352,7 +352,7 @@ async fn handle_search(
|
||||
continue;
|
||||
};
|
||||
let href = format!("{}/", nc_href(url_user, subpath));
|
||||
let fid = folder_id_map.get(&folder.id).copied();
|
||||
let fid = nc_id_of(&folder_id_map, &folder.id);
|
||||
let oc_id = fid.map(|id| format_oc_id(id, file_id_svc));
|
||||
let dead = dead_props_for(&folder.id, &folder_deads);
|
||||
write_folder_response(
|
||||
|
||||
Reference in New Issue
Block a user