Cache remote media by origin server and media ID¶
- Status: accepted; the cache is made durable, and its lookup, expiry and cap-0 rules are restated, by ADR-0119
- Deciders: James Chapman
- Date: 2026-10-05
Context and Problem Statement¶
Before this change, every request for a remote mxc:// URI re-fetched the media
from the origin server and re-ran it through the local admission pipeline
(scanner, decoder, quarantine, thumbnailer). This wasted bandwidth and CPU and
allowed a remote server to amplify storage/thumbnail work by returning the same
media ID repeatedly (security audit finding OUT-4). There was no cache, TTL, or
eviction.
Decision Drivers¶
- Repeated requests for the same remote URI should serve the already-admitted local copy.
- The cache must be bounded so it cannot itself become a denial-of-service surface.
- Cached entries must expire so that remote content updates are eventually visible.
- Eviction must be predictable (LRU) rather than arbitrary.
Considered Options¶
- Cache only the raw remote bytes before admission. Rejected: the admission pipeline (scanner, decoder, quarantine) is the expensive part; we want to avoid re-running it.
- Cache by local media ID only. Rejected: callers arrive with an
(origin_server, media_id)pair, not a local ID, so the mapping must be keyed by the remote pair. - Cache by
(origin_server, media_id)pointing to the admitted local record, with TTL and LRU eviction. Accepted: it avoids duplicate fetches and admission work, is keyed by what the caller provides, and is bounded.
Decision Outcome¶
Chosen option: "in-memory vector cache keyed by (origin_server, media_id) that
stores the local media ID assigned on first admission, with a configurable TTL
and least-recently-used eviction when the entry cap is reached", because it
saves both network and admission work while keeping memory bounded.
Positive Consequences¶
- Repeated remote media requests do not hit the network or re-admit bytes.
- The cache size is capped and old/least-used entries are evicted.
- Cache entries are invalidated when the underlying local record is no longer available (quarantined or removed).
Where the lookup sits¶
The media service consults the cache (media::find_cached_remote_media) in
fetch_remote_media_live, after the remote_fetch_enabled/allow_remote
refusal and before the client-outbound budget slot, discovery and the outbound
request. A lookup placed after the fetch, inside media::fetch_remote_media,
stops duplicate storage but still sends one outbound request per client request,
which is the amplification this decision exists to remove; that was the first
implementation, and it was wrong. The post-fetch lookup remains only so two
requests that both missed the cache do not admit the same media twice.
An entry for a key replaces any older entry for it, an expired entry or one whose
record is no longer available is erased on lookup, and
remote_media_cache_max_entries = 0 disables the cache rather than removing its
bound.
Negative Consequences¶
- A zero TTL disables caching; a very long TTL delays visibility of updated remote media. Defaults are chosen for a one-day window.