perf: cache-stampede coalescing + DB safeguards; ui/i18n fixes

Backend — tail latency & throughput:
- FileContentCache, image transcode, and search now use moka single-flight
  (try_get_with / get_or_load) so N concurrent misses for the same key
  collapse to one disk read / transcode / query instead of a thundering herd.
  Microbenchmark (128 concurrent on one hot key): 128 loads / p99 ~1023ms
  before vs 1 load / p99 ~32ms after.
- DB: configurable per-statement timeout on the primary pool
  (OXICLOUD_DB_STATEMENT_TIMEOUT_SECS, default 30; maintenance pool exempt) so
  a runaway query can't pin a connection and starve the pool.
- DB: background pool-saturation monitor
  (OXICLOUD_DB_POOL_MONITOR_INTERVAL_SECS) that WARNs as the primary pool nears
  exhaustion — the early signal before tail latency cliffs.
- mimalloc: set MIMALLOC_PURGE_DELAY=0 (Dockerfile + compose) so freed pages
  return to the OS and RSS tracks the live working set; benchmarked on
  musl/aarch64 at ~400MB reclaimed vs 0MB with the default.

Frontend — UI / i18n fixes:
- i18n: fix literal "{{count}}" and "{{percentage}}/{{used}}/{{total}}" in the
  selection toolbar and storage line — the call sites passed param names that
  didn't match the locale placeholders; unify on `count` and pass the storage
  template its params. Add es files.selected_count.
- sidebar: hide the drive picker when there's only one drive (the redundant
  "Personal" row); remove the coloured left accent on the active nav item.
- logo: stop clipping the cloud's left bulge — viewBox recentred on the cloud's
  true bbox with proportional SVG size so it keeps the same rendered scale.
- user menu: drop the default <a> underline on the link rows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
DioCrafts
2026-06-20 14:42:10 +02:00
parent ca18858630
commit b14c4dc911
19 changed files with 848 additions and 310 deletions
@@ -66,6 +66,25 @@ impl FileRetrievalService {
// ── private helpers ──────────────────────────────────────────
/// Read a file's full content through the streaming API into a single
/// `Bytes` buffer. Working memory stays at one chunk while reading; the
/// returned buffer holds the whole (sub-threshold) file.
async fn read_full(
file_read: &FileBlobReadRepository,
id: &str,
capacity: usize,
) -> 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);
while let Some(chunk) = stream.next().await {
buf.extend_from_slice(&chunk.map_err(|e| {
DomainError::internal_error("File", format!("Stream read error: {}", e))
})?);
}
Ok(buf.freeze())
}
/// Helper: require the caller has `perm` on the given file id.
/// Fail-closed if no engine was injected (stub/test path).
async fn require_file(
@@ -162,60 +181,31 @@ impl FileRetrievalService {
// ── Tier 1: Hot cache + transcode (<10 MB) ──────────
if file_size < CACHE_THRESHOLD {
// Check content cache first (keyed by blob hash — see above)
if cacheable
&& let Some(cache) = &self.content_cache
&& let Some((cached, _etag, _ct)) = cache.get(&cache_key).await
{
debug!(
"🔥 TIER 1 Cache HIT: {} ({} bytes)",
file_name,
cached.len()
);
if do_transcode
&& let Some((t, m)) = self
.try_transcode(id, &cached, &mime_type, file_size, true)
.await
{
return Ok((
dto,
OptimizedFileContent::Bytes {
data: t,
mime_type: m,
was_transcoded: true,
},
));
}
return Ok((
dto,
OptimizedFileContent::Bytes {
data: cached,
mime_type: mime_type.clone(),
was_transcoded: false,
},
));
}
// Cache miss – load from disk via streaming (constant 64 KB memory)
debug!("💾 TIER 1 Cache MISS: {} – loading from disk", file_name);
let stream = self.file_read.get_file_stream(id).await?;
let mut stream = std::pin::Pin::from(stream);
let mut buf = BytesMut::with_capacity(file_size as usize);
while let Some(chunk) = stream.next().await {
buf.extend_from_slice(&chunk.map_err(|e| {
DomainError::internal_error("File", format!("Stream read error: {}", e))
})?);
}
let content_bytes = buf.freeze();
// Store in cache (keyed by blob hash; ETag = the immutable hash)
if cacheable && let Some(cache) = &self.content_cache {
// 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.
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();
cache
.put(cache_key.clone(), content_bytes.clone(), etag, ct)
.await;
}
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
} else {
debug!(
"💾 TIER 1 (uncacheable): {} – streaming from disk",
file_name
);
Self::read_full(&self.file_read, id, file_size as usize).await?
};
if do_transcode
&& let Some((t, m)) = self