ETS created on Application init

Hi, I have been using ETS a lot in a vehicle tracking app. After noticing the ArgumentError when a parent process of ETS table terminates, the ETS tables creation are now mostly done on the Application init process.

Would you see any risk with this approach other than the encapsulation? I had to change them to :public but it didn’t affect performance. I’m curious if this is a common practice because if you have a cache why not avoid loosing its data while the modules comply to the let it crash.

A common solution to keep ETS tables around is to create a GenServer with no other purpose than to encapsulate the table. You can avoid running code on that server, but still neatly expose module functions to access it. Here’s a very simple example

defmodule Wrapper do
  use GenServer

  def init(arg) do
    :ets.new(:wrapper, [
      :set,
      :public,
      :named_table,
      {:read_concurrency, true},
      {:write_concurrency, true}
    ])

    {:ok, arg}
  end

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

  def get(key) do
    case :ets.lookup(:wrapper, key) do
      [] ->
        nil

      [{_key, value}] ->
        value
    end
  end

  def put(key, value) do
    :ets.insert(:wrapper, {key, value})
  end
end

The concurrency options should be tweaked to your use case, but the access level has to be :public like you noticed.

Put the GenServer in your application supervision tree and then usage is

Wrapper.put("key", "value")
Wrapper.get("key") # "value"

Even though it’s been wrapped in a GenServer all the operations run in the calling process, making it fully concurrent (assuming the correct options are set at table creation).

7 Likes

Another common technique is to have a GenServer create the ETS table, set the heir and gives it away to another GenServer that does the work. See GiveAway

3 Likes

Another possible solution is to use a DETS. DETS saves the state of the ETS on disk, so even if you lose the ETS table you can always restore it back.

http://erlang.org/doc/man/dets.html

All of the above ideas work. I often create ETS or Mnesia tables in supervisors because you can be sure that code will run before anything it starts so it is a good place for it. Or you can start a GenServer that only holds the ETS table, which also means you have a natural place for update code should you need it later

The hex package external is good for keeping ets tables alive and it uses the Genserver + :heir option to do just that.

3 Likes

Hi, it would be a good idea but the data is already saved in database for another purpose so extra IO will be avoided. Thanks

Hi zkessin, supervisors seems interesting in the top ones. Have you seen an ETS crash so that you decided doing it in the supervisor instead of Application init process?

That’s it kip! Thanks man! The benefit of GiveAway pointed by @tty keeping the concurrency on ETS’s hands like @jola example.

Thank you guys!