perf: round 12 — auth write-path narrowing, fused quota gate, moka blob-cache index, media single-read, sized listing JSON
Benchmark-gated round (benches/ROUND12.md; every change ships with a BEFORE/AFTER harness + equivalence gates, one candidate rejected by its own bench): DB / query shapes (bench_round12_queries): - NC sharee search: username-only projection instead of the 21-column row (incl. the <=512 KiB avatar) per match, + gin_trgm_ops indexes on auth.users for the leading-wildcard ILIKE (4.98x; 54.7x with index). - Password login: delete the redundant full-row update_user — create_session already stamps last_login_at in its own txn (4.45x per login). - Email-verified stamp: narrow conditional UPDATE (8.9x); OIDC repeat login now compares profile state in memory and issues ZERO queries when nothing changed (was: full 17-column rewrite per login). - Refresh rotation: revoke+insert+stamp fused into one transaction via new rotate_session port method (1.18x). - WOPI CheckFileInfo / authorize_wopi_access: require(Read) + get_file + check(Update) overlapped with tokio::join!, original result precedence (cold 1.34x). - Upload quota gate: user-envelope + drive-cap checks fused into ONE round-trip (check_upload_quotas) — the NC chunked PUT pays this per chunk (1.81x, 2 -> 1 queries/chunk); shared verdict evaluators keep error shapes byte-identical. CPU / allocs (bench_round12_micro): - sized_json: pre-sized listing serialization replacing axum Json's 128 B seed + doubling-realloc chain on files/folder-resources/photos/search responses (1.40x, 13 -> 2 allocs per 500-row page; byte-identical). - Security headers: 4 SetResponseHeaderLayer folded into the CSP middleware pass (5 layers -> 1; 1.43x per request, -26 allocs; header set gated byte-identical incl. 304s). - Media capture-metadata: single-read extraction — nom-exif now parses the buffer kamadak already read (zero-copy Bytes) and videos open once with a kind() dispatch; per-image opens 2-3 -> 1 (1.44x warm geomean, 1.6-3.2x cold cache; extraction outputs gated identical incl. the MIME-mislabel track fallback). - Chunked-upload session ops: owner gate folded into the operation's own DashMap lookup + stack-encoded uuid compare (5 -> 3 lookups, -2 allocs, 1.28x per chunk). Blob cache (bench_blob_cache_index + round-3 regression guard): - CachedBlobBackend index: tokio::sync::Mutex<LruCache> -> moka::sync::Cache with byte weigher. The mutex serialized every cached chunk read and scaled NEGATIVELY (2.08 -> 1.07 Mops/s from 1 -> 2 readers); moka probes are lock-free (2.17x at K=2). Byte budget now enforced by moka (manual current_size + collect_evictions machinery deleted); eviction listener unlinks size-evicted files only (Replaced entries keep their file — gated). Single-flight miss gate unchanged (16 concurrent misses -> 1 fetch re-verified via the round-3 harness). - put_blob now populates the cache BEFORE the inner backend consumes the source file (the old order failed 100% of the time — local renames, S3/Azure delete the source — so the first read after a whole-file put re-downloaded from the remote); inner-put failure invalidates the entry. Frontend (vitest gates): - List-view thumbnails request the 150px icon rendition instead of 400px preview into a 40px slot (~7.1x fewer pixels, ~4-5x fewer bytes per thumbnail across list views); grid keeps preview. Rejected by its own bench (kept as evidence in bench_round12_micro §2): - Single-pass compression predicate: the monomorphized And-chain already costs ~4.6 ns / 0 allocs total; the fused node measured within noise. New migration: 20260719000000_users_search_trgm.sql (trgm indexes). Deferred with prepared design: grouped file/grid view virtualization (single-VirtualRows flatten, the photos pattern) — next round's headline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BfidAJD5AHw23jtvBUNamB
This commit is contained in:
@@ -96,22 +96,33 @@ impl MediaMetadataService {
|
||||
}
|
||||
|
||||
if Self::is_image_file(mime_type) {
|
||||
// ONE disk read: kamadak needs the full buffer anyway, and
|
||||
// nom-exif 3.6+ parses from in-RAM bytes zero-copy
|
||||
// (`MediaSource::from_memory` over the same allocation). This
|
||||
// path used to re-open the file 1-2 more times — nom-exif's
|
||||
// `read_exif(path)` plus a `read_track(path)` fallback for
|
||||
// date-less images (2-3 opens per image, benches/ROUND12.md §M4:
|
||||
// 1.44x warm geomean, 2-3x cold-cache).
|
||||
let buf = std::fs::read(path).ok()?;
|
||||
// Rich EXIF (GPS / camera / orientation / dimensions + naive date)
|
||||
// from the proven kamadak extractor.
|
||||
let kamadak = std::fs::read(path)
|
||||
.ok()
|
||||
.and_then(|b| ExifService::extract(&b));
|
||||
let kamadak = ExifService::extract(&buf);
|
||||
// nom-exif complements kamadak: a timezone-correct capture date and,
|
||||
// crucially, the date + GPS for files kamadak rejects outright
|
||||
// ("Unexpected next IFD"), where `kamadak` is None and the GPS would
|
||||
// otherwise be lost. See `merge_image_metadata`.
|
||||
merge_image_metadata(kamadak, read_nom_exif(path))
|
||||
let bytes = bytes::Bytes::from(buf);
|
||||
merge_image_metadata(kamadak, read_nom_exif_from_bytes(&bytes))
|
||||
} else if Self::is_video_file(mime_type) {
|
||||
// Videos carry no EXIF — pull the container creation time only.
|
||||
read_nom_exif(path).captured_at.map(|dt| ExifMetadata {
|
||||
captured_at: Some(dt),
|
||||
..Default::default()
|
||||
})
|
||||
// Single open + header sniff; the old shape opened twice (a
|
||||
// doomed `read_exif` sniff, then `read_track`).
|
||||
read_nom_exif_video(path)
|
||||
.captured_at
|
||||
.map(|dt| ExifMetadata {
|
||||
captured_at: Some(dt),
|
||||
..Default::default()
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -375,34 +386,49 @@ struct NomExif {
|
||||
/// carries `OffsetTimeOriginal` (or a tz-aware container time); otherwise the
|
||||
/// naive wall-clock is interpreted as UTC. Either way it is converted to a true
|
||||
/// UTC instant. GPS is returned as signed decimal degrees.
|
||||
fn read_nom_exif(path: &Path) -> NomExif {
|
||||
use nom_exif::{EntryValue, ExifTag, TrackInfoTag, read_exif, read_track};
|
||||
fn nom_to_utc(ev: &nom_exif::EntryValue) -> Option<DateTime<Utc>> {
|
||||
let edt = ev.as_datetime()?;
|
||||
let utc0 = FixedOffset::east_opt(0)?;
|
||||
Some(edt.or_offset(utc0).with_timezone(&Utc))
|
||||
}
|
||||
|
||||
// Captures nothing → `Copy`, so it can be reused across the calls below.
|
||||
let to_utc = |ev: &EntryValue| -> Option<DateTime<Utc>> {
|
||||
let edt = ev.as_datetime()?;
|
||||
let utc0 = FixedOffset::east_opt(0)?;
|
||||
Some(edt.or_offset(utc0).with_timezone(&Utc))
|
||||
};
|
||||
fn nom_fill_from_exif(exif: &nom_exif::Exif, out: &mut NomExif) {
|
||||
use nom_exif::ExifTag;
|
||||
out.captured_at = exif
|
||||
.get(ExifTag::DateTimeOriginal)
|
||||
.and_then(nom_to_utc)
|
||||
.or_else(|| exif.get(ExifTag::CreateDate).and_then(nom_to_utc));
|
||||
if let Some(gps) = exif.gps_info() {
|
||||
out.latitude = gps.latitude_decimal();
|
||||
out.longitude = gps.longitude_decimal();
|
||||
}
|
||||
}
|
||||
|
||||
/// Image arm: nom-exif fed from the buffer the kamadak pass already read —
|
||||
/// `MediaSource::from_memory` shares the `Bytes` refcount, so this re-parses
|
||||
/// without touching the disk again (the old shape re-opened the file once,
|
||||
/// plus a second time for date-less images). The track fallback stays (fed
|
||||
/// from the same bytes): it covers MIME-mislabeled rows whose actual
|
||||
/// container is a video — the only case where it ever produced a date.
|
||||
fn read_nom_exif_from_bytes(bytes: &bytes::Bytes) -> NomExif {
|
||||
use nom_exif::{MediaParser, MediaSource, TrackInfoTag};
|
||||
|
||||
let mut out = NomExif::default();
|
||||
let mut parser = MediaParser::new();
|
||||
|
||||
// Images: EXIF DateTimeOriginal → DateTimeDigitized (CreateDate), plus GPS.
|
||||
if let Ok(exif) = read_exif(path) {
|
||||
out.captured_at = exif
|
||||
.get(ExifTag::DateTimeOriginal)
|
||||
.and_then(to_utc)
|
||||
.or_else(|| exif.get(ExifTag::CreateDate).and_then(to_utc));
|
||||
if let Some(gps) = exif.gps_info() {
|
||||
out.latitude = gps.latitude_decimal();
|
||||
out.longitude = gps.longitude_decimal();
|
||||
}
|
||||
if let Ok(ms) = MediaSource::from_memory(bytes.clone())
|
||||
&& let Ok(iter) = parser.parse_exif(ms)
|
||||
{
|
||||
let exif: nom_exif::Exif = iter.into();
|
||||
nom_fill_from_exif(&exif, &mut out);
|
||||
}
|
||||
|
||||
// Videos / audio containers (mov/mp4/mkv): track creation time.
|
||||
if out.captured_at.is_none()
|
||||
&& let Ok(track) = read_track(path)
|
||||
&& let Some(dt) = track.get(TrackInfoTag::CreateDate).and_then(to_utc)
|
||||
&& let Ok(ms) = MediaSource::from_memory(bytes.clone())
|
||||
&& let Ok(track) = parser.parse_track(ms)
|
||||
&& let Some(dt) = track.get(TrackInfoTag::CreateDate).and_then(nom_to_utc)
|
||||
{
|
||||
out.captured_at = Some(dt);
|
||||
}
|
||||
@@ -410,6 +436,42 @@ fn read_nom_exif(path: &Path) -> NomExif {
|
||||
out
|
||||
}
|
||||
|
||||
/// Video arm: ONE open, dispatched on the sniffed container kind. Matches
|
||||
/// the old `read_exif(path)`-then-`read_track(path)` observable behaviour
|
||||
/// exactly — a Track container never parsed as EXIF (the old first open was
|
||||
/// pure waste) and an Image container never parsed as a track, so the
|
||||
/// two-open sequence always reduced to a single effective parse.
|
||||
fn read_nom_exif_video(path: &Path) -> NomExif {
|
||||
use nom_exif::{MediaKind, MediaParser, MediaSource, TrackInfoTag};
|
||||
|
||||
let mut out = NomExif::default();
|
||||
let Ok(file) = std::fs::File::open(path) else {
|
||||
return out;
|
||||
};
|
||||
let Ok(ms) = MediaSource::seekable(file) else {
|
||||
return out;
|
||||
};
|
||||
let mut parser = MediaParser::new();
|
||||
match ms.kind() {
|
||||
MediaKind::Image => {
|
||||
// MIME said video, bytes say image (mislabeled row): same EXIF
|
||||
// extraction the old `read_exif(path)` performed.
|
||||
if let Ok(iter) = parser.parse_exif(ms) {
|
||||
let exif: nom_exif::Exif = iter.into();
|
||||
nom_fill_from_exif(&exif, &mut out);
|
||||
}
|
||||
}
|
||||
MediaKind::Track => {
|
||||
if let Ok(track) = parser.parse_track(ms)
|
||||
&& let Some(dt) = track.get(TrackInfoTag::CreateDate).and_then(nom_to_utc)
|
||||
{
|
||||
out.captured_at = Some(dt);
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Combine kamadak's rich EXIF with nom-exif's date + GPS.
|
||||
///
|
||||
/// nom-exif's tz-correct date wins whenever present; its GPS only fills gaps
|
||||
|
||||
Reference in New Issue
Block a user