⚠️ This package has been renamed to expirable_store. Please use{:expirable_store, "~> 0.4"}instead.
🔒 Lightweight global cache for Elixir with expiration support and intelligent failure handling.
Perfect for caching OAuth tokens, API keys, and other time-sensitive data that shouldn't be repeatedly refreshed.
- ✅ Smart caching: Caches successes, retries failures on next fetch
- 🌍 Read replicas: Each node maintains local copy for fast, lock-free access
- 🔐 Concurrency-safe: Safe concurrent access via
:global.trans/2 - ⏱️ Expiration: Time-based invalidation with custom logic
- 📝 Clean DSL: Spark-based compile-time configuration with auto-generated functions
- ⚡ Minimal overhead: No background processes or setup
def deps do
[{:nano_global_cache, "~> 0.3.0"}]
enddefmodule MyApp.TokenCache do
use NanoGlobalCache
# Regular OAuth token - calculate expiration yourself
cache :github do
fetch fn ->
case GitHub.refresh_token() do
{:ok, token} ->
expires_at = System.system_time(:millisecond) + :timer.hours(1)
{:ok, token, expires_at}
:error ->
:error
end
end
end
# JWT token - use expiration time from the token itself
cache :auth0 do
fetch fn ->
case Auth0.get_access_token() do
{:ok, jwt} ->
# JWT exp claim is in seconds, convert to milliseconds
%{"exp" => exp_seconds} = JOSE.JWT.peek_payload(jwt)
expires_at = exp_seconds * 1000
{:ok, jwt, expires_at}
:error ->
:error
end
end
end
# Generated functions: fetch/1, fetch!/1, clear/1, clear_all/0
end# Pattern match on result with expiration time
{:ok, token, expires_at} = MyApp.TokenCache.fetch(:github)
# Or use bang version (no expiration time)
token = MyApp.TokenCache.fetch!(:github)
# Clear cache
MyApp.TokenCache.clear(:github)
MyApp.TokenCache.clear_all()NanoGlobalCache uses a read replica pattern to eliminate network latency:
- Each node maintains a local Agent with its own copy of the cached data
- Reads are served from the local replica (no network hop, no lock)
- First access on a node creates a local replica by copying from existing nodes or fetching fresh
- Expiration checks: Each node independently checks expiration (eventually consistent)
- Updates: Coordinated via
:global.trans/2to prevent race conditions - Double-check pattern: Lock winner verifies expiration again before fetching
- Replica sync: All replicas updated atomically within the lock
- Failed results: Never cached, always retried on next call
This library is optimized for lightweight data like:
- OAuth tokens, API keys, JWT tokens
- Small configuration values
- Session identifiers
- Cached credentials
NOT recommended for:
- High-traffic scenarios (frequent reads/writes)
- Dynamic cache keys (unbounded number of entries)
- Large cache values that would cause heavy network traffic between nodes
NanoGlobalCache uses :pg for replication and :global.trans/2 for coordination. All nodes maintain local replicas for fast access, eliminating network latency for reads. Updates are coordinated globally to keep replicas consistent. Designed for scenarios where simplicity and predictable local access are valued.
For more demanding use cases, consider Cachex or Nebulex.
cache :cache_name do
fetch fn ->
# Your fetch logic here
# Must return {:ok, value, expires_at} or :error
# expires_at is Unix timestamp in milliseconds
{:ok, value, System.system_time(:millisecond) + ttl_milliseconds}
end
endfetch(name)→{:ok, value, expires_at}or:errorfetch!(name)→value(without expiration time) or raisesRuntimeErrorclear(name)→:okclear_all()→:ok
- Spark DSL for compile-time configuration
:pgfor agent replication across cluster:global.trans/2for distributed coordination- Automatic function generation via transformers
# Run all tests (includes distributed tests)
mix test
# Run only distributed tests
mix test --only distributed
# Exclude distributed tests
mix test --exclude distributedDistributed tests automatically start a local Erlang cluster using :net_kernel and :peer to verify cache replication and synchronization across multiple nodes.