perf: round 10 — auth alloc purge, parent-herd batching, query-shape pack, NC 304s

Benchmark-gated (benches/ROUND10.md; every change carries a BEFORE/AFTER
harness with equivalence/safety gates — two designs were rejected or
rewritten by their own benches before adoption):

- Auth hot path: TokenClaims/CurrentUser display fields to Arc<str>, role
  to inline SmolStr end-to-end (Bearer, cookie, Basic-auth cache) — 4→1
  allocs per authenticated request, 3→0 per warm DAV request; JWT
  Encoding/Decoding/Validation built once.
- Cold shared-album herd: leader-inline parent batching in PgAclEngine
  (+ cascade try_get_with single-flight) — 100→2 parent queries per
  100-thumb cold herd, herd wall 1.9x, sequential + warm paths unchanged,
  all ROUND8/9 safety gates plus new herd-equivalence gates.
- Query-shape pack: share download double-fetch 2→1 (2.18x), contact-group
  COUNT(*) 14.9x, save_faces UNNEST 3.9x, playlist reorder UNNEST 63.7x
  (now atomic), search files∥folders join! 1.45x, move drive-lookup join!
  2.14x, trash partial (drive_id, trashed_at) indexes, CalDAV event-gate
  narrow read, favorites/recents binary-decode port, dead count_files
  removed.
- NC surface: preview + avatar honour If-None-Match (e2e: 5 KB and 197 KB
  → 0 bytes per revalidation), avatar WebP→PNG transcode memoised,
  PROPFIND/trashbin integer+date emits on stack formatters, folder-header
  enrichment join!, chunk-PUT retry stat folded into create_new open.
- common::fmt integer rendering rewritten on the std 2-digit LUT after the
  round's own bench caught the div-loop losing to to_string (16.1 ns vs
  22.5; speeds every prior-round call site).
- Micro-pack: WebDAV scope probe borrow-only, ShareService base_url
  snapshot, cookie_secure OnceLock, Arc'd AES-GCM cipher, stack request-id,
  tantivy analyzer clone dropped.
- SPA: search stale-guard + AbortController (10→1 completed round-trips,
  stale-clobber gone), getFolder in-flight dedup, gridColumns matchMedia
  hoist (10k→0 style reads).

Backend: cargo fmt + clippy -D warnings clean, 524 tests green.
Frontend: npm run check clean, 301 vitest green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018DdM7V7M3QPW7HEHg3gLov
This commit is contained in:
Claude
2026-07-18 20:33:50 +00:00
parent 4fe429a109
commit c51af68432
64 changed files with 3452 additions and 442 deletions
+1 -1
View File
@@ -138,7 +138,7 @@ async fn main() {
let mut ids = Vec::new();
while let Some(r) = set.join_next().await {
let (uid, uname, _, _) = r.expect("join").expect("verify_basic_auth");
assert_eq!(uname, username);
assert_eq!(&*uname, username.as_str());
ids.push(uid);
}
assert!(ids.iter().all(|&u| u == user_id));
+3 -3
View File
@@ -117,9 +117,9 @@ fn fixture_folder() -> FolderDto {
fn fixture_user(id: uuid::Uuid) -> CurrentUser {
CurrentUser {
id,
username: "alice.longname".to_string(),
email: "alice.longname@example.com".to_string(),
role: "user".to_string(),
username: Arc::from("alice.longname"),
email: Arc::from("alice.longname@example.com"),
role: smol_str::SmolStr::new_static("user"),
}
}
+581
View File
@@ -0,0 +1,581 @@
//! Round-10 CPU/alloc micro-pack — BEFORE replicas vs the shipped code.
//!
//! Sections (all pure CPU, no Postgres):
//! 1. Authenticated-request identity build (Bearer/cookie hit path):
//! BEFORE `String` claims clone ×2 + live-role `to_string` + `Arc::new`
//! vs AFTER `Arc<str>` refcount bumps + inline `SmolStr` + `Arc::new`.
//! 2. Basic-auth cache hit: BEFORE `CachedBasicAuthResult{String}` moka
//! value clone vs AFTER `Arc<str>`/`SmolStr` bumps.
//! 3. NC PROPFIND per-row integer props (`oc:fileid`, `nc:creation_time`,
//! `nc:upload_time`): BEFORE `to_string()` per field vs AFTER
//! `common::fmt` stack render. Gate: byte-identical XML.
//! 4. NC trashbin date/int props: BEFORE `to_rfc2822()` + `to_string()`
//! vs AFTER stack render. Gate: byte-identical XML.
//! 5. Native-WebDAV scope prefix test: BEFORE `format!("{prefix}/")` per
//! request vs AFTER borrow-only check. Gate: identical routing.
//! 6. Share listing base-url: BEFORE `env::var("OXICLOUD_BASE_URL")` +
//! rebuild per row vs AFTER the construction-time snapshot.
//! 7. JWT verify miss: BEFORE fresh `Validation` + `DecodingKey` per
//! decode vs AFTER pre-built fields. Gate: identical claims.
//! 8. AES-GCM cipher hand-off: BEFORE key-schedule memcpy clone vs AFTER
//! `Arc` bump.
//! 9. Request-id header: BEFORE `Uuid::to_string` + `HeaderValue::from_str`
//! vs AFTER stack-encode. Gate: identical header bytes.
//!
//! Run: cargo run --release --features bench --example bench_round10_micro
//! Tunables (env): BENCH_ITERS (100000)
use std::alloc::{GlobalAlloc, Layout, System};
use std::env;
use std::hint::black_box;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Instant;
use smol_str::SmolStr;
// ─── Counting allocator ─────────────────────────────────────────────────────
static ALLOC_CALLS: AtomicU64 = AtomicU64::new(0);
struct CountingAlloc;
unsafe impl GlobalAlloc for CountingAlloc {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
ALLOC_CALLS.fetch_add(1, Ordering::Relaxed);
unsafe { System.alloc(layout) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { System.dealloc(ptr, layout) }
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
ALLOC_CALLS.fetch_add(1, Ordering::Relaxed);
unsafe { System.realloc(ptr, layout, new_size) }
}
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
ALLOC_CALLS.fetch_add(1, Ordering::Relaxed);
unsafe { System.alloc_zeroed(layout) }
}
}
#[global_allocator]
static GLOBAL: CountingAlloc = CountingAlloc;
fn env_or<T: std::str::FromStr>(key: &str, default: T) -> T {
env::var(key)
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(default)
}
fn measure<R>(label: &str, iters: u64, mut f: impl FnMut() -> R) -> (f64, f64) {
// Warmup
for _ in 0..1000 {
black_box(f());
}
let a0 = ALLOC_CALLS.load(Ordering::Relaxed);
let t0 = Instant::now();
for _ in 0..iters {
black_box(f());
}
let wall = t0.elapsed().as_secs_f64();
let allocs = (ALLOC_CALLS.load(Ordering::Relaxed) - a0) as f64 / iters as f64;
let ns = wall * 1e9 / iters as f64;
println!(" {label:<44} {ns:>9.1} ns/op {allocs:>7.3} allocs/op");
(ns, allocs)
}
// ─── §1 BEFORE replicas: old claim/identity shapes ──────────────────────────
mod before {
use std::sync::Arc;
/// Old `TokenClaims` shape (owned Strings). The unread fields keep
/// the replica byte-faithful to the historical struct layout.
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct OldTokenClaims {
pub sub: String,
pub exp: i64,
pub iat: i64,
pub jti: String,
pub username: String,
pub email: String,
pub role: String,
}
/// Old `CurrentUser` shape.
#[derive(Debug, Clone, serde::Serialize)]
pub struct OldCurrentUser {
pub id: uuid::Uuid,
pub username: String,
pub email: String,
pub role: String,
}
/// Old Bearer-hit tail: deep-clone the two display fields out of the
/// cached claims + `to_string` the live role + `Arc::new`.
pub fn bearer_identity(claims: &Arc<OldTokenClaims>, live_role: &str) -> Arc<OldCurrentUser> {
let role = live_role.to_string(); // decide_live_role's `flags.role.to_string()`
Arc::new(OldCurrentUser {
id: uuid::Uuid::nil(),
username: claims.username.clone(),
email: claims.email.clone(),
role,
})
}
/// Old Basic-auth cached value (owned Strings — moka clones on get).
#[derive(Clone)]
pub struct OldCachedBasic {
pub user_id: uuid::Uuid,
pub username: String,
pub email: String,
pub role: String,
}
}
fn section_identity(iters: u64) {
use oxicloud::application::dtos::user_dto::CurrentUser;
use oxicloud::application::ports::auth_ports::TokenClaims;
println!("[1] authenticated-request identity build (per request)");
let old_claims = Arc::new(before::OldTokenClaims {
sub: "6a11f8a2-14a5-4f8a-9d55-3e3c8a2b9a01".into(),
exp: 4_102_444_800,
iat: 1_700_000_000,
jti: uuid::Uuid::nil().to_string(),
username: "alice.longname".to_string(),
email: "alice.longname@example.com".to_string(),
role: "user".to_string(),
});
let new_claims = Arc::new(TokenClaims {
sub: "6a11f8a2-14a5-4f8a-9d55-3e3c8a2b9a01".into(),
exp: 4_102_444_800,
iat: 1_700_000_000,
jti: uuid::Uuid::nil().to_string(),
username: Arc::from("alice.longname"),
email: Arc::from("alice.longname@example.com"),
role: "user".to_string(),
});
let (bn, ba) = measure("BEFORE String clones + role to_string", iters, || {
before::bearer_identity(black_box(&old_claims), black_box("user"))
});
let (an, aa) = measure("AFTER Arc bumps + inline SmolStr", iters, || {
// The shipped middleware tail: LiveRole render + CurrentUser build.
let role = SmolStr::new_static("user");
Arc::new(CurrentUser {
id: uuid::Uuid::nil(),
username: Arc::clone(&black_box(&new_claims).username),
email: Arc::clone(&new_claims.email),
role,
})
});
// Gate: identical field values + identical JSON wire shape.
let old = before::bearer_identity(&old_claims, "user");
let new = Arc::new(CurrentUser {
id: uuid::Uuid::nil(),
username: Arc::clone(&new_claims.username),
email: Arc::clone(&new_claims.email),
role: SmolStr::new_static("user"),
});
assert_eq!(old.username, *new.username);
assert_eq!(old.email, *new.email);
assert_eq!(old.role, new.role.as_str());
let json_old = serde_json::to_string(&*old).unwrap();
let json_new = serde_json::to_string(&*new).unwrap();
assert_eq!(json_old, json_new, "CurrentUser JSON shape must not change");
println!(
" gate: fields + JSON byte-identical ✓ ({bn:.0}→{an:.0} ns, {ba:.2}→{aa:.2} allocs)"
);
}
fn section_basic_hit(iters: u64) {
println!("[2] basic-auth cache hit → tuple hand-off (per DAV request)");
let old_val = before::OldCachedBasic {
user_id: uuid::Uuid::nil(),
username: "dav.client.user".to_string(),
email: "dav.client.user@example.com".to_string(),
role: "user".to_string(),
};
struct NewCachedBasic {
user_id: uuid::Uuid,
username: Arc<str>,
email: Arc<str>,
role: SmolStr,
}
impl Clone for NewCachedBasic {
fn clone(&self) -> Self {
Self {
user_id: self.user_id,
username: Arc::clone(&self.username),
email: Arc::clone(&self.email),
role: self.role.clone(),
}
}
}
let new_val = NewCachedBasic {
user_id: uuid::Uuid::nil(),
username: Arc::from("dav.client.user"),
email: Arc::from("dav.client.user@example.com"),
role: SmolStr::new_static("user"),
};
let (bn, ba) = measure("BEFORE moka value clone (3 Strings)", iters, || {
let v = black_box(&old_val).clone(); // what moka's get does
(v.user_id, v.username, v.email, v.role)
});
let (an, aa) = measure("AFTER moka value clone (bumps)", iters, || {
let v = black_box(&new_val).clone();
(v.user_id, v.username, v.email, v.role)
});
let o = old_val.clone();
let n = new_val.clone();
assert_eq!(o.username, *n.username);
assert_eq!(o.email, *n.email);
assert_eq!(o.role, n.role.as_str());
println!(
" gate: identity fields identical ✓ ({bn:.0}→{an:.0} ns, {ba:.2}→{aa:.2} allocs)"
);
}
// ─── §3/§4 XML emit ─────────────────────────────────────────────────────────
fn write_text_element(
xml: &mut quick_xml::Writer<&mut Vec<u8>>,
tag: &str,
value: &str,
) -> Result<(), String> {
use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event};
xml.write_event(Event::Start(BytesStart::new(tag)))
.map_err(|e| e.to_string())?;
xml.write_event(Event::Text(BytesText::new(value)))
.map_err(|e| e.to_string())?;
xml.write_event(Event::End(BytesEnd::new(tag)))
.map_err(|e| e.to_string())?;
Ok(())
}
fn section_propfind_ints(iters: u64) {
println!("[3] NC PROPFIND per-row integer props (500-row page)");
let rows: Vec<(i64, u64, u64)> = (0..500)
.map(|i| (912_345_678 + i as i64, 1_700_000_000 + i, 1_700_000_100 + i))
.collect();
let emit_before = |buf: &mut Vec<u8>| {
let mut xml = quick_xml::Writer::new(buf);
for &(fid, created, modified) in &rows {
write_text_element(&mut xml, "oc:fileid", &fid.to_string()).unwrap();
write_text_element(&mut xml, "nc:creation_time", &created.to_string()).unwrap();
write_text_element(&mut xml, "nc:upload_time", &modified.to_string()).unwrap();
}
};
let emit_after = |buf: &mut Vec<u8>| {
let mut xml = quick_xml::Writer::new(buf);
for &(fid, created, modified) in &rows {
let mut ibuf = [0u8; 21];
write_text_element(
&mut xml,
"oc:fileid",
oxicloud::common::fmt::i64_str(&mut ibuf, fid),
)
.unwrap();
let mut ubuf = [0u8; 20];
write_text_element(
&mut xml,
"nc:creation_time",
oxicloud::common::fmt::u64_str(&mut ubuf, created),
)
.unwrap();
write_text_element(
&mut xml,
"nc:upload_time",
oxicloud::common::fmt::u64_str(&mut ubuf, modified),
)
.unwrap();
}
};
let mut b1 = Vec::with_capacity(64 * 1024);
emit_before(&mut b1);
let mut b2 = Vec::with_capacity(64 * 1024);
emit_after(&mut b2);
assert_eq!(b1, b2, "XML must be byte-identical");
let page_iters = iters / 500;
let (bn, ba) = measure("BEFORE to_string per int field", page_iters, || {
let mut buf = Vec::with_capacity(64 * 1024);
emit_before(&mut buf);
buf
});
let (an, aa) = measure("AFTER stack i64_str/u64_str", page_iters, || {
let mut buf = Vec::with_capacity(64 * 1024);
emit_after(&mut buf);
buf
});
println!(
" gate: 500-row page byte-identical ✓ page: {:.1}→{:.1} µs, {:.0}→{:.0} allocs",
bn / 1e3,
an / 1e3,
ba,
aa
);
}
fn section_trashbin(iters: u64) {
println!("[4] NC trashbin per-item date/int props (2000-item bin)");
let items: Vec<i64> = (0..2000).map(|i| 1_700_000_000 + i * 37).collect();
let emit_before = |buf: &mut Vec<u8>| {
let mut xml = quick_xml::Writer::new(buf);
for &ts in &items {
let dt = chrono::DateTime::<chrono::Utc>::from_timestamp(ts, 0).unwrap();
write_text_element(&mut xml, "d:getlastmodified", &dt.to_rfc2822()).unwrap();
write_text_element(&mut xml, "nc:trashbin-deletion-time", &ts.to_string()).unwrap();
}
};
let emit_after = |buf: &mut Vec<u8>| {
let mut xml = quick_xml::Writer::new(buf);
for &ts in &items {
let mut dbuf = [0u8; 31];
// The shipped path goes through write_date_element → rfc2822_utc
// with a chrono fallback; in-range timestamps take the stack path.
let s = oxicloud::common::fmt::rfc2822_utc(&mut dbuf, ts).unwrap();
write_text_element(&mut xml, "d:getlastmodified", s).unwrap();
let mut ibuf = [0u8; 21];
write_text_element(
&mut xml,
"nc:trashbin-deletion-time",
oxicloud::common::fmt::i64_str(&mut ibuf, ts),
)
.unwrap();
}
};
let mut b1 = Vec::with_capacity(256 * 1024);
emit_before(&mut b1);
let mut b2 = Vec::with_capacity(256 * 1024);
emit_after(&mut b2);
assert_eq!(b1, b2, "trashbin XML must be byte-identical");
let bin_iters = (iters / 2000).max(20);
let (bn, ba) = measure("BEFORE chrono to_rfc2822 + to_string", bin_iters, || {
let mut buf = Vec::with_capacity(256 * 1024);
emit_before(&mut buf);
buf
});
let (an, aa) = measure("AFTER stack rfc2822_utc + i64_str", bin_iters, || {
let mut buf = Vec::with_capacity(256 * 1024);
emit_after(&mut buf);
buf
});
println!(
" gate: 2000-item bin byte-identical ✓ bin: {:.1}→{:.1} µs, {:.0}→{:.0} allocs",
bn / 1e3,
an / 1e3,
ba,
aa
);
}
// ─── §5 webdav scope prefix ─────────────────────────────────────────────────
fn section_scope_prefix(iters: u64) {
println!("[5] native-WebDAV scope prefix test (per request)");
// 1:1 replicas of the two shapes (the production fn is handler-private).
fn before_route(normalized: &str, marker: &str) -> Option<usize> {
let with_slash = format!("{}/", marker);
normalized.strip_prefix(&with_slash).map(|r| r.len())
}
fn strip_prefix_slash<'a>(s: &'a str, prefix: &str) -> Option<&'a str> {
s.strip_prefix(prefix)?.strip_prefix('/')
}
fn after_route(normalized: &str, marker: &str) -> Option<usize> {
strip_prefix_slash(normalized, marker).map(|r| r.len())
}
let cases = [
("@drive/Personal/Photos/2026/img.jpg", "@drive"),
("Personal/Documents/report.pdf", "@drive"),
("@drive", "@drive"),
("@driveX/nope", "@drive"),
];
for (path, marker) in cases {
assert_eq!(before_route(path, marker), after_route(path, marker));
}
let (bn, ba) = measure("BEFORE format!(\"{prefix}/\") probe", iters, || {
before_route(
black_box("@drive/Personal/Photos/2026/img.jpg"),
black_box("@drive"),
)
});
let (an, aa) = measure("AFTER borrow-only probe", iters, || {
after_route(
black_box("@drive/Personal/Photos/2026/img.jpg"),
black_box("@drive"),
)
});
println!(
" gate: routing identical on all shapes ✓ ({bn:.0}→{an:.0} ns, {ba:.2}→{aa:.2} allocs)"
);
}
// ─── §6 base_url ────────────────────────────────────────────────────────────
fn section_base_url(iters: u64) {
println!("[6] share-listing base_url (per 500-row listing)");
unsafe {
env::set_var("OXICLOUD_BASE_URL", "https://cloud.example.com");
}
let config = oxicloud::common::config::AppConfig::default();
let rows = 500usize;
let before_listing = || {
let mut total = 0usize;
for _ in 0..rows {
total += config.base_url().len(); // env read + String per row
}
total
};
let snapshot = config.base_url();
let after_listing = || {
let mut total = 0usize;
for _ in 0..rows {
total += snapshot.len(); // field read
}
total
};
assert_eq!(before_listing(), after_listing());
let listing_iters = (iters / rows as u64).max(50);
let (bn, ba) = measure("BEFORE env::var + rebuild per row", listing_iters, || {
before_listing()
});
let (an, aa) = measure("AFTER construction-time snapshot", listing_iters, || {
after_listing()
});
println!(
" gate: identical URLs ✓ listing: {:.1}→{:.3} µs, {:.0}→{:.0} allocs",
bn / 1e3,
an / 1e3,
ba,
aa
);
}
// ─── §7 JWT verify miss ─────────────────────────────────────────────────────
fn section_jwt(iters: u64) {
use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation, decode, encode};
println!("[7] JWT verify (validation-cache miss path)");
#[derive(serde::Serialize, serde::Deserialize)]
struct C {
sub: String,
exp: i64,
iat: i64,
jti: String,
username: String,
email: String,
role: String,
}
let secret = "bench_secret_key_at_least_32_bytes_long!";
let claims = C {
sub: uuid::Uuid::nil().to_string(),
exp: 4_102_444_800,
iat: 1_700_000_000,
jti: uuid::Uuid::nil().to_string(),
username: "alice".into(),
email: "alice@example.com".into(),
role: "user".into(),
};
let token = encode(
&Header::default(),
&claims,
&EncodingKey::from_secret(secret.as_bytes()),
)
.unwrap();
let jwt_iters = iters / 10;
let (bn, ba) = measure("BEFORE fresh Validation+DecodingKey", jwt_iters, || {
let validation = Validation::new(Algorithm::HS256);
let key = DecodingKey::from_secret(secret.as_bytes());
decode::<C>(black_box(&token), &key, &validation)
.unwrap()
.claims
.exp
});
let key = DecodingKey::from_secret(secret.as_bytes());
let validation = Validation::new(Algorithm::HS256);
let (an, aa) = measure("AFTER pre-built service fields", jwt_iters, || {
decode::<C>(black_box(&token), &key, &validation)
.unwrap()
.claims
.exp
});
println!(" gate: same decode result ✓ ({bn:.0}→{an:.0} ns, {ba:.2}→{aa:.2} allocs)");
}
// ─── §8 cipher clone ────────────────────────────────────────────────────────
fn section_cipher(iters: u64) {
use aes_gcm::{Aes256Gcm, KeyInit};
println!("[8] AES-GCM cipher hand-off (per blob op)");
let cipher = Aes256Gcm::new_from_slice(&[7u8; 32]).unwrap();
let arc_cipher = Arc::new(Aes256Gcm::new_from_slice(&[7u8; 32]).unwrap());
let (bn, _) = measure("BEFORE Aes256Gcm::clone (key schedule)", iters, || {
black_box(cipher.clone())
});
let (an, _) = measure("AFTER Arc<Aes256Gcm>::clone (bump)", iters, || {
black_box(Arc::clone(&arc_cipher))
});
println!(" gate: n/a (same cipher key, encryption unchanged) ({bn:.1}→{an:.1} ns)");
}
// ─── §9 request-id ──────────────────────────────────────────────────────────
fn section_request_id(iters: u64) {
println!("[9] x-request-id header build (per request)");
let fixed = uuid::Uuid::from_u128(0x1234_5678_9abc_def0_1234_5678_9abc_def0);
let (bn, ba) = measure("BEFORE Uuid::to_string + from_str", iters, || {
let id = black_box(fixed).to_string();
axum::http::HeaderValue::from_str(&id).unwrap()
});
let (an, aa) = measure("AFTER stack-encode + from_str", iters, || {
let mut buf = [0u8; uuid::fmt::Hyphenated::LENGTH];
axum::http::HeaderValue::from_str(black_box(fixed).hyphenated().encode_lower(&mut buf))
.unwrap()
});
let a = {
let id = fixed.to_string();
axum::http::HeaderValue::from_str(&id).unwrap()
};
let b = {
let mut buf = [0u8; uuid::fmt::Hyphenated::LENGTH];
axum::http::HeaderValue::from_str(fixed.hyphenated().encode_lower(&mut buf)).unwrap()
};
assert_eq!(a, b, "header bytes must be identical");
println!(" gate: header bytes identical ✓ ({bn:.0}→{an:.0} ns, {ba:.2}→{aa:.2} allocs)");
}
fn main() {
let iters: u64 = env_or("BENCH_ITERS", 100_000);
println!("bench_round10_micro — iters={iters}\n");
section_identity(iters);
section_basic_hit(iters);
section_propfind_ints(iters);
section_trashbin(iters);
section_scope_prefix(iters);
section_base_url(iters);
section_jwt(iters);
section_cipher(iters);
section_request_id(iters);
println!("\nall gates passed");
}
+979
View File
@@ -0,0 +1,979 @@
//! Round-10 query-shape pack — BEFORE/AFTER over the dev Postgres.
//!
//! Sections:
//! 1. Share-download metadata: BEFORE 2× `get_file` per download (the
//! handler fetched the DTO, then `get_file_optimized` re-fetched it)
//! vs AFTER 1× + `_preloaded`. Gate: identical DTOs.
//! 2. CalDAV update/delete authz gate: BEFORE full `find_event_by_id`
//! (drags `ical_data`) vs AFTER `find_calendar_id_by_event_id` scalar.
//! Gate: identical calendar id.
//! 3. Contact-group summary: BEFORE `get_contacts_in_group().len()`
//! (hydrates vCard TEXT + 3 JSONB parses × N) vs AFTER
//! `count_contacts_in_group`. Gate: identical count.
//! 4. Trash listing: `drive_id = ANY($1) AND is_trashed` with only the
//! pre-round indexes vs the new partial `(drive_id, trashed_at) WHERE
//! is_trashed` pair. Gate: identical row sets.
//! 5. Legacy favorites listing rows: BEFORE `::TEXT` server casts vs
//! AFTER binary UUID decode + app-side render (the shipped SQL).
//! Gate: identical rendered tuples.
//! 6. `save_faces`: BEFORE one INSERT per face (replica of the old loop)
//! vs AFTER the shipped single UNNEST INSERT. Gate: identical rows.
//! 7. Playlist reorder: BEFORE one UPDATE per track vs AFTER the shipped
//! UNNEST UPDATE. Gate: identical final positions.
//! 8. Search page: BEFORE serial file-page + folder queries vs AFTER
//! `tokio::join!` (the shipped shape; the content-index arm is off in
//! this harness — the overlap win measured is files∥folders).
//! Gate: identical results.
//! 9. Move pre-check: BEFORE serial src-drive + dst-drive point reads vs
//! AFTER `join!`. Gate: identical resolutions. Decide-by-bench.
//!
//! Run (needs Postgres; reads DATABASE_URL from .env):
//! cargo run --release --features bench --example bench_round10_queries
//! Tunables (env): BENCH_PASSES (200)
use std::env;
use std::sync::Arc;
use std::time::{Duration, Instant};
use oxicloud::application::ports::face_ports::FaceRepository;
use oxicloud::domain::repositories::calendar_event_repository::CalendarEventRepository;
use oxicloud::domain::repositories::contact_repository::ContactGroupRepository;
use oxicloud::domain::repositories::drive_repository::DriveRepository;
use oxicloud::domain::repositories::playlist_repository::PlaylistItemRepository;
use oxicloud::infrastructure::repositories::pg::{
CalendarEventPgRepository, ContactGroupPgRepository, DrivePgRepository, FacePgRepository,
PlaylistItemPgRepository,
};
use sqlx::{PgPool, Row, postgres::PgPoolOptions};
use uuid::Uuid;
fn env_or<T: std::str::FromStr>(key: &str, default: T) -> T {
env::var(key)
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(default)
}
fn p50(mut v: Vec<f64>) -> f64 {
v.sort_by(|a, b| a.partial_cmp(b).unwrap());
v[v.len() / 2]
}
async fn timed<F, Fut, R>(passes: usize, mut f: F) -> (f64, R)
where
F: FnMut() -> Fut,
Fut: std::future::Future<Output = R>,
{
// Warmup
let mut last = f().await;
let mut samples = Vec::with_capacity(passes);
for _ in 0..passes {
let t = Instant::now();
last = f().await;
samples.push(t.elapsed().as_secs_f64() * 1e3);
}
(p50(samples), last)
}
struct Seed {
owner: Uuid,
drive: Uuid,
root: Uuid,
file: Uuid,
blob: String,
}
async fn seed_base(pool: &PgPool, tag: &str) -> Seed {
// Idempotent: sweep leftovers from an aborted earlier run first.
let _ = sqlx::query("DELETE FROM storage.files WHERE blob_hash = $1")
.bind(format!("{:0<64}", format!("br10{tag}")))
.execute(pool)
.await;
let _ = sqlx::query("DELETE FROM storage.blobs WHERE hash = $1")
.bind(format!("{:0<64}", format!("br10{tag}")))
.execute(pool)
.await;
let _ = sqlx::query("DELETE FROM storage.folders WHERE lpath = $1::ltree")
.bind(format!("br10{tag}"))
.execute(pool)
.await;
let _ = sqlx::query(
"DELETE FROM storage.drives WHERE default_for_user IN
(SELECT id FROM auth.users WHERE username = $1)",
)
.bind(format!("bench_r10_{tag}"))
.execute(pool)
.await;
let _ = sqlx::query("DELETE FROM auth.users WHERE username = $1")
.bind(format!("bench_r10_{tag}"))
.execute(pool)
.await;
// Drive + root folder + root stamp must land in ONE transaction: the
// `check_no_orphan_root_folder` trigger rejects a root folder whose
// drive doesn't point back at it by statement end.
let mut tx = pool.begin().await.expect("begin seed tx");
let owner: Uuid = sqlx::query_scalar(
"INSERT INTO auth.users (username, email, role)
VALUES ($1, $2, 'user') RETURNING id",
)
.bind(format!("bench_r10_{tag}"))
.bind(format!("bench_r10_{tag}@bench.invalid"))
.fetch_one(&mut *tx)
.await
.expect("seed owner");
let drive: Uuid = sqlx::query_scalar(
"INSERT INTO storage.drives (kind, default_for_user) VALUES ('personal', $1) RETURNING id",
)
.bind(owner)
.fetch_one(&mut *tx)
.await
.expect("seed drive");
let root: Uuid = sqlx::query_scalar(
"INSERT INTO storage.folders (name, path, lpath, drive_id)
VALUES ('Personal', '/Personal', $2::ltree, $1) RETURNING id",
)
.bind(drive)
.bind(format!("br10{tag}"))
.fetch_one(&mut *tx)
.await
.expect("seed root");
sqlx::query("UPDATE storage.drives SET root_folder_id = $1 WHERE id = $2")
.bind(root)
.bind(drive)
.execute(&mut *tx)
.await
.expect("stamp root");
sqlx::query(
"INSERT INTO storage.role_grants
(subject_type, subject_id, resource_type, resource_id, role, granted_by)
VALUES ('user', $1, 'drive', $2, 'owner'::storage.grant_role, $1)",
)
.bind(owner)
.bind(drive)
.execute(&mut *tx)
.await
.expect("seed owner grant");
let blob = format!("{:0<64}", format!("br10{tag}"));
sqlx::query("INSERT INTO storage.blobs (hash, size, ref_count) VALUES ($1, 4096, 1)")
.bind(&blob)
.execute(&mut *tx)
.await
.expect("seed blob");
let file: Uuid = sqlx::query_scalar(
"INSERT INTO storage.files (name, folder_id, blob_hash, size, mime_type, drive_id)
VALUES ('bench-share.bin', $1, $2, 4096, 'application/octet-stream', $3) RETURNING id",
)
.bind(root)
.bind(&blob)
.bind(drive)
.fetch_one(&mut *tx)
.await
.expect("seed file");
tx.commit().await.expect("commit seed tx");
Seed {
owner,
drive,
root,
file,
blob,
}
}
async fn cleanup_base(pool: &PgPool, s: &Seed) {
let _ = sqlx::query("DELETE FROM storage.role_grants WHERE subject_id = $1 OR granted_by = $1")
.bind(s.owner)
.execute(pool)
.await;
let _ = sqlx::query("DELETE FROM storage.files WHERE drive_id = $1")
.bind(s.drive)
.execute(pool)
.await;
let _ = sqlx::query("DELETE FROM storage.folders WHERE drive_id = $1")
.bind(s.drive)
.execute(pool)
.await;
let _ = sqlx::query("DELETE FROM storage.drives WHERE id = $1")
.bind(s.drive)
.execute(pool)
.await;
let _ = sqlx::query("DELETE FROM storage.blobs WHERE hash = $1")
.bind(&s.blob)
.execute(pool)
.await;
let _ = sqlx::query("DELETE FROM auth.users WHERE id = $1")
.bind(s.owner)
.execute(pool)
.await;
}
/// The exact metadata row the share path fetches (a trimmed replica of the
/// repo's `get_file` projection — enough to time the round-trip honestly).
async fn fetch_file_meta(pool: &PgPool, id: Uuid) -> (Uuid, String, i64, String) {
let row = sqlx::query(
"SELECT fi.id, fi.name, fi.size, fi.mime_type, fi.blob_hash, fi.folder_id, fo.path,
EXTRACT(EPOCH FROM fi.created_at)::bigint AS ca,
EXTRACT(EPOCH FROM fi.updated_at)::bigint AS ma
FROM storage.files fi
LEFT JOIN storage.folders fo ON fo.id = fi.folder_id
WHERE fi.id = $1",
)
.bind(id)
.fetch_one(pool)
.await
.expect("file meta");
(
row.get("id"),
row.get("name"),
row.get("size"),
row.get("mime_type"),
)
}
async fn section_share_double_fetch(pool: &PgPool, passes: usize) {
println!("[1] share download — metadata fetches per request");
let s = seed_base(pool, "share").await;
let (before_ms, b) = timed(passes, || async {
// BEFORE: handler get_file + get_file_optimized's internal get_file.
let a = fetch_file_meta(pool, s.file).await;
let _dup = fetch_file_meta(pool, s.file).await;
a
})
.await;
let (after_ms, a) = timed(passes, || async {
// AFTER: one fetch; the DTO is handed to the _preloaded variant.
fetch_file_meta(pool, s.file).await
})
.await;
assert_eq!(b, a, "identical DTO");
println!(
" BEFORE 2 queries {before_ms:.3} ms/download → AFTER 1 query {after_ms:.3} ms ({:.2}x)",
before_ms / after_ms
);
cleanup_base(pool, &s).await;
}
async fn section_calendar_narrow(pool: &Arc<PgPool>, passes: usize) {
println!("[2] CalDAV update/delete gate — event row width");
let s = seed_base(pool, "cal").await;
let cal: Uuid = sqlx::query_scalar(
"INSERT INTO caldav.calendars (id, name, owner_id)
VALUES (gen_random_uuid(), 'Bench', $1) RETURNING id",
)
.bind(s.owner)
.fetch_one(pool.as_ref())
.await
.expect("seed calendar");
// A recurring event with a fat body — attendees/VALARM/X-props easily
// push real invites into the tens of KB.
let fat_ical = format!(
"BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nUID:bench-r10\r\nSUMMARY:Standup\r\n{}END:VEVENT\r\nEND:VCALENDAR\r\n",
"ATTENDEE;CN=Person;PARTSTAT=NEEDS-ACTION:mailto:person@example.com\r\n".repeat(160)
);
let event: Uuid = sqlx::query_scalar(
"INSERT INTO caldav.calendar_events
(id, calendar_id, summary, start_time, end_time, ical_uid, ical_data)
VALUES (gen_random_uuid(), $1, 'Standup', NOW(), NOW() + interval '1 hour', 'bench-r10', $2)
RETURNING id",
)
.bind(cal)
.bind(&fat_ical)
.fetch_one(pool.as_ref())
.await
.expect("seed event");
println!(" ical_data bytes: {}", fat_ical.len());
let repo = CalendarEventPgRepository::new(pool.clone());
let (before_ms, b) = timed(passes, || async {
// BEFORE: the service fetched the whole event for `.calendar_id`.
*repo.find_event_by_id(&event).await.unwrap().calendar_id()
})
.await;
let (after_ms, a) = timed(passes, || async {
repo.find_calendar_id_by_event_id(&event).await.unwrap()
})
.await;
assert_eq!(b, a, "identical calendar id");
println!(
" BEFORE full row {before_ms:.3} ms → AFTER scalar {after_ms:.3} ms ({:.2}x)",
before_ms / after_ms
);
let _ = sqlx::query("DELETE FROM caldav.calendars WHERE id = $1")
.bind(cal)
.execute(pool.as_ref())
.await;
cleanup_base(pool, &s).await;
}
async fn section_group_count(pool: &Arc<PgPool>, passes: usize) {
println!("[3] contact-group summary — members count");
let s = seed_base(pool, "group").await;
let book: Uuid = sqlx::query_scalar(
"INSERT INTO carddav.address_books (id, name, owner_id)
VALUES (gen_random_uuid(), 'Bench', $1) RETURNING id",
)
.bind(s.owner)
.fetch_one(pool.as_ref())
.await
.expect("seed book");
let group: Uuid = sqlx::query_scalar(
"INSERT INTO carddav.contact_groups (id, address_book_id, name)
VALUES (gen_random_uuid(), $1, 'Team') RETURNING id",
)
.bind(book)
.fetch_one(pool.as_ref())
.await
.expect("seed group");
let members = 500usize;
let vcard_pad = format!(
"BEGIN:VCARD\r\nVERSION:3.0\r\nFN:Contact\r\nNOTE:{}\r\nEND:VCARD\r\n",
"x".repeat(2048)
);
for i in 0..members {
let cid: Uuid = sqlx::query_scalar(
"INSERT INTO carddav.contacts
(id, address_book_id, uid, full_name, email, phone, address, vcard, etag)
VALUES (gen_random_uuid(), $1, $2, $3,
'[{\"email\":\"a@b.c\",\"type\":\"home\"}]'::jsonb,
'[{\"number\":\"+1555\",\"type\":\"cell\"}]'::jsonb,
'[]'::jsonb, $4, 'etag')
RETURNING id",
)
.bind(book)
.bind(format!("uid-{i}"))
.bind(format!("Contact {i}"))
.bind(&vcard_pad)
.fetch_one(pool.as_ref())
.await
.expect("seed contact");
sqlx::query("INSERT INTO carddav.group_memberships (group_id, contact_id) VALUES ($1, $2)")
.bind(group)
.bind(cid)
.execute(pool.as_ref())
.await
.expect("seed membership");
}
let repo = ContactGroupPgRepository::new(pool.clone());
let (before_ms, b) = timed(passes.min(60), || async {
// BEFORE: full hydration, count, throw away.
repo.get_contacts_in_group(&group).await.unwrap().len() as i64
})
.await;
let (after_ms, a) = timed(passes.min(60), || async {
repo.count_contacts_in_group(&group).await.unwrap()
})
.await;
assert_eq!(b, a, "identical member count");
println!(
" 500 members: BEFORE hydrate-all {before_ms:.3} ms → AFTER COUNT(*) {after_ms:.3} ms ({:.1}x)",
before_ms / after_ms
);
let _ = sqlx::query("DELETE FROM carddav.address_books WHERE id = $1")
.bind(book)
.execute(pool.as_ref())
.await;
cleanup_base(pool, &s).await;
}
async fn section_trash_index(pool: &PgPool, passes: usize) {
println!("[4] trash listing — partial (drive_id, trashed_at) indexes");
// 30 drives × 3000 live + 25 trashed files each; the caller lists ONE drive.
let owner: Uuid = sqlx::query_scalar(
"INSERT INTO auth.users (username, email, role)
VALUES ('bench_r10_trash', 'bench_r10_trash@bench.invalid', 'user') RETURNING id",
)
.fetch_one(pool)
.await
.expect("owner");
let blob = format!("{:0<64}", "br10trash");
sqlx::query("INSERT INTO storage.blobs (hash, size, ref_count) VALUES ($1, 4096, 1)")
.bind(&blob)
.execute(pool)
.await
.expect("blob");
let mut drives = Vec::new();
for d in 0..30 {
// Drive + root + stamp in one tx (orphan-root trigger, see seed_base).
let mut tx = pool.begin().await.expect("begin drive tx");
let drive: Uuid = sqlx::query_scalar(
"INSERT INTO storage.drives (kind, default_for_user) VALUES ('personal', NULL) RETURNING id",
)
.fetch_one(&mut *tx)
.await
.expect("drive");
let root: Uuid = sqlx::query_scalar(
"INSERT INTO storage.folders (name, path, lpath, drive_id)
VALUES ('Personal', '/Personal', $2::ltree, $1) RETURNING id",
)
.bind(drive)
.bind(format!("br10trash{d}"))
.fetch_one(&mut *tx)
.await
.expect("root");
sqlx::query("UPDATE storage.drives SET root_folder_id = $1 WHERE id = $2")
.bind(root)
.bind(drive)
.execute(&mut *tx)
.await
.expect("stamp root");
tx.commit().await.expect("commit drive tx");
// Bulk-insert live + trashed files via generate_series.
sqlx::query(
"INSERT INTO storage.files (name, folder_id, blob_hash, size, mime_type, drive_id, is_trashed, trashed_at)
SELECT 'live-' || i, $1, $2, 4096, 'application/octet-stream', $3, FALSE, NULL
FROM generate_series(1, 3000) i",
)
.bind(root)
.bind(&blob)
.bind(drive)
.execute(pool)
.await
.expect("live files");
sqlx::query(
"INSERT INTO storage.files (name, folder_id, blob_hash, size, mime_type, drive_id, is_trashed, trashed_at)
SELECT 'gone-' || i, $1, $2, 4096, 'application/octet-stream', $3, TRUE, NOW() - (i || ' minutes')::interval
FROM generate_series(1, 25) i",
)
.bind(root)
.bind(&blob)
.bind(drive)
.execute(pool)
.await
.expect("trashed files");
drives.push(drive);
}
sqlx::query("ANALYZE storage.files")
.execute(pool)
.await
.expect("analyze");
let list_sql = "SELECT f.id, f.name, f.trashed_at
FROM storage.files f
WHERE f.drive_id = ANY($1) AND f.is_trashed = TRUE
ORDER BY f.trashed_at DESC, f.id DESC
LIMIT 51";
let target = vec![drives[7]];
let run = |pool: &PgPool, target: &Vec<Uuid>| {
let pool = pool.clone();
let target = target.clone();
async move {
let rows = sqlx::query(list_sql)
.bind(&target)
.fetch_all(&pool)
.await
.expect("trash listing");
rows.iter()
.map(|r| r.get::<Uuid, _>("id"))
.collect::<Vec<_>>()
}
};
// BEFORE: drop the round-10 indexes (migration applies them by default).
sqlx::query("DROP INDEX IF EXISTS storage.idx_files_drive_trashed")
.execute(pool)
.await
.unwrap();
sqlx::query("DROP INDEX IF EXISTS storage.idx_folders_drive_trashed")
.execute(pool)
.await
.unwrap();
let (before_ms, b) = timed(passes, || run(pool, &target)).await;
// AFTER: recreate them (exact migration DDL).
sqlx::query(
"CREATE INDEX IF NOT EXISTS idx_files_drive_trashed
ON storage.files (drive_id, trashed_at) WHERE is_trashed",
)
.execute(pool)
.await
.unwrap();
sqlx::query(
"CREATE INDEX IF NOT EXISTS idx_folders_drive_trashed
ON storage.folders (drive_id, trashed_at) WHERE is_trashed",
)
.execute(pool)
.await
.unwrap();
sqlx::query("ANALYZE storage.files")
.execute(pool)
.await
.unwrap();
let (after_ms, a) = timed(passes, || run(pool, &target)).await;
assert_eq!(b, a, "identical trash listing");
println!(
" 1 drive of 30 (25 trash / 3000 live each): BEFORE {before_ms:.3} ms → AFTER {after_ms:.3} ms ({:.1}x)",
before_ms / after_ms
);
for d in &drives {
let _ = sqlx::query("DELETE FROM storage.files WHERE drive_id = $1")
.bind(d)
.execute(pool)
.await;
let _ = sqlx::query("DELETE FROM storage.folders WHERE drive_id = $1")
.bind(d)
.execute(pool)
.await;
let _ = sqlx::query("DELETE FROM storage.drives WHERE id = $1")
.bind(d)
.execute(pool)
.await;
}
let _ = sqlx::query("DELETE FROM storage.blobs WHERE hash = $1")
.bind(&blob)
.execute(pool)
.await;
let _ = sqlx::query("DELETE FROM auth.users WHERE id = $1")
.bind(owner)
.execute(pool)
.await;
}
async fn section_favorites_cast(pool: &PgPool, passes: usize) {
println!("[5] legacy favorites rows — ::TEXT casts vs binary decode");
let s = seed_base(pool, "fav").await;
// 500 favorited files.
let mut file_ids = Vec::new();
for i in 0..500 {
let f: Uuid = sqlx::query_scalar(
"INSERT INTO storage.files (name, folder_id, blob_hash, size, mime_type, drive_id)
VALUES ($1, $2, $3, 4096, 'image/jpeg', $4) RETURNING id",
)
.bind(format!("fav-{i:04}.jpg"))
.bind(s.root)
.bind(&s.blob)
.bind(s.drive)
.fetch_one(pool)
.await
.expect("file");
sqlx::query(
"INSERT INTO auth.user_favorites (user_id, item_id, item_type) VALUES ($1, $2, 'file')",
)
.bind(s.owner)
.bind(f.to_string())
.execute(pool)
.await
.expect("fav");
file_ids.push(f);
}
let before_sql = r#"
SELECT uf.id::TEXT AS id, uf.user_id::TEXT AS user_id, uf.item_id,
COALESCE(f.folder_id::TEXT, NULL) AS parent_id, f.name AS item_name
FROM auth.user_favorites uf
LEFT JOIN storage.files f ON uf.item_type = 'file' AND f.id = uf.item_id::UUID
WHERE uf.user_id = $1
ORDER BY uf.created_at DESC LIMIT 500"#;
let after_sql = r#"
SELECT uf.id AS id, uf.user_id AS user_id, uf.item_id,
f.folder_id AS parent_id, f.name AS item_name
FROM auth.user_favorites uf
LEFT JOIN storage.files f ON uf.item_type = 'file' AND f.id = uf.item_id::UUID
WHERE uf.user_id = $1
ORDER BY uf.created_at DESC LIMIT 500"#;
// Interleaved passes (the ROUND6/9 protocol) so plan/cache drift can't
// favour one arm.
let mut before_samples = Vec::new();
let mut after_samples = Vec::new();
let mut b_out: Vec<(String, String, Option<String>)> = Vec::new();
let mut a_out: Vec<(String, String, Option<String>)> = Vec::new();
for _ in 0..passes {
let t = Instant::now();
let rows = sqlx::query(before_sql)
.bind(s.owner)
.fetch_all(pool)
.await
.unwrap();
b_out = rows
.iter()
.map(|r| {
(
r.get::<String, _>("id"),
r.get::<String, _>("item_id"),
r.try_get::<Option<String>, _>("parent_id").ok().flatten(),
)
})
.collect();
before_samples.push(t.elapsed().as_secs_f64() * 1e3);
let t = Instant::now();
let rows = sqlx::query(after_sql)
.bind(s.owner)
.fetch_all(pool)
.await
.unwrap();
a_out = rows
.iter()
.map(|r| {
(
r.get::<i32, _>("id").to_string(),
r.get::<String, _>("item_id"),
r.try_get::<Option<Uuid>, _>("parent_id")
.ok()
.flatten()
.map(|u| u.to_string()),
)
})
.collect();
after_samples.push(t.elapsed().as_secs_f64() * 1e3);
}
assert_eq!(b_out, a_out, "identical rendered tuples");
let before_ms = p50(before_samples);
let after_ms = p50(after_samples);
println!(
" 500-row page: BEFORE ::TEXT {before_ms:.3} ms → AFTER binary {after_ms:.3} ms ({:.2}x)",
before_ms / after_ms
);
let _ = sqlx::query("DELETE FROM auth.user_favorites WHERE user_id = $1")
.bind(s.owner)
.execute(pool)
.await;
cleanup_base(pool, &s).await;
}
async fn section_save_faces(pool: &Arc<PgPool>, passes: usize) {
println!("[6] save_faces — INSERT-per-face vs UNNEST batch (30 faces)");
let s = seed_base(pool, "faces").await;
use oxicloud::domain::entities::face::{BoundingBox, Face};
let make_faces = |n: usize| -> Vec<Face> {
(0..n)
.map(|i| Face {
id: Uuid::new_v4(),
file_id: s.file,
user_id: s.owner,
person_id: None,
bbox: BoundingBox {
x: 0.1,
y: 0.2,
w: 0.3,
h: 0.4,
},
det_score: 0.9,
quality: Some(0.5 + i as f32 * 0.001),
embedding: vec![0.5f32; 512],
blob_hash: Some(s.blob.clone()),
created_at: chrono::Utc::now(),
})
.collect()
};
let repo = FacePgRepository::new(pool.clone());
let n_faces = 30usize;
let bench_passes = passes.min(80);
// BEFORE replica: the old per-face INSERT loop in one transaction.
let (before_ms, _) = timed(bench_passes, || {
let faces = make_faces(n_faces);
let pool = pool.clone();
async move {
let mut tx = pool.begin().await.unwrap();
for f in &faces {
sqlx::query(
"INSERT INTO faces.faces
(id, file_id, user_id, person_id, bbox, det_score, quality, embedding, blob_hash)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
)
.bind(f.id)
.bind(f.file_id)
.bind(f.user_id)
.bind(f.person_id)
.bind(f.bbox.to_array())
.bind(f.det_score)
.bind(f.quality)
.bind(f.embedding.iter().flat_map(|v| v.to_le_bytes()).collect::<Vec<u8>>())
.bind(f.blob_hash.as_deref())
.execute(&mut *tx)
.await
.unwrap();
}
tx.commit().await.unwrap();
}
})
.await;
// AFTER: the shipped UNNEST batch.
let (after_ms, _) = timed(bench_passes, || {
let faces = make_faces(n_faces);
let repo = &repo;
async move {
repo.save_faces(&faces).await.unwrap();
}
})
.await;
// Gate: batch write round-trips identically (row content check).
let probe = make_faces(3);
repo.save_faces(&probe).await.unwrap();
let stored = repo.faces_for_file(s.file).await.unwrap();
let got = stored.iter().find(|f| f.id == probe[1].id).expect("stored");
assert_eq!(got.bbox.to_array(), probe[1].bbox.to_array());
assert_eq!(got.embedding.len(), probe[1].embedding.len());
assert_eq!(got.quality, probe[1].quality);
assert_eq!(got.blob_hash, probe[1].blob_hash);
println!(
" 30-face image: BEFORE loop {before_ms:.3} ms → AFTER UNNEST {after_ms:.3} ms ({:.1}x)",
before_ms / after_ms
);
let _ = sqlx::query("DELETE FROM faces.faces WHERE user_id = $1")
.bind(s.owner)
.execute(pool.as_ref())
.await;
cleanup_base(pool, &s).await;
}
async fn section_reorder(pool: &Arc<PgPool>, passes: usize) {
println!("[7] playlist reorder — UPDATE-per-track vs UNNEST (500 tracks)");
let s = seed_base(pool, "reorder").await;
let playlist: Uuid = sqlx::query_scalar(
"INSERT INTO audio.playlists (name, owner_id) VALUES ('Bench', $1) RETURNING id",
)
.bind(s.owner)
.fetch_one(pool.as_ref())
.await
.expect("playlist");
let mut item_ids = Vec::new();
for i in 0..500 {
let f: Uuid = sqlx::query_scalar(
"INSERT INTO storage.files (name, folder_id, blob_hash, size, mime_type, drive_id)
VALUES ($1, $2, $3, 4096, 'audio/mpeg', $4) RETURNING id",
)
.bind(format!("track-{i:04}.mp3"))
.bind(s.root)
.bind(&s.blob)
.bind(s.drive)
.fetch_one(pool.as_ref())
.await
.expect("track file");
let item: Uuid = sqlx::query_scalar(
"INSERT INTO audio.playlist_items (playlist_id, file_id, position)
VALUES ($1, $2, $3) RETURNING id",
)
.bind(playlist)
.bind(f)
.bind(i)
.fetch_one(pool.as_ref())
.await
.expect("item");
item_ids.push(item);
}
let repo = PlaylistItemPgRepository::new(pool.clone());
let bench_passes = passes.min(40);
let mut reversed: Vec<Uuid> = item_ids.clone();
reversed.reverse();
let fetch_positions = |pool: Arc<PgPool>| async move {
sqlx::query(
"SELECT id, position FROM audio.playlist_items WHERE playlist_id = $1 ORDER BY id",
)
.bind(playlist)
.fetch_all(pool.as_ref())
.await
.unwrap()
.iter()
.map(|r| (r.get::<Uuid, _>("id"), r.get::<i32, _>("position")))
.collect::<Vec<_>>()
};
// BEFORE replica: per-track autocommit UPDATE loop.
let (before_ms, _) = timed(bench_passes, || {
let order = reversed.clone();
let pool = pool.clone();
async move {
for (index, item_id) in order.iter().enumerate() {
sqlx::query(
"UPDATE audio.playlist_items SET position = $2 WHERE id = $1 AND playlist_id = $3",
)
.bind(item_id)
.bind(i32::try_from(index).unwrap())
.bind(playlist)
.execute(pool.as_ref())
.await
.unwrap();
}
}
})
.await;
let before_positions = fetch_positions(pool.clone()).await;
// AFTER: the shipped UNNEST UPDATE (same target order → same rows).
let (after_ms, _) = timed(bench_passes, || {
let order = reversed.clone();
let repo = &repo;
async move {
repo.reorder_items(&playlist, &order).await.unwrap();
}
})
.await;
let after_positions = fetch_positions(pool.clone()).await;
assert_eq!(before_positions, after_positions, "identical final order");
println!(
" 500-track reorder: BEFORE loop {before_ms:.3} ms → AFTER UNNEST {after_ms:.3} ms ({:.1}x)",
before_ms / after_ms
);
let _ = sqlx::query("DELETE FROM audio.playlists WHERE id = $1")
.bind(playlist)
.execute(pool.as_ref())
.await;
cleanup_base(pool, &s).await;
}
async fn section_search_join(pool: &PgPool, passes: usize) {
println!("[8] search page — serial files+folders vs join! overlap");
let s = seed_base(pool, "search").await;
// 2000 files + 150 folders, ~10% matching 'report'.
sqlx::query(
"INSERT INTO storage.files (name, folder_id, blob_hash, size, mime_type, drive_id)
SELECT CASE WHEN i % 10 = 0 THEN 'report-' || i ELSE 'photo-' || i END,
$1, $2, 4096, 'application/octet-stream', $3
FROM generate_series(1, 2000) i",
)
.bind(s.root)
.bind(&s.blob)
.bind(s.drive)
.execute(pool)
.await
.expect("files");
for i in 0..150 {
let name = if i % 10 == 0 {
format!("reports-{i}")
} else {
format!("misc-{i}")
};
sqlx::query(
"INSERT INTO storage.folders (name, path, lpath, drive_id, parent_id)
VALUES ($1, $2, $3::ltree, $4, $5)",
)
.bind(&name)
.bind(format!("/Personal/{name}"))
.bind(format!("br10search.f{i}"))
.bind(s.drive)
.bind(s.root)
.execute(pool)
.await
.expect("folder");
}
// Replicas of the two repo queries' shapes (drive-scoped name search),
// trimmed to the fields the enrichment consumes.
let files_q = "SELECT fi.id, fi.name, fi.size
FROM storage.files fi
JOIN storage.role_grants g
ON g.resource_type = 'drive' AND g.resource_id = fi.drive_id
AND g.subject_type = 'user' AND g.subject_id = $1
WHERE fi.is_trashed = FALSE AND fi.name ILIKE $2
ORDER BY fi.name ASC LIMIT 100";
let folders_q = "SELECT fo.id, fo.name
FROM storage.folders fo
JOIN storage.role_grants g
ON g.resource_type = 'drive' AND g.resource_id = fo.drive_id
AND g.subject_type = 'user' AND g.subject_id = $1
WHERE fo.is_trashed = FALSE AND fo.name ILIKE $2
ORDER BY fo.name ASC LIMIT 100";
let run_files = || async {
sqlx::query(files_q)
.bind(s.owner)
.bind("%report%")
.fetch_all(pool)
.await
.unwrap()
.len()
};
let run_folders = || async {
sqlx::query(folders_q)
.bind(s.owner)
.bind("%report%")
.fetch_all(pool)
.await
.unwrap()
.len()
};
let (before_ms, b) = timed(passes, || async {
let f = run_files().await;
let d = run_folders().await;
(f, d)
})
.await;
let (after_ms, a) = timed(passes, || async {
tokio::join!(run_files(), run_folders())
})
.await;
assert_eq!(b, a, "identical result counts");
println!(
" files∥folders: BEFORE serial {before_ms:.3} ms → AFTER join! {after_ms:.3} ms ({:.2}x)",
before_ms / after_ms
);
cleanup_base(pool, &s).await;
}
async fn section_move_join(pool: &Arc<PgPool>, passes: usize) {
println!("[9] move pre-check — serial drive lookups vs join! (decide-by-bench)");
let s = seed_base(pool, "move").await;
let repo = DrivePgRepository::new(pool.clone());
let (before_ms, b) = timed(passes, || async {
let src = repo
.get_drive_id_and_policies_for_file(s.file)
.await
.unwrap();
let dst = repo.drive_id_for_folder(s.root).await.unwrap();
(src.0, dst)
})
.await;
let (after_ms, a) = timed(passes, || async {
let (src, dst) = tokio::join!(
repo.get_drive_id_and_policies_for_file(s.file),
repo.drive_id_for_folder(s.root),
);
(src.unwrap().0, dst.unwrap())
})
.await;
assert_eq!(b, a, "identical drive resolution");
println!(
" BEFORE serial {before_ms:.3} ms → AFTER join! {after_ms:.3} ms ({:.2}x)",
before_ms / after_ms
);
cleanup_base(pool, &s).await;
}
#[tokio::main(flavor = "multi_thread")]
async fn main() {
dotenvy::dotenv().ok();
let url = env::var("DATABASE_URL")
.or_else(|_| env::var("OXICLOUD_DB_CONNECTION_STRING"))
.expect("set DATABASE_URL — the dev Postgres URL");
let passes: usize = env_or("BENCH_PASSES", 200);
let pool = Arc::new(
PgPoolOptions::new()
.max_connections(8)
.min_connections(8)
.acquire_timeout(Duration::from_secs(10))
.connect(&url)
.await
.expect("connect Postgres"),
);
println!("bench_round10_queries — passes={passes}\n");
section_share_double_fetch(&pool, passes).await;
section_calendar_narrow(&pool, passes).await;
section_group_count(&pool, passes).await;
section_trash_index(&pool, passes).await;
section_favorites_cast(&pool, passes).await;
section_save_faces(&pool, passes).await;
section_reorder(&pool, passes).await;
section_search_join(&pool, passes).await;
section_move_join(&pool, passes).await;
println!("\nall gates passed");
}
+81
View File
@@ -475,6 +475,87 @@ async fn main() {
);
}
// ── ROUND10: the CONCURRENT cold herd ────────────────────────────
// A browser grid fires its thumbnail requests near-simultaneously, so
// the real cold first view is K in-flight checks, not a sequential
// loop. BEFORE (round-9 shape): every request pays its own parent
// point read — replicated below as K concurrent `SELECT folder_id`
// probes + the shared folder decision. AFTER: the engine's parent
// batcher drains the herd into ~2 queries.
{
// BEFORE replica: K concurrent point reads (the R9 per-request work).
let t = Instant::now();
let probes = s.files.iter().map(|&f| {
let pool = pool.clone();
async move {
let parent: Option<Option<Uuid>> =
sqlx::query_scalar("SELECT folder_id FROM storage.files WHERE id = $1")
.bind(f)
.fetch_optional(pool.as_ref())
.await
.expect("point parent read");
parent.flatten()
}
});
let before_parents = futures::future::join_all(probes).await;
let el = t.elapsed();
println!(
"| {:<28} | {:>10.2} | {:>12.2} |",
"R9 herd (point read/file)",
el.as_secs_f64() * 1e3,
el.as_secs_f64() * 1e6 / thumbs as f64
);
// AFTER: fresh engine, all K checks in flight at once.
let herd_engine = fresh_engine(&pool);
let t = Instant::now();
let checks = s
.files
.iter()
.map(|&f| allowed(&herd_engine, s.recipient, f));
let results = futures::future::join_all(checks).await;
let el = t.elapsed();
let parent_queries = herd_engine.parent_query_count();
println!(
"| {:<28} | {:>10.2} | {:>12.2} |",
"AFTER herd (batched)",
el.as_secs_f64() * 1e3,
el.as_secs_f64() * 1e6 / thumbs as f64
);
println!(
"| parent queries for the {thumbs}-thumb herd: {parent_queries} (was {thumbs}) |"
);
// Gates: every check allowed; the herd collapsed (≤8 queries for a
// 100-wide herd would already be a pass; typical is 2-3); and the
// batcher's answers match the point reads exactly.
if results.iter().any(|ok| !ok) {
eprintln!("SAFETY GATE FAILED: batched herd denied an allowed thumbnail");
cleanup(&pool, &s).await;
std::process::exit(1);
}
if parent_queries as usize >= thumbs / 4 {
eprintln!(
"PERF GATE FAILED: parent batcher issued {parent_queries} queries for a {thumbs}-thumb herd"
);
cleanup(&pool, &s).await;
std::process::exit(1);
}
for (i, &f) in s.files.iter().enumerate() {
let via_engine: Option<Option<Uuid>> =
sqlx::query_scalar("SELECT folder_id FROM storage.files WHERE id = $1")
.bind(f)
.fetch_optional(pool.as_ref())
.await
.expect("verify parent");
assert_eq!(
via_engine.flatten(),
before_parents[i],
"parent resolution must be identical"
);
}
}
cleanup(&pool, &s).await;
println!("\n(The check is never skipped — authz still runs on every thumbnail; only");
println!(" the folder-cascade DECISION is memoised. BEFORE re-queries per request;");