A cache in memory, on each node. ≈ Redis / Memcached used as a cache — but it lives inside the app (Cachex), so a read costs no network trip.
Start it in your supervision tree, after your Phoenix.PubSub:
{Dandelion.Cache, pubsub: MyApp.Broadcast}then:
Cache.fetch({:product, sku}, fn -> Repo.get(Product, sku) end)
Cache.delete({:product, sku})fetch/2returns the cached value, or calls the function, keeps what it returns (nilis not kept) and returns that. The function runs in the caller's process, so it uses the caller's database connection — inside a transaction it sees that transaction's writes, and it never needs a second connection while holding one. The price: two requests missing the same key at once both call it (like a plain Redis get-then-set). If you need one load for all of them, useCachex.fetch/3on theDandelion.Cachecache directly, and mind that its function runs in another process.delete/1clears the key on every node (over the PubSub). Call it after the database change is committed: earlier, another node could read the old row and cache it again.- Entries expire after
:ttl(default one minute), so a missed message (a network split) fixes itself quickly. A node that joins or rejoins the cluster clears its whole cache (Dandelion.Cache.Listener).
Options: :pubsub (required, the name of your Phoenix.PubSub) and :ttl
in milliseconds.
It is a cache: a node that restarts starts empty. Anything that must survive goes in Postgres.
One cache per app: the name is Dandelion.Cache.
Summary
Functions
Removes key on this node and on every other node.
The value for key, from the cache or from fun (not kept if nil).
Functions
@spec delete(term()) :: :ok
Removes key on this node and on every other node.
The value for key, from the cache or from fun (not kept if nil).
Emits [:dandelion, :cache, :fetch] with %{count: 1} and %{result: :hit}
or %{result: :miss}, for a counter of hits and misses:
counter("dandelion.cache.fetch.count", tags: [:result])