perf(round29): cache-serve borrow-probe, NC REPORT href buffer, auth per-req allocs, DB over-fetch

Seven behaviour-preserving allocation / copy / bandwidth cuts, each behind a
counting-allocator BEFORE/AFTER gate that exit(1)s unless AFTER allocates
strictly fewer than BEFORE (benches/ROUND29.md, examples/bench_round29_micro.rs).

- [B] Content-cache serve fast path (optimized_inner Tier 1 +
  get_file_range_preloaded — the video-scrub hot path): probe the cache with a
  borrow first and build the owned get_or_load args (quoted-etag / key / id
  Strings) only on a miss, instead of allocating them before every probe and
  discarding them on a hit. 6 -> 0 allocs per cache hit. Splits get_or_load into
  get + load_and_cache so the miss path is not re-probed and the hit/miss stat
  counters stay byte-identical. Also drops the unconditional content_hash/name
  clones that ran for the >=10 MB streaming tier that used neither.
- [A] NextCloud REPORT emit loops: per-row href String (and format! per folder
  row) -> one reused href_buf via nc_href_into / nc_collection_href_into with the
  URL-encoded user computed once per page. 1497 fewer allocs on a 500-row page.
- [C] read_full: a single-frame blob is returned zero-copy instead of a second
  whole-payload memcpy into a fresh BytesMut; multi-frame path unchanged.
- [D] login-lockout key: to_lowercase()+format! -> one pre-sized ASCII buffer
  (non-ASCII keeps str::to_lowercase). 3 -> 1 alloc/req, byte-identical key.
- [E] NC composite-username parse: owned clone/to_string -> &str borrow of the
  already-owned raw_username. 1 -> 0 alloc on the common no-marker path.
- [F] get_contacts_in_group: stop SELECTing the discarded multi-KB vcard column
  (the live method ROUND25 §Q2 missed; ContactDto has no vcard field).
- [G] count_admin_users: add count_users_by_role -> scalar COUNT(*) instead of
  hydrating every admin's full row (incl. up-to-512 KiB avatar + ui_preferences
  JSONB) only to .len() it, on a bootstrap-polled status endpoint.

