A cache for Ash resources. Reads come back from memory instead of the database, and the cache clears itself when the data changes.
TL;DR
defmodule MyApp.Shop.Product do
use Ash.Resource,
domain: MyApp.Shop,
extensions: [AshCache.Resource]
cache do
read :get_by_sku
end
endThe first get_by_sku read goes to the database and its results are
saved. The next reads come from the cache, not the database. That's it.
Features
- You pick what is cached. List the reads to cache in a
cacheblock on the resource. - No manual clearing. When a record is created, updated or deleted, the cache for that resource is cleared.
- No data leaks between users. Each user and each tenant has their own cached results.
Installation
Add the package to mix.exs:
def deps do
[
{:ash_cache, "~> 0.1"},
{:cachex, "~> 3.6"}
]
endCachex is the default store. To use another store, see the Storage guide.
Add the supervisor to your application. It starts the cache stores:
# lib/my_app/application.ex
children = [
# ...
{AshCache.Supervisor, otp_app: :my_app}
]Usage
Set how long results are kept
ttl is in milliseconds. A read without one keeps its results for 5
minutes. Set ttl on the block for every read, or on one read:
cache do
ttl :timer.hours(1)
read :get_by_sku
read :featured, ttl: :timer.minutes(1)
endget_by_sku keeps results for an hour, featured for a minute.
Cache a generic action
For example, a call to another company's API:
cache do
action :get_rate, ttl: :timer.minutes(10)
endOn a cache hit, the action's run function is not called.
Cache a slow calculation
A calculation written as an Elixir module is cached one result per record:
cache do
calculation :shipping_quote, ttl: :timer.hours(1)
endWrites don't clear these results; they run out with their ttl. See
"A slow calculation" in Examples.
Clearing the cache
Writes through Ash clear the cache by themselves. To clear it when a related resource changes, or by hand after an import, see When the cache clears.
Learn more
- Examples: the
cacheblock for common app features, and when not to cache. - When the cache clears: what clears it, related resources, and clearing by hand.
- Storage: the built-in stores, running on several nodes, and writing your own adapter.
- How to test with AshCache: check that a read is cached and that a write clears it, keep parallel tests apart.
- How AshCache works: what goes in a cache key, how clearing works, and the limits.
- DSL reference: every
option in the
cacheblock.
Limits
- The cache clears per resource, not per record. Any write to a resource clears every cached read and generic action of that resource.
- Values computed from another resource need it listed. Related
records loaded with
loadare always read fresh. A count, a sum or a filter over another resource is cached only when that resource is inclear_when_changed; otherwise the read is not cached. - The default store is per server. On several servers, a write clears the cache only on the server that made it. Sharing one cache between servers is experimental and not tested on real servers; see Storage.
- Cached calculations are not cleared by writes. A changed record gets
a new result at once, but a calculation that reads something outside the
record (an API, the clock) can serve an old answer until its
ttlruns out. ttl: :infinityholds memory. A write doesn't delete old results, it only stops them being used. They stay in memory until theirttlruns out, so use:infinityonly for data that is rarely written.- Writes get a little slower. Each write has to clear the cache. Measure before caching a resource that is written to constantly.
For AI assistants
Instructions for coding agents (Claude Code, Cursor, Copilot, ...)
The file for agents is
usage-rules.md.
It ships inside the package, so after mix deps.get it is at
deps/ash_cache/usage-rules.md. Point your agent at that file.
If your project uses the
usage_rules
package, add :ash_cache to its list instead.
License
MIT. See LICENSE.