perf: scale fixes for 1M+ photo libraries

Indexes: share_targets.target_id, duplicate_groups.status,
GIN on stacks members + duplicate candidates JSONB,
composite (owner_user_id, created_at DESC) on assets.

N+1 elimination: batch metadata loading via find_by_assets(ids)
using WHERE asset_id = ANY($1), used in timeline + sidecar export.

Visibility: cache find_targets_for_user per request via OnceCell,
extract filter_visible helper to reduce duplication.

Streaming: FileStoragePort.open_file() returns (DataStream, u64),
LocalFileStorage uses ReaderStream instead of loading full file.
serve_file/serve_derivative use Body::from_stream().

Unbounded queries: sidecar full_export/import batched in 500-row
chunks instead of u32::MAX. find_unresolved paginated with
limit/offset. list_duplicates API accepts pagination params.
This commit is contained in:
2026-05-31 22:40:25 +02:00
parent d879fd6437
commit bcaf49cc81
21 changed files with 263 additions and 123 deletions

View File

@@ -5,6 +5,7 @@ use domain::catalog::entities::{Asset, AssetType, SourceReference};
use domain::errors::DomainError;
use domain::ports::{AssetRepository, FileStoragePort};
use domain::value_objects::{Checksum, SystemId};
use futures::StreamExt;
use std::sync::Arc;
#[tokio::test]
@@ -36,7 +37,9 @@ async fn reads_file_successfully() {
.await
.unwrap();
assert_eq!(result.data, file_data);
let chunks: Vec<Bytes> = result.stream.map(|r| r.unwrap()).collect().await;
let data: Bytes = chunks.into_iter().flatten().collect();
assert_eq!(data, file_data);
assert_eq!(result.mime_type, "image/jpeg");
assert_eq!(result.filename, "cat.jpg");
}