cachex-caching
Skill4 starsChanged 4 months agoMITView on GitHub
MANDATORY for implementing caching in Elixir applications. Invoke before adding caching layers. Configures Cachex instances, implements cache-aside and get-or-set patterns, sets TTL policies, builds cache warmers, monitors cache statistics, and sets up distributed caching across nodes. Trigger words: Cachex, caching, cache, TTL, ETS, distributed cache, cache warmer, cache warmup,
In this repository34 other files it gives its agents.
- AGENTS.mdAGENTS.md
- apply-phoenix-controller-conventionsSkill
- ash-frameworkSkill
- benchee-profilingSkill
- broadway-data-pipelinesSkill
- code-qualitySkill
- code-reviewSkill
- deployment-gotchasSkill
- ecto-changeset-patternsSkill
- ecto-essentialsSkill
- ecto-migrationSkill
- ecto-nested-associationsSkill
- elixir-essentialsSkill
- gettext-i18nSkill
- liveview-streamsSkill
- mix-tasks-generatorsSkill
- oban-essentialsSkill
- otp-essentialsSkill
- phoenix-authenticationSkill
- phoenix-authorization-patternsSkill
- phoenix-channels-essentialsSkill
- phoenix-json-apiSkill
- phoenix-liveview-essentialsSkill
- phoenix-pubsub-patternsSkill
- phoenix-uploadsSkill
- property-based-testingSkill
- refactor-codeSkill
- req-http-clientSkill
- respond-to-reviewSkill
- security-essentialsSkill
- swoosh-emailsSkill
- telemetry-essentialsSkill
- testing-essentialsSkill
- typespec-dialyzerSkill
The file
We split this skill into the rules it gives an agent
1 rule. About 0 are common, 1 is distinctive, and 0 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
0
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 35
Follow this sequence when adding caching to a feature:
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Cachex Caching
RULES — Follow these with no exceptions
1. Set a TTL on every cached entry — never cache indefinitely unless the data is truly immutable
2. Enable stats: true — required to measure cache effectiveness via hit_rate
3. Use cache warmers for startup — pre-populate expensive data when the application starts
4. Use Cachex.fetch/3 for get-or-set — atomic cache-aside avoids the dogpile/race on concurrent misses
5. Invalidate with Cachex.del/2 after every mutation — stale entries outlive the source of truth otherwise
6. Handle the {:error, reason} tuple from every Cachex call — fall back to the database; a cache failure must never break the request
7. Bound the cache with :limit and an eviction policy — unbounded caches leak memory
End-to-End Workflow
Follow this sequence when adding caching to a feature:
- Add dependency — add
{:cachex, "~> 3.6"}tomix.exsand runmix deps.get - Configure cache — start a named Cachex instance in your application supervisor
- Implement get-or-set — use
Cachex.fetch/3for atomic cache-aside pattern - Add invalidation — call
Cachex.del/2after mutating data - Enable stats — configure
stats: truein cache options - Add cache warmer — for expensive data, pre-populate on startup
- Verify hit rate — call
Cachex.stats/1and confirmhit_rateis non-zero; see Monitoring Cache Stats for interpretation thresholds and remediation guidance - Monitor in production — emit telemetry events for cache operations
Setup
# mix.exs
defp deps do
[
{:cachex, "~> 3.6"}
]
endIn your application supervisor:
# lib/my_app/application.ex
defmodule MyApp.Application do
use Application
@impl true
def start(_type, _args) do
children = [
# Basic cache with 1000 entry limit
{Cachex, name: :my_cache, limit: 1000},
# Cache with stats enabled for monitoring
{Cachex, name: :stats_cache, limit: 5000, stats: true},
# Cache with TTL, LRW eviction policy, stats, and hooks
{Cachex,
name: :ttl_cache,
limit: 10_000,
ttl: :timer.minutes(10),
policy: Cachex.Policy.LRW,
stats: true,
hooks: [
%Cachex.Hook{module: MyApp.CacheLogger}
]}
]
Supervisor.start_link(children, strategy: :one_for_one)
end
endBasic Operations
case Cachex.get(:my_cache, "user:123") do
{:ok, nil} -> :miss
{:ok, value} -> {:hit, value}
end
# put with explicit TTL (overrides cache-level default)
Cachex.put(:my_cache, "session:abc", data, ttl: :timer.minutes(30))
Cachex.del(:my_cache, "user:123")
Cachex.clear(:my_cache)Get-or-Set Pattern
# Cachex.fetch/3 — atomic cache-aside, avoids race conditions
{status, value} =
Cachex.fetch(:my_cache, "user:123", fn key ->
user = MyApp.Accounts.get_user(123)
{:commit, user, ttl: :timer.minutes(5)}
end)
case status do
:ok -> IO.puts("Cache hit")
:commit -> IO.puts("Cache miss - computed and cached")
endCache Warmers
defmodule MyApp.CacheWarmer do
use Cachex.Warmer
def execute(state) do
users = MyApp.Accounts.list_active_users()
actions =
Enum.map(users, fn user ->
{:put, "user:#{user.id}", user, ttl: :timer.hours(1)}
end)
{:ok, actions}
end
end
children = [
{Cachex,
name: :my_cache,
warmers: [
%Cachex.Warmer{module: MyApp.CacheWarmer, interval: :timer.minutes(5)}
]}
]Cache Invalidation
# All Cachex operations return {:ok, result} or {:error, reason}; fall back to DB on failure
defmodule MyApp.Accounts do
def update_user(user, attrs) do
with {:ok, updated_user} <- Repo.update(User.changeset(user, attrs)) do
case Cachex.del(:my_cache, "user:#{user.id}") do
{:ok, _} ->
:ok
{:error, reason} ->
Logger.warning("Cache invalidation failed for user:#{user.id}: #{inspect(reason)}")
end
{:ok, updated_user}
end
end
def get_user(id) do
case Cachex.fetch(:my_cache, "user:#{id}", fn _ ->
{:commit, Repo.get(User, id), ttl: :timer.minutes(5)}
end) do
{:ok, user} -> user
{:error, _} -> Repo.get(User, id) # Fallback to database
end
end
endMonitoring Cache Stats
{Cachex, name: :my_cache, stats: true}
{:ok, stats} = Cachex.stats(:my_cache)
# %{hit_rate: 85.5, hits: 8550, misses: 1450, gets: 10000, sets: 1200, evictions: 100}Interpret hit rate:
> 80%— excellent, cache is very effective60-80%— good, normal for read-heavy workloads< 60%— investigate TTL values and check for over-invalidation< 20%— cache may be ineffective; revisit TTL strategy and key design before deploying to production
Telemetry Integration
Emit telemetry events for cache operations to monitor in production:
def get_user(id) do
case Cachex.fetch(:my_cache, "user:#{id}", fn _ ->
{:commit, Repo.get(User, id)}
end) do
{:ok, user} ->
:telemetry.execute([:my_app, :cache, :my_cache], %{hits: 1}, %{key: "user:#{id}"})
user
{:error, _} ->
:telemetry.execute([:my_app, :cache, :my_cache], %{misses: 1}, %{key: "user:#{id}"})
Repo.get(User, id)
end
endDistributed Caching
Broadcast-based invalidation across nodes:
defmodule MyApp.CacheSync do
@topic "cache:invalidate"
def broadcast_delete(key) do
Phoenix.PubSub.broadcast(MyApp.PubSub, @topic, {:invalidate, key})
end
def handle_info({:invalidate, key}, state) do
Cachex.del(:my_cache, key)
{:noreply, state}
end
end
# Subscribe in your GenServer or LiveView
Phoenix.PubSub.subscribe(MyApp.PubSub, "cache:invalidate")Remote reads via RPC (single authoritative node pattern):
def get_user_distributed(id) do
case Cachex.get(:my_cache, "user:#{id}") do
{:ok, nil} ->
:rpc.call(primary_node(), Cachex, :get, [:my_cache, "user:#{id}"])
|> case do
{:ok, nil} -> fetch_from_db(id)
{:ok, value} -> value
end
{:ok, value} ->
value
end
end
defp primary_node, do: Application.fetch_env!(:my_app, :primary_cache_node)Common Pitfalls
| ❌ Don't | ✅ Do |
| Cache with no TTL | Set a per-entry TTL (or a cache-level default) so stale data expires |
Do Cachex.get/2 + Cachex.put/3 for cache-aside | Use Cachex.fetch/3 — atomic get-or-set avoids concurrent-miss races |
| Ignore Cachex return tuples | Match {:ok, _} / {:error, _} and fall back to the database on error |
| Forget to invalidate after writes | Call Cachex.del/2 after every mutation of the cached record |
| Run a cache without stats | Enable stats: true and watch hit_rate to confirm effectiveness |
| Let the cache grow unbounded | Set :limit with an eviction policy (e.g. Cachex.Policy.LRW) |
| Assume a local cache is shared across nodes | Broadcast invalidation via PubSub or read from an authoritative node via RPC |
Integration
| Predecessor | This Skill | Successor |
| ecto-essentials | cachex-caching | telemetry-essentials |
| otp-essentials | cachex-caching | deployment-gotchas |
Companion skills:
telemetry-essentials— emit and monitor cache hit/miss events in productionphoenix-pubsub-patterns— broadcast cross-node cache invalidation
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