All seven gates pass; cargo fmt --check and cargo clippy --all-features
--all-targets -D warnings clean. §F/§G additionally validated against a live
PostgreSQL 16 with the full migration set (query validity, result equivalence,
600000 -> 8 byte wire delta on the admin count).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhpDZxSQTAGnAqCHUdtG5N
This commit is contained in:
Claude
2026-07-21 10:25:36 +00:00
parent b7d5d41c90
commit 8e55caa8a9
14 changed files with 967 additions and 81 deletions
+6
View File
@@ -172,6 +172,12 @@ pub trait UserStoragePort: Send + Sync + 'static {
/// Lists users by role (e.g., "admin" or "user")
async fn list_users_by_role(&self, role: &str) -> Result<Vec<User>, DomainError>;
/// Counts users with a given role WITHOUT hydrating their rows — a scalar
/// `COUNT(*)` instead of fetching every full user row (incl. the up-to-512
/// KiB avatar `image` and the `ui_preferences` JSONB) only to `.len()` them
/// (benches/ROUND29.md §G).
async fn count_users_by_role(&self, role: &str) -> Result<i64, DomainError>;
/// Deletes a user by their ID
async fn delete_user(&self, user_id: Uuid) -> Result<(), DomainError>;
@@ -2118,21 +2118,11 @@ impl AuthApplicationService {
// Method to count how many admin users exist in the system
// Used to determine if we have multiple admins or just the default one
pub async fn count_admin_users(&self) -> Result<i64, DomainError> {
// Use the list_users_by_role method or similar from user_storage port
// For now, we'll use a basic implementation that counts all users with role = "admin"
let admin_users = self
.user_storage
.list_users_by_role("admin")
.await
.map_err(|e| {
DomainError::new(
ErrorKind::InternalError,
"User",
format!("Error counting admin users: {}", e),
)
})?;
Ok(admin_users.len() as i64)
// Scalar COUNT(*) — the old form fetched every admin's FULL row (incl.
// the up-to-512 KiB avatar `image` + `ui_preferences` JSONB) only to
// call `.len()`, on a status/init endpoint that is polled at bootstrap
// (benches/ROUND29.md §G).
self.user_storage.count_users_by_role("admin").await
}
/// Lists internal users only. External (grant-only) users are filtered
@@ -111,7 +111,26 @@ impl FileRetrievalService {
) -> Result<Bytes, DomainError> {
let stream = file_read.get_file_stream(id).await?;
let mut stream = Pin::from(stream);
let mut buf = BytesMut::with_capacity(capacity);
// Most sub-threshold reads arrive as ONE owned contiguous frame from the
// backend (the local ReaderStream emits ≤256 KiB frames, and a
// sub-threshold blob fits in one). Return that frame directly instead of
// copying the whole payload a second time into a fresh BytesMut; only a
// multi-frame read pays the pre-sized concat — byte-identical output
// (benches/ROUND29.md §C).
let Some(first) = stream.next().await else {
return Ok(Bytes::new());
};
let first = first.map_err(|e| {
DomainError::internal_error("File", format!("Stream read error: {}", e))
})?;
let Some(second) = stream.next().await else {
return Ok(first);
};
let mut buf = BytesMut::with_capacity(capacity.max(first.len()));
buf.extend_from_slice(&first);
buf.extend_from_slice(&second.map_err(|e| {
DomainError::internal_error("File", format!("Stream read error: {}", e))
})?);
while let Some(chunk) = stream.next().await {
buf.extend_from_slice(&chunk.map_err(|e| {
DomainError::internal_error("File", format!("Stream read error: {}", e))
@@ -202,7 +221,6 @@ impl FileRetrievalService {
) -> Result<(FileDto, OptimizedFileContent), DomainError> {
let mime_type = dto.mime_type.clone();
let file_size = dto.size;
let file_name = dto.name.clone();
// The content cache is content-addressed: keyed by the blob hash, not
// the file id. Identical content deduplicated to one blob on disk is
// then cached ONCE in RAM and shared by every file/user that references
@@ -210,34 +228,39 @@ impl FileRetrievalService {
// construction, so entries never go stale (no invalidation needed). A
// stub DTO without a hash disables caching for that request rather than
// colliding every hash-less file on the key "".
let cache_key = dto.content_hash.clone();
let cacheable = !cache_key.is_empty();
let cacheable = !dto.content_hash.is_empty();
let do_transcode = accept_webp && !prefer_original;
// ── Tier 1: Hot cache + transcode (<10 MB) ──────────
if file_size < CACHE_THRESHOLD {
// Fetch the raw blob bytes. When cacheable, `get_or_load` serves
// from the content cache on a hit and, on a miss, coalesces every
// concurrent request for the same blob hash into a SINGLE disk read
// (single-flight) — no thundering herd under load. Hash-less stub
// DTOs are uncacheable and stream straight from disk.
// Probe the content cache with a BORROW first: a hit serves the blob
// straight from RAM, and only a miss builds the owned load arguments
// (the quoted-etag / key / id Strings) that a hit would otherwise
// allocate and immediately discard (benches/ROUND29.md §B). On a miss
// `load_and_cache` still coalesces concurrent requests for the same
// blob hash into a SINGLE disk read (single-flight) — no thundering
// herd. Hash-less stub DTOs are uncacheable and stream from disk.
let content_bytes = if cacheable && let Some(cache) = &self.content_cache {
let etag: Arc<str> = format!("\"{}\"", cache_key).into();
let ct: Arc<str> = mime_type.clone();
let file_read = Arc::clone(&self.file_read);
let id_owned = id.to_string();
let cap = file_size as usize;
let (bytes, _etag, _ct) = cache
.get_or_load(cache_key.clone(), etag, ct, async move {
debug!("💾 TIER 1 Cache MISS: {} – loading from disk", id_owned);
Self::read_full(&file_read, &id_owned, cap).await
})
.await?;
bytes
if let Some((bytes, ..)) = cache.get(&dto.content_hash).await {
bytes
} else {
let etag: Arc<str> = format!("\"{}\"", dto.content_hash).into();
let ct: Arc<str> = mime_type.clone();
let file_read = Arc::clone(&self.file_read);
let id_owned = id.to_string();
let cap = file_size as usize;
let (bytes, ..) = cache
.load_and_cache(dto.content_hash.to_string(), etag, ct, async move {
debug!("💾 TIER 1 Cache MISS: {} – loading from disk", id_owned);
Self::read_full(&file_read, &id_owned, cap).await
})
.await?;
bytes
}
} else {
debug!(
"💾 TIER 1 (uncacheable): {} – streaming from disk",
file_name
dto.name
);
Self::read_full(&self.file_read, id, file_size as usize).await?
};
@@ -269,7 +292,7 @@ impl FileRetrievalService {
// ── Tier 2 + 3: Streaming (≥10 MB) ──────────────────
info!(
"📡 TIER 2 STREAMING: {} ({} MB)",
file_name,
dto.name,
file_size / (1024 * 1024)
);
let stream = self.file_read.get_file_stream(id).await?;
@@ -353,17 +376,27 @@ impl FileRetrievalService {
) -> Result<RangeContent, DomainError> {
let cacheable = dto.size < CACHE_THRESHOLD && !dto.content_hash.is_empty();
if cacheable && let Some(cache) = &self.content_cache {
let etag: Arc<str> = format!("\"{}\"", dto.content_hash).into();
let ct: Arc<str> = dto.mime_type.clone();
let file_read = Arc::clone(&self.file_read);
let id_owned = dto.id.clone();
let cap = dto.size as usize;
let (bytes, _etag, _ct) = cache
.get_or_load(dto.content_hash.to_string(), etag, ct, async move {
debug!("💾 Range cache MISS: {} – loading from disk", id_owned);
Self::read_full(&file_read, &id_owned, cap).await
})
.await?;
// Probe with a BORROW first: the video-scrub steady state is a cache
// hit, and a hit must not allocate the owned load args (quoted-etag /
// key / id Strings) it would immediately discard — those are built
// only on the miss branch (benches/ROUND29.md §B). A miss still
// populates via the same single-flight coalescing.
let bytes = if let Some((bytes, ..)) = cache.get(&dto.content_hash).await {
bytes
} else {
let etag: Arc<str> = format!("\"{}\"", dto.content_hash).into();
let ct: Arc<str> = dto.mime_type.clone();
let file_read = Arc::clone(&self.file_read);
let id_owned = dto.id.clone();
let cap = dto.size as usize;
let (bytes, ..) = cache
.load_and_cache(dto.content_hash.to_string(), etag, ct, async move {
debug!("💾 Range cache MISS: {} – loading from disk", id_owned);
Self::read_full(&file_read, &id_owned, cap).await
})
.await?;
bytes
};
let len = bytes.len() as u64;
let s = start.min(len) as usize;
let e = end.unwrap_or(len).min(len) as usize;