Multi-layer caching strategy that cascades through multiple cache layers.
Keys are read from fastest to slowest, with automatic backfill on cache hits from slower layers. Writes go slowest-first to avoid polluting fast layers with data that failed to persist in slow ones.
Usage
Pass a list of layers as the strategy config. Each element can be:
- A module that implements
Cache(already running, not supervised by this adapter) - An adapter module (e.g.
Cache.ETS) — will be auto-started and supervised - A tuple
{AdapterModule, opts}— adapter with inline opts
defmodule MyApp.LayeredCache do
use Cache,
adapter: {Cache.MultiLayer, [Cache.ETS, MyApp.RedisCache]},
name: :layered_cache,
opts: []
end__MODULE__ in Layers
You may include __MODULE__ in the layer list to position the current
module's own underlying cache within the chain. If __MODULE__ is omitted,
no local cache is created for the defining module—it acts as a pure facade.
defmodule MyApp.LayeredCache do
use Cache,
adapter: {Cache.MultiLayer, [Cache.ETS, __MODULE__, MyApp.RedisCache]},
name: :layered_cache,
opts: [uri: "redis://localhost"]
endRead Behaviour
Layers are iterated fastest → slowest (list order). On a hit from layer N, the value is backfilled into layers 1..N-1.
Write Behaviour
Layers are written slowest → fastest (reverse list order). If a slow write fails, the write stops and an error is returned — preventing polluting faster layers with potentially-unsaved data.
Fetch Callback (Optional)
If all layers miss, an optional fetch callback can supply the value. The fetched value is then backfilled into all layers.
Define it as a module callback or pass it via opts:
defmodule MyApp.LayeredCache do
use Cache,
adapter: {Cache.MultiLayer, [Cache.ETS, MyApp.RedisCache]},
name: :layered_cache,
opts: [on_fetch: &__MODULE__.fetch/1]
def fetch(key) do
{:ok, "value_for_#{key}"}
end
endCross-Node Coherence (Optional)
Node-local fast layers (e.g. Cache.ETS) go stale on every node except the
writer. Setting broadcast_mode keeps them coherent: after a successful
put/delete, every other node running this cache (tracked via :pg —
see Cache.MultiLayer.Coordinator) is notified and applies the change to
its own broadcast_layers.
defmodule MyApp.LayeredCache do
use Cache,
adapter: {Cache.MultiLayer, [MyApp.EtsLayer, MyApp.RedisCache]},
name: :layered_cache,
opts: [
backfill_ttl: :timer.seconds(30),
broadcast_mode: :invalidate,
broadcast_layers: [MyApp.EtsLayer]
]
end:invalidate sends only the key — remote nodes drop their local entry and
lazily re-read through the shared layer. :replicate ships the value so
remote local layers are updated immediately; use it only for small values.
Delivery is best-effort: always keep backfill_ttl (and layer TTLs) as the
correctness floor for members that miss a message.
Options
:on_fetch- Optional fetch callback invoked on total cache miss. Receives the key, returns{:ok, value}or{:error, reason}.:backfill_ttl- TTL in milliseconds to use when backfilling layers on a hit from a slower layer. Defaults to nil (no expiry).:broadcast_mode- Cross-node coherence for writes::invalidatedeletes the key from other nodes'broadcast_layers;:replicatepushes the written value to them. Best-effort delivery.:broadcast_layers(list ofatom/0) - Node-local layer modules the broadcast applies to on other nodes. Required withbroadcast_mode; must not include the shared (slowest) layer.