Skip to content

Instantly share code, notes, and snippets.

@bluzky
Created November 12, 2021 13:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bluzky/849da85e4a872e3d0c6eaab487bfee19 to your computer and use it in GitHub Desktop.
Save bluzky/849da85e4a872e3d0c6eaab487bfee19 to your computer and use it in GitHub Desktop.
Elixir redis cache
defmodule Toolkit.Cache do
@moduledoc """
Cache provide some helper method to do the cache thing
Backend could be any. Currently using Redis via :redix library
"""
# TODO: move this to config
@redix_conn :octosells_cache
# 512kb
@max_cache_size 512 * 1024
@doc """
Cache a value with given key
"""
@spec set(String.t(), String.t()) :: :ok | {:error, any()}
def set(key, value, timeout_sec \\ nil) do
if timeout_sec do
set_ttl(key, value, timeout_sec)
else
with {:ok, value} <- serialize_data(value),
{:ok, _} <- Redix.command(@redix_conn, ["SET", key, value]) do
:ok
end
end
end
@doc """
Cache a value with given key and timeout(in second)
"""
@spec set(String.t(), String.t(), timeout_sec :: integer()) :: :ok | {:error, any()}
def set_ttl(key, value, timeout_sec) do
with {:ok, value} <- serialize_data(value),
{:ok, _} <- Redix.command(@redix_conn, ["SETEX", key, timeout_sec, value]) do
:ok
end
end
@doc """
Get cached value by key
"""
@spec get(String.t()) :: {:ok, String.t()} | {:error, :not_found} | {:error, any()}
def get(key) do
case Redix.command(@redix_conn, ["GET", key]) do
{:ok, nil} -> {:error, :not_found}
{:ok, value} -> deserialize_data(value)
error -> error
end
end
@doc """
Get cached value by key. If key is not cached, then execute given function, cache new value and return the value.
"""
@spec get_or_set(String.t(), function(), timeout_sec :: integer()) ::
{:ok, String.t()} | {:error, any()}
def get_or_set(key, func, ttl \\ nil) when is_function(func) do
case get(key) do
{:error, _} ->
case func.() do
{:ok, value} = rs ->
set(key, value, ttl)
rs
{:error, _} = error ->
error
value ->
set(key, value, ttl)
{:ok, value}
end
{:ok, value} ->
{:ok, value}
end
end
@doc """
Delete a cache value by key
"""
def delete(key) do
Redix.command(@redix_conn, ["DEL", key])
end
# we remove struct and meta to reduce size
defp serialize_data(value) do
value =
if is_map(value) do
Map.drop(value, [:__struct__, :__meta__])
else
value
end
data = :erlang.term_to_binary(value)
if byte_size(data) > @max_cache_size do
{:error, "Not allow cache value > 512kb"}
else
{:ok, data}
end
end
defp deserialize_data(data) do
try do
{:ok, :erlang.binary_to_term(data)}
rescue
_ -> {:error, "Bad data"}
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment