defmodule Glific.Caches do
@moduledoc """
Glific Cache management
"""
@cache_bucket :glific_cache
@behaviour Glific.Caches.CacheBehaviour
# set timer limit
@ttl_limit 24
@doc false
@impl Glific.Caches.CacheBehaviour
@spec set(non_neg_integer, any(), any(), Keyword.t()) :: {:ok, any()}
def set(organization_id, key, value, opts \\ [])
def set(organization_id, keys, value, opts) when is_list(keys),
do: set_to_cache(organization_id, keys, value, opts)
@impl Glific.Caches.CacheBehaviour
def set(organization_id, key, value, opts),
do: set_to_cache(organization_id, [key], value, opts)
@doc false
@spec set_to_cache(non_neg_integer, list(), any, Keyword.t()) :: {:ok, any()}
defp set_to_cache(organization_id, keys, value, opts) do
keys = Enum.reduce(keys, [], fn key, acc -> [{{organization_id, key}, value} | acc] end)
# also update the reload key for consumers to refresh caches
keys = [{{organization_id, :cache_reload_key}, Ecto.UUID.generate()} | keys]
default_opts = [ttl: :timer.hours(@ttl_limit)]
{:ok, true} = Cachex.put_many(@cache_bucket, keys, Keyword.merge(default_opts, opts))
{:ok, value}
end
@doc """
Get a cached value based on a key
"""
@impl Glific.Caches.CacheBehaviour
@spec get(non_neg_integer, any(), Keyword.t()) :: {:ok, any()} | {:ok, false}
def get(organization_id, key, opts \\ []) do
case Cachex.exists?(@cache_bucket, {organization_id, key}) do
{:ok, true} ->
refresh_cache = Keyword.get(opts, :refresh_cache, true)
if refresh_cache, do: Cachex.refresh(@cache_bucket, {organization_id, key})
Cachex.get(@cache_bucket, {organization_id, key})
_ ->
{:ok, false}
end
end
@doc """
Get a cached value based on a key with fallback
"""
@impl Glific.Caches.CacheBehaviour
@spec fetch(non_neg_integer, any(), (any() -> any())) ::
{:ok | :error | :commit | :ignore, any()}
def fetch(organization_id, key, fallback_fn) do
Cachex.fetch(@cache_bucket, {organization_id, key}, fallback_fn)
end
@doc """
Remove a value from the cache
"""
@impl Glific.Caches.CacheBehaviour
@spec remove(non_neg_integer, list()) :: any()
def remove(organization_id, keys),
do:
Enum.map(keys, fn key ->
{:ok, _} = Cachex.del(@cache_bucket, {organization_id, key})
end)
@doc """
Set a global value, ttl is in number of hours
For global keys, we expect relatively short ttls
"""
@spec put_global(any, any, non_neg_integer) :: {:ok | :error, boolean()}
def put_global(key, value, ttl),
do: Cachex.put(@cache_bucket, {:global, key}, value, ttl: :timer.hours(ttl))
@doc """
Retrieve a global value from the cache global scope
"""
@spec get_global(any()) :: {:ok | :error, any()}
def get_global(key),
do: Cachex.get(@cache_bucket, {:global, key})
end