Skip to content
This repository was archived by the owner on Nov 30, 2025. It is now read-only.

devall-org/nano_global_cache

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

32 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

NanoGlobalCache

⚠️ 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.

Why NanoGlobalCache?

  • 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

Installation

def deps do
  [{:nano_global_cache, "~> 0.3.0"}]
end

Quick Example

defmodule 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

Usage

# 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()

How It Works

Read Replicas for Performance

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

Consistency Model

  • Expiration checks: Each node independently checks expiration (eventually consistent)
  • Updates: Coordinated via :global.trans/2 to 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

When to Use

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.

API Reference

Define Caches

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
end

Generated Functions

  • fetch(name){:ok, value, expires_at} or :error
  • fetch!(name)value (without expiration time) or raises RuntimeError
  • clear(name):ok
  • clear_all():ok

Implementation

  • Spark DSL for compile-time configuration
  • :pg for agent replication across cluster
  • :global.trans/2 for distributed coordination
  • Automatic function generation via transformers

Development

Running Tests

# Run all tests (includes distributed tests)
mix test

# Run only distributed tests
mix test --only distributed

# Exclude distributed tests
mix test --exclude distributed

Distributed tests automatically start a local Erlang cluster using :net_kernel and :peer to verify cache replication and synchronization across multiple nodes.

About

Lightweight global cache for Elixir

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Languages