perf(cache): key the file content cache by blob hash, not file id
FileContentCache (moka, 512 MiB) was keyed by the file UUID, so content that the CDC store already deduplicates to ONE blob on disk was cached once PER FILE in RAM: N files sharing a blob held N copies, all counting against the 512 MiB cap. With effective dedup the cache filled with duplicates and thrashed. Key it by the blob hash instead (already on FileDto::content_hash): - The in-RAM cache now benefits from dedup like the disk does — each distinct blob is cached once and shared across every file/user that references it, so a download by user A warms the cache for user B's identical content. - Content is immutable by hash, so entries never go stale; the existing invalidate(file_id) calls become harmless no-ops (a UUID never matches a hash key) and can be removed in a later cleanup. - ETag is now the immutable content hash (strong validator). - Guarded: a hash-less stub DTO disables caching for that request rather than colliding every such file on the empty key. Response Content-Type still comes from the DTO, not the cache, so keying does not affect the served MIME (verified). Benchmark (real moka, exact 512 MiB/weight config, 400 files x 4 MiB = 1600 MiB working set, 4000 uniform-random accesses): dedup 1x : file_id 35.8% hit / 2568 reads vs hash 35.6% / 2577 (no dedup -> no change; control) dedup 5x : file_id 35.8% hit / 2570 reads vs hash 98.0% / 80 (32x fewer disk reads, RAM 512->320 MiB) dedup 20x: file_id 35.1% hit / 2596 reads vs hash 99.5% / 20 (130x fewer disk reads, RAM 512->80 MiB) The win scales with the dedup ratio; with no dedup it is a no-op. https://claude.ai/code/session_01DCszkkU11LYxMEUWr4setK
This commit is contained in:
@@ -149,14 +149,23 @@ impl FileRetrievalService {
|
||||
let mime_type = dto.mime_type.clone();
|
||||
let file_size = dto.size;
|
||||
let file_name = dto.name.clone();
|
||||
let modified_at = dto.modified_at;
|
||||
// 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
|
||||
// it — the cache benefits from dedup, not just the disk. Immutable by
|
||||
// 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 do_transcode = accept_webp && !prefer_original;
|
||||
|
||||
// ── Tier 1: Hot cache + transcode (<10 MB) ──────────
|
||||
if file_size < CACHE_THRESHOLD {
|
||||
// Check content cache first
|
||||
if let Some(cache) = &self.content_cache
|
||||
&& let Some((cached, _etag, _ct)) = cache.get(id).await
|
||||
// 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)",
|
||||
@@ -199,12 +208,12 @@ impl FileRetrievalService {
|
||||
}
|
||||
let content_bytes = buf.freeze();
|
||||
|
||||
// Store in cache
|
||||
if let Some(cache) = &self.content_cache {
|
||||
let etag: Arc<str> = format!("\"{}-{}\"", id, modified_at).into();
|
||||
// Store in cache (keyed by blob hash; ETag = the immutable hash)
|
||||
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(id.to_string(), content_bytes.clone(), etag, ct)
|
||||
.put(cache_key.clone(), content_bytes.clone(), etag, ct)
|
||||
.await;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user