dashbitco /
nimble_parsec
A simple and fast library for text-based parser combinators
75/100 healthLoading repository data…
CrankWheel / repository
A simple, fast ETS-based cache with timed expiry for Erlang and Elixir.
A transparent discovery signal based on current public GitHub metadata.
This score does not audit code, security, maintainers, documentation quality, or suitability. Verify the repository and its current documentation before adoption.
A simple, fast ETS-based cache with timed expiry for Erlang and Elixir.
Copyright CrankWheel ehf. 2025. Initially developed for CrankWheel Screen Sharing back in 2021, (https://crankwheel.com/).
Authored by Jói Sigurdsson (https://joisig.com/).
put/get/delete operations with minimal overheadkiss_cache (Keep It Simple, Stupid) is designed to be the simplest possible timed-expiry cache you can think of for Erlang/Elixir. It's based on ETS and adds minimal complexity while solving common caching problems.
The motivation for writing this cache was that other solutions we tried would sometimes deadlock under heavy load, likely due to transactions. We needed something foolproof but simpler.
This implementation is fairly battle tested with several years in the field and some high-contention use cases that have happened multiple times.
Add to your mix.exs:
def deps do
[
{:kiss_cache, "~> 1.0"}
]
end
Add to your rebar.config:
{deps, [
{kiss_cache, "1.0.0"}
]}.
⚠️ Important: ETS Table Ownership
Cache tables are ETS tables owned by the process that creates them. If that process dies, the cache is deleted. Always create caches from a long-lived, supervised process.
Recommended approach: Pass cache names to the supervisor:
{ok, _Pid} = kiss_cache_sup:start_link([my_cache, other_cache]).Alternative: Call
start_cache/1from your own supervised GenServer/worker.See Architecture for integration details.
% Create a cache table (do this once, from a long-lived process)
kiss_cache:start_cache(my_cache).
% Put values with default 10 second expiry
kiss_cache:put(my_cache, my_key, my_value).
% Put with custom expiry (in milliseconds)
kiss_cache:put(my_cache, my_key, my_value, 60000). % 60 seconds
% Get values
Value = kiss_cache:get(my_cache, my_key).
% Get with default if not found
Value = kiss_cache:get(my_cache, my_key, default_value).
% Delete a key
kiss_cache:delete(my_cache, my_key).
# Create cache
:kiss_cache.start_cache(:my_cache)
# Store and retrieve
:kiss_cache.put(:my_cache, :user_123, %{name: "Alice"})
user = :kiss_cache.get(:my_cache, :user_123)
# With custom expiry (30 seconds)
:kiss_cache.put(:my_cache, :session, session_data, 30_000)
For expensive operations that many processes might request simultaneously, use fetch/4+ to serialize the computation and cache the result:
% First, start the supervisor with your cache names (do this once in your app)
% This creates the caches AND supervises the fetcher processes
{ok, Pid} = kiss_cache_sup:start_link([my_cache]).
% Now fetch will serialize calls to expensive_function/1
Result = kiss_cache:fetch(my_cache, my_module, expensive_function, [arg1]).
% Even if 1000 processes call this simultaneously, expensive_function
% will only be called once (assuming the result isn't already cached).
The serialized fetch prevents "thundering herd" problems where many processes simultaneously try to compute the same expensive value.
Note: Passing cache names to start_link/1 ensures the supervisor owns the ETS tables, guaranteeing they survive for the lifetime of your application.
Cache cleanup is lazy and efficient:
kiss_cache:delete_all_expired/1 to clean up explicitlyYou can control cleanup behavior:
% Never trigger cleanup on this write
kiss_cache:put(my_cache, key, value, 10000, false).
% Always trigger cleanup on this write
kiss_cache:put(my_cache, key, value, 10000, true).
% Trigger cleanup 1 in N times (here: 1 in 500)
kiss_cache:put(my_cache, key, value, 10000, 500).
start_cache(Cache) - Create a new cache tableput(Cache, Key, Value) - Store with default 10s expiryput(Cache, Key, Value, ExpiryMs) - Store with custom expiryput(Cache, Key, Value, ExpiryMs, ShouldCleanup) - Store with cleanup controlget(Cache, Key) - Retrieve value (returns nil if not found/expired)get(Cache, Key, Default) - Retrieve with custom defaultdelete(Cache, Key) - Delete a keydelete_all_expired(Cache) - Manually clean up all expired entriesdelete_all(Cache) - Clear the entire cachefetch(Cache, Module, Func, Params) - Serialized cached function callfetch(Cache, Module, Func, Params, CheckReturn) - With result validationfetch(Cache, Module, Func, Params, ExpiryMs) - With custom expiryfetch(Cache, Module, Func, Params, ExpiryMs, ShouldCleanup) - Full controlBasic operations (put/get/delete) require no supervisor. Only use the supervisor if you need the fetch/* functions for serialized fetching.
Cache tables are ETS tables with ownership semantics: the table belongs to the process that creates it and is automatically deleted when that process terminates.
Integration patterns:
Using kiss_cache_sup (Recommended)
Erlang:
% In your application supervisor
init([]) ->
Children = [
{kiss_cache_sup, {kiss_cache_sup, start_link, [[cache1, cache2]]},
permanent, 5000, supervisor, [kiss_cache_sup]},
% ... other children
],
{ok, {{one_for_one, 10, 10}, Children}}.
Elixir:
# In your application supervisor
def init(_) do
children = [
{kiss_cache_sup, [[cache1, cache2]]},
# ... other children
]
Supervisor.init(children, strategy: :one_for_one)
end
The supervisor creates and owns the caches, ensuring they persist for your app's lifetime.
Custom worker pattern
Erlang:
% In your own GenServer's init/1
init(_Args) ->
kiss_cache:start_cache(my_cache),
{ok, #state{}}.
Elixir:
# In your own GenServer's init/1
def init(_args) do
:kiss_cache.start_cache(:my_cache)
{:ok, %{}}
end
Your GenServer owns the cache. If your GenServer is supervised, the cache survives restarts.
Basic operations only (no supervisor needed)
Erlang:
% Create cache from a long-lived process
kiss_cache:start_cache(my_cache),
% Use basic operations
kiss_cache:put(my_cache, key, value),
kiss_cache:get(my_cache, key).
Elixir:
# Create cache from a long-lived process
:kiss_cache.start_cache(:my_cache)
# Use basic operations
:kiss_cache.put(:my_cache, :key, :value)
:kiss_cache.get(:my_cache, :key)
Suitable if you don't need the fetch/* serialization features.
Warning: Never create caches from the Erlang shell or temporary processes in production, as they will be deleted when that process terminates.
This cache is designed to be simple and fast at the cost of some consistency guarantees:
These trade-offs are acceptable for use cases where:
If you need stronger consistency guarantees, consider using the serialized fetch/* functions or a different caching solution.
MIT
Selected from shared topics, language and repository description—not editorial ratings.
dashbitco /
A simple and fast library for text-based parser combinators
75/100 healthdashbitco /
A simple and fast CSV parsing and dumping library for Elixir
73/100 healthdashbitco /
A simple and fast strftime-based datetime formatter
sntran /
An Elixir open source and collaborative framework for extracting the data you need from websites. In a fast, simple, yet extensible way. Or just an experiment writing a scraper in Elixir to scratch my own itch. Use at your own risk.
33/100 healthdealloc /
A fast, simple Discord bot framework
25/100 healthAllyedge /
A simple project that demonstrates how to use MeiliSearch and Phoenix to build a blazing-fast search engine.
44/100 health