Current section
Files
Jump to
Current section
Files
lib/cache.ex
defmodule Kdb.Cache do
defstruct [:name, :ttl, :t]
@type t :: %__MODULE__{
name: atom(),
ttl: integer(),
t: any()
}
@unit_time :millisecond
@key_delete :delete
def new(opts) do
name = Keyword.get(opts, :name) || make_ref()
case Kdb.Registry.get_cache(name) do
nil ->
t =
:ets.new(__MODULE__, [
:set,
:public,
read_concurrency: true,
write_concurrency: true
])
ttl = Keyword.get(opts, :ttl, :infinity)
cache = %__MODULE__{name: name, ttl: ttl, t: t}
public = Keyword.get(opts, :public, true)
if public do
Kdb.Registry.register(cache)
end
cache
cache ->
cache
end
end
@spec put(
cache :: t(),
bucket :: atom(),
key :: binary(),
value :: term()
) ::
boolean()
def put(%__MODULE__{t: t, ttl: :infinity = ttl}, bucket, key, value) do
:ets.insert(t, {{bucket, key}, value, ttl})
end
def put(%__MODULE__{t: t, ttl: ttl}, bucket, key, value) do
:ets.insert(t, {{bucket, key}, value, now() + ttl})
end
@spec update_counter(
cache :: t(),
bucket_name :: atom(),
key :: binary(),
amount :: integer(),
default :: term()
) ::
integer()
def update_counter(%__MODULE__{t: t, ttl: ttl}, bucket_name, key, amount, default) do
id = {bucket_name, key}
:ets.update_counter(t, id, {2, amount}, {id, default, ttl})
end
@spec get(cache :: t(), bucket_name :: atom(), key :: binary()) :: term() | nil
def get(%__MODULE__{t: t}, bucket, key) do
case :ets.lookup(t, {bucket, key}) do
[{_key, @key_delete, _timestamp}] ->
@key_delete
[{_key, value, _timestamp}] ->
value
_ ->
nil
end
end
def has_key?(%__MODULE__{t: t}, bucket, key) do
case :ets.lookup(t, {bucket, key}) do
[{_key, @key_delete, _timestamp}] ->
false
[{_key, _value, _timestamp}] ->
true
_ ->
nil
end
end
@spec update(
cache :: t(),
bucket :: atom(),
key :: binary(),
value :: term(),
default :: term()
) ::
boolean()
def update(%__MODULE__{t: t, ttl: ttl}, bucket, key, value, default) do
:ets.update_element(t, {bucket, key}, {2, value}, {{bucket, key}, default, ttl})
end
@spec delete(cache :: t(), atom(), binary()) :: true
def delete(%__MODULE__{t: t}, bucket, id) do
# :ets.delete(t, {bucket, id})
:ets.insert(t, {{bucket, id}, @key_delete, 0})
end
defp now do
:os.system_time(@unit_time)
end
def cleanup(older_than) do
tid = :ets.whereis(Kdb.Registry)
:ets.foldl(
fn
{{:batch, _batch_id}, %{cache: cache}}, acc ->
if cache.ttl != :infinity do
acc + cleanup(cache, older_than)
else
acc
end
_, acc ->
acc
end,
0,
tid
)
end
@spec cleanup(batch :: any(), older_than :: integer()) :: integer()
def cleanup(%Kdb.Cache{t: tid}, older_than) do
n =
:ets.foldl(
fn
{key, @key_delete, _}, acc ->
:ets.delete(tid, key)
acc + 1
{key, _value, readed_at}, acc when readed_at < older_than ->
:ets.delete(tid, key)
acc + 1
_, acc ->
acc
end,
0,
tid
)
n
end
end