d33d1932b6
Every thumbnail request paid an uncached storage.file_attached_blobs point query before it could answer — including 304 revalidations and RAM thumbnail hits, where the ETag path (thumbnail_content_id) probes the row every time and tier 2b probes it again with the same key. A photos grid revalidating 60 thumbnails per visit meant 60+ point queries per browse, repeated on every visit. find_attached_blob now reads through a process-local moka cache in DedupService, keyed by the row's (file_id, kind, variant) PK, holding positive and negative entries (most files have no attached preview, so the negative side carries the win). Two rules keep it honest: - DB faults are surfaced as Err and never cached — a transient outage cannot freeze "no attached blob" into a negative entry (a read failure is never proof that data is absent). The public signature is unchanged; the SQL body moved to find_attached_blob_uncached. - Writes invalidate eagerly: store_attached_blob and the Inserted arm of store_attached_blob_if_absent on success, and deletions via ThumbnailRefreshHook::on_file_deleted, which all three production delete paths (single file, folder cascade, trash clear) fire after the DELETE commits. The 60s TTL bounds only what the process cannot see (bare SQL, copy_file_satellites races). The Nextcloud preview endpoint rides the same lookup and benefits identically. Five in-memory contract tests pin the cache behaviour, including the fault-not-cached rule. Co-Authored-By: Claude Code <noreply@anthropic.com>