Skip to content

Latest commit

 

History

History
98 lines (70 loc) · 2.11 KB

README.md

File metadata and controls

98 lines (70 loc) · 2.11 KB

Erlang REDI: an ETS cache without REDIS

Copyright (c) 2019-24 Renaud Mariana.

hex.pm badge Erlang CI

Erlang Redi

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.

Example:

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">>).
...

Performance

Excerpt from a run of the unit test (i5-1235U)

  • throughput 363464 writes/s.
  • throughput 3478260 reads/s.

Registering to keys that are GCed

redi:gc_client(Redi_Pid, self(), Opts).

receive
	{redi_gc, _, Keys} -> ok

%% see more examples in unit tests

Elixir

Redi requires Elixir v1.15 or later. Then add Redi as a dependency:

def deps do
  [
    {:redi, "~> 0.8"},
  ]
end

Starting Redi inside a supervisor :

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