Dandelion.Cache (dandelion v0.2.0)

Copy Markdown View Source

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/2 returns the cached value, or calls the function, keeps what it returns (nil is 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, use Cachex.fetch/3 on the Dandelion.Cache cache directly, and mind that its function runs in another process.
  • delete/1 clears 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

delete(key)

@spec delete(term()) :: :ok

Removes key on this node and on every other node.

fetch(key, fun)

@spec fetch(term(), (-> term())) :: term()

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])