lcabrini

lcabrini

What would be the “proper” way to implement a simple persistent key/value store?

What would be the “proper” way to implement a simple persistent key/value store? Settings that are read far more than they are written. In Go I used a single postgres table and on application start-up I read it into to a mutex-backed struct. The write method would persist the setting in the database.

I wouldn’t necessarily need to use Postgres as a backing store, but that way the settings get backed up with the rest of the data. That being said, I’m not against using a non-postgres backing store, if that makes sense.

I’ve looked a bit into ETS, Mnesia, I also read somewhere that it would be a good idea to build a GenServer around a key/value store. I feel a bit overwhelmed by the choices and would like a pointer in the right direction. Thank you.

Most Liked

garrison

garrison

If you’re using Postgres, use Postgres. Otherwise use SQLite. Create a table with binary keys/values and then use ETS as a write-through cache.

Always write to the DB first and then the cache before returning from the write function. Always read from the cache.

def put(key, value) do
  Repo.insert! %Row{key: key, value: value}
  :ets.insert(@table, {key, value})
end

def get(key) do
  case :ets.lookup(@table, key) do
    [{^key, value}] -> value
    [] -> nil
  end
end

Load the rows from the DB into the cache at startup. If you want to use arbitrary terms just encode them with term_to_binary().

Edit: the put() function above is only correct for a single writer, meaning it cannot be used from multiple processes without synchronization. See @Asd 's more thorough answer below for an example that uses a GenServer as a single writer to serialize writes.

Asd

Asd

Yeah, you’re right, if its a single writer, then your code is correct.

Why is it always the same people in KV DB topics :smile: . Happy new year btw!

Asd

Asd

I’d do this like this if you want it backed by postgres table

defmodule Table do
  @moduledoc "ets table backed by postgres table"

  use GenServer

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

  def init(_opts) do
    table = :ets.new(:table_name, [:protected, :set, :named_table])
    entries = for %{key: k, value: v} <- Repo.all(Table), do: {k, v}
    :ets.insert(table, entries)
    {:ok, %{table: table}}
  end

  def handle_call({:write, key, value}, _from, %{table: table}) do
    Repo.insert!(%Table{key: key, value: value})
    :ets.insert(table, {key, value})
  end

  def read(key) do
    case :ets.lookup(:table_name, key) do
      [{_, value}] -> {:ok, value}
      _ -> :error
    end
  end

  def write(key, value) do
    GenServer.call(Table, {:write, key, value})
  end
end

But if you’re okay with just a file on disk, I’d consider using a dets


However, I am going to release much more performant persistent LVM KV db in the upcoming months, so I will reply here again once it’s ready

Last Post!

cmo

cmo

Unless someone edits the database directly, the cache shouldn’t get out of sync with the database in that design.

I would not bother with the cache just yet unless you’re reading these values rapidly. These reads are going to be very quick.

Where Next?

Popular in Questions Top

JeremM34
Hello, how can I check the Phoenix version ? Thanks !
New
RisingFromAshes
I’ve read in another post that it may be possible with a router helper - but I couldn’t find an appropriate one, and tbh, I’m still just ...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New

Other popular topics Top

Qqwy
Update: How to use the Blogs &amp; Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3271 130286 1222
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 36654 110
New
JeremM34
Hello, how can I check the Phoenix version ? Thanks !
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
sorentwo
Hello! tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability. After spen...
985 44532 311
New
sergio
Kind of like when jquery came out, it was super necessary. Existing drag and drop libraries have a bunch of baggage to support old browse...
New

We're in Beta

About us Mission Statement