Curated library of 38 atomic skills, 7 personas, and 1 orchestrator for Elixir and Phoenix development. Organized by category: fundamentals, phoenix, database, testing, auth, infrastructure, quality, security, integrations, tooling, frameworks, personas, and orchestration. Covers core Elixir patterns, Phoenix LiveView, Ecto, OTP, Oban, testing, security, deployment, real-time, and modern tooling (Req, Swoosh, Cachex, Broadway, Ash).
91
91%
Does it follow best practices?
Impact
91%
1.37xAverage score across 56 eval scenarios
Advisory
Suggest reviewing before use
stats: true — required to measure cache effectiveness via hit_rateCachex.fetch/3 for get-or-set — atomic cache-aside avoids the dogpile/race on concurrent missesCachex.del/2 after every mutation — stale entries outlive the source of truth otherwise{:error, reason} tuple from every Cachex call — fall back to the database; a cache failure must never break the request:limit and an eviction policy — unbounded caches leak memoryFollow this sequence when adding caching to a feature:
{:cachex, "~> 3.6"} to mix.exs and run mix deps.getCachex.fetch/3 for atomic cache-aside patternCachex.del/2 after mutating datastats: true in cache optionsCachex.stats/1 and confirm hit_rate is non-zero; see Monitoring Cache Stats for interpretation thresholds and remediation guidance# 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
endcase 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)# 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")
enddefmodule 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)}
]}
]# 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
end{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 productionEmit 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
endBroadcast-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)| ❌ 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 |
| 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.tessl-plugin
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10
scenario-11
scenario-12
scenario-13
scenario-14
scenario-15
scenario-16
scenario-17
scenario-18
scenario-19
scenario-20
scenario-21
scenario-22
scenario-23
scenario-24
scenario-25
scenario-26
scenario-27
scenario-28
scenario-29
scenario-30
scenario-31
scenario-32
scenario-33
scenario-34
scenario-35
scenario-36
scenario-37
scenario-38
scenario-39
scenario-40
scenario-41
scenario-42
scenario-43
scenario-44
scenario-45
scenario-46
scenario-47
scenario-48
scenario-49
scenario-50
scenario-51
scenario-52
scenario-53
scenario-54
scenario-55
scenario-56
skills
frameworks
ash-framework
infrastructure
orchestration
elixir-skill-router
personas
phoenix
quality
security
security-essentials
tooling
mix-tasks-generators