How to Choose the Right Cache Adapter
View SourceElixirCache supports multiple cache adapters, each with its own strengths and use cases. This guide will help you choose the most appropriate adapter for your specific needs.
Available Adapters
ElixirCache provides the following adapters:
Cache.ETS- Erlang Term StorageCache.DETS- Disk-based ETSCache.Redis- Redis-backed distributed cacheCache.Agent- Simple Agent-based in-memory cacheCache.ConCache- ConCache wrapperCache.PersistentTerm- Erlang:persistent_termfor rarely-written valuesCache.Counter- Lock-free atomic integer countersCache.Sandbox- Isolated cache for testing
Choosing an Adapter
Cache.ETS
Best for:
- High-performance in-memory caching
- Single node applications
- Low-latency requirements
Configuration example:
defmodule MyApp.Cache do
use Cache,
adapter: Cache.ETS,
name: :my_app_cache,
opts: [
read_concurrency: true,
write_concurrency: true
]
endCache.DETS
Best for:
- Persistent caching across application restarts
- Larger datasets that shouldn't be lost on restart
- Less frequent access patterns
Configuration example:
defmodule MyApp.PersistentCache do
use Cache,
adapter: Cache.DETS,
name: :my_app_persistent_cache,
opts: [
file_path: "/tmp/cache_data"
]
endCache.Redis
Best for:
- Distributed applications running on multiple nodes
- Systems requiring shared cache across services
- Applications needing advanced features like expiration, pub/sub, etc.
Configuration example:
defmodule MyApp.DistributedCache do
use Cache,
adapter: Cache.Redis,
name: :my_app_redis_cache,
opts: [
uri: "redis://localhost:6379",
size: 10,
max_overflow: 5
]
endCache.Agent
Best for:
- Simple use cases
- Small applications
- Development environments
Configuration example:
defmodule MyApp.SimpleCache do
use Cache,
adapter: Cache.Agent,
name: :my_app_simple_cache
endCache.ConCache
Best for:
- Applications already using ConCache
- Needs for automatic key expiration and callback execution
Configuration example:
defmodule MyApp.ConCache do
use Cache,
adapter: Cache.ConCache,
name: :my_app_con_cache,
opts: [
ttl_check_interval: :timer.seconds(1),
global_ttl: :timer.minutes(10)
]
endCache.PersistentTerm
Best for:
- Configuration data or look-up tables that change infrequently
- Extremely high-read, low-write workloads
- Single-node or multi-node scenarios where write cost is acceptable
Backed by Erlang's :persistent_term storage. Reads are constant-time and
require no locking or process round-trips, making them faster than ETS for
read-heavy workloads. However, every write or delete copies the entire
persistent term table, so this adapter is not suitable for frequently
mutated keys.
Note: TTL is not supported — values persist until explicitly deleted.
Configuration example:
defmodule MyApp.ConfigCache do
use Cache,
adapter: Cache.PersistentTerm,
name: :my_app_config_cache,
opts: []
endCache.Counter
Best for:
- Atomic integer counters (page views, rate limiting, event counts)
- High-concurrency write workloads where lock-free semantics matter
Backed by Erlang's :counters module, which provides lock-free atomic
increment and decrement. Counter references and the key-to-index mapping are
stored in :persistent_term so all processes can access them without a
process round-trip.
Using use Cache with this adapter injects increment/1,2 and
decrement/1,2 into your module in addition to the standard get/1,
put/3, and delete/1 functions.
Note:
put/3only accepts1or-1as values (increment/decrement). Useincrement/1,2anddecrement/1,2for more ergonomic access.
Configuration example:
defmodule MyApp.CounterCache do
use Cache,
adapter: Cache.Counter,
name: :my_app_counter_cache,
opts: [initial_size: 32]
end
MyApp.CounterCache.increment(:page_views)
MyApp.CounterCache.decrement(:active_users)
{:ok, count} = MyApp.CounterCache.get(:page_views)Cache.Sandbox
Best for:
- Testing environments
- Isolated tests that shouldn't interfere with each other
Configuration example:
defmodule MyApp.TestCache do
use Cache,
adapter: Cache.ETS,
name: :my_app_test_cache,
sandbox?: true
endSwitching Between Adapters
One of the main benefits of ElixirCache is the ability to easily switch between adapters without changing your application code. You can use different adapters in different environments:
defmodule MyApp.Cache do
use Cache,
adapter: get_adapter(),
name: :my_app_cache,
opts: get_opts()
if Mix.env() === :test do
defp get_adapter, do: Cache.ETS
defp get_opts, do: []
else
defp get_adapter, do: Cache.Redis
defp get_opts, do: [uri: "redis://localhost:6379", size: 10]
end
endPerformance Considerations
When choosing an adapter, consider:
- Access patterns - How frequently are you reading vs writing?
- Data volume - How much data will be stored?
- Persistence requirements - Does the data need to survive restarts?
- Distribution needs - Will multiple nodes/services need access?
- Complexity - Do you need advanced features or simple key-value storage?
Always benchmark different options with your specific workload to determine the best fit.