Elixir Phoenix Cache

An implementation with ets.

Let’s start with implementation

defmodule SimpleCache do
  @table :simple_cache

  def init(_) do
    :ets.new(@table, [
      :set,
      :named_table,
      :public,
      read_concurrency: true,
      write_concurrency: true
    ])

    {:ok, %{}}
  end

  def start_link do
    GenServer.start_link(__MODULE__, [], name: __MODULE__)
  end

  def fetch(key, expires_in_seconds, fun) do
    case lookup(key) do
      {:hit, value} ->
        value

      :miss ->
        value = fun.()
        put(key, expires_in_seconds, value)
        value
    end
  end

  defp lookup(key) do
    case :ets.lookup(@table, key) do
      [{^key, expires_at, value}] ->
        case now < expires_at do
          true -> {:hit, value}
          false -> :miss
        end

      _ ->
        :miss
    end
  end

  defp put(key, expires_in_seconds, value) do
    expires_at = now + expires_in_seconds
    :ets.insert(@table, {key, expires_at, value})
  end

  defp now do
    :erlang.system_time(:seconds)
  end
end


Update application.ex

 def start(_type, _args) do
    import Supervisor.Spec

    children = [
      supervisor(SimpleCache, [])
    ]
    opts = [strategy: :one_for_one, name: Supervisor]
    Supervisor.start_link(children, opts)
  end

Finally, use it

    cache_for_seconds = 60
    key = 'key'

    SimpleCache.fetch(key, cache_for_seconds, fn ->
      {:ok, some_expensive_operation}
    end)

Relavent links:
https://stackoverflow.com/questions/35218738/caching-expensive-computation-in-elixir
https://dockyard.com/blog/2017/05/19/optimizing-elixir-and-phoenix-with-ets

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.