Steps 1-3 built a store that lives in a variable. To
share it between processes it has to live in a
process. A GenServer holds the buckets as its state,
and every caller talks to it by name.
The pure data code doesn't change. The GenServer is
a thin shell around it, which keeps the logic easy
to test without processes.
1
defmoduleKV.Datadodefput(b,bucket,key,value),do:Map.update(b,bucket,%{key=>value},&Map.put(&1,key,value))defget(b,bucket,key),do:b|>Map.get(bucket,%{})|>Map.fetch(key)defdelete(b,bucket,key),do:Map.update(b,bucket,%{},&Map.delete(&1,key))enddefmoduleKV.ServerdouseGenServerdefstart_link(opts),do:GenServer.start_link(__MODULE__,%{},name:opts[:name]||__MODULE__)# Writes are calls too: the caller learns the write# happened before it continues, so a read right after# a write always sees it.defput(bucket,key,value),do:GenServer.call(__MODULE__,{:put,bucket,key,value})defget(bucket,key),do:GenServer.call(__MODULE__,{:get,bucket,key})defdelete(bucket,key),do:GenServer.call(__MODULE__,{:delete,bucket,key})@impltruedefinit(buckets),do:{:ok,buckets}@impltruedefhandle_call({:put,b,k,v},_from,s),do:{:reply,:ok,KV.Data.put(s,b,k,v)}defhandle_call({:get,b,k},_from,s),do:{:reply,KV.Data.get(s,b,k),s}defhandle_call({:delete,b,k},_from,s),do:{:reply,:ok,KV.Data.delete(s,b,k)}end{:ok,_}=KV.Server.start_link([])KV.Server.put("users","ada",:admin)IO.inspect(KV.Server.get("users","ada"),label:"get")
get: {:ok, :admin}
Now many processes share one store. 50 tasks write
concurrently; the GenServer applies them one at a
time, so none are lost.
2
1..50|>Enum.map(fni->Task.async(fn->KV.Server.put("counters","k#{i}",i)end)end)|>Task.await_many(){:ok,50}=KV.Server.get("counters","k50")IO.inspect(KV.Server.get("counters","k7"),label:"written by another process")KV.Server.delete("users","ada")IO.inspect(KV.Server.get("users","ada"),label:"after delete")
written by another process: {:ok, 7}
after delete: :error
Two problems remain. One crash in any bucket takes
the whole store down, and one busy bucket slows down
all the others because a single process serves
everything. Step 5 gives each bucket its own
supervised process.