Copyright (c) 2019-24 Renaud Mariana.
An erlang ETS cache with TTL.
The typical usage is:
case redi:get(..) of
[] ->
get the value from slow storage
redi:set(..)
[value] ->
use value ...
end
This service ensures that data hold by the cache are fresh enough
(<= TTL) and doesn't guarantee that data before their TTL expiration are still in sync with the underlying storage, but this is good enough for our use cases.
Redi is a gen_server that could be added to a supervision tree.
Its usage is very simple :
$ rebar3 shell
Bucket = test,
{ok, Pid} = redi:start_link(#{bucket_name => Bucket,
entry_ttl_ms=> 30000}),
redi:set(Pid, <<"key1">>, <<"data11">>),
redi:set(Pid, <<"key1">>, <<"data12">>),
redi:set(Pid, <<"key2">>, <<"moredata">>),
redi:delete(Pid, <<"key2">>),
redi:get(Bucket, <<"key1">>).
%% working with several buckets
redi:add_bucket(Pid, another_bucket, bag),
redi:set(Pid, <<"keyx">>, <<"data.aaay">>, another_bucket),
redi:get(another_bucket, <<"keyx">>).
...
Excerpt from a run of the unit test (i5-1235U)
- throughput 363464 writes/s.
- throughput 3478260 reads/s.
redi:gc_client(Redi_Pid, self(), Opts).
receive
{redi_gc, _, Keys} -> ok
%% see more examples in unit tests
Redi requires Elixir v1.15 or later. Then add Redi as a dependency:
def deps do
[
{:redi, "~> 0.8"},
]
end
# application.ex
children = [
{:redi,
[:redi_dns, #
%{bucket_name: :dns, entry_ttl_ms: :timer.minutes(5)} ]}
]
then Redi can be used as:
:redi.set(Process.whereis(:redi_dns), "mykey", "a_value")
:redi.get(:dns, "mykey")