Reads in step 5 went through a process mailbox, so
a slow write delayed every reader. Most stores read
far more than they write. Here, writes still go
through one GenServer (so they're ordered), but reads
go straight to a :protected ETS table: every
process can read it, only the owner can write.
1
defmoduleKV.CachedouseGenServer@table__MODULE__defstart_link(_),do:GenServer.start_link(__MODULE__,nil,name:__MODULE__)# Reads: no message, no waiting on the GenServer.# Rows are `{{bucket, key}, value}`.defget(bucket,key)docase:ets.lookup(@table,{bucket,key})do[{_,value}]->{:ok,value}[]->:errorendend# Writes: serialised through the owner process.defput(bucket,key,value),do:GenServer.call(__MODULE__,{:put,bucket,key,value})# A bucket's keys, using a match on the first half of# the composite key.defkeys(bucket)do@table|>:ets.match({{bucket,:"$1"},:_})|>List.flatten()|>Enum.sort()end@impltruedefinit(nil)do# A *named* table, readable by everyone, tuned for# many concurrent readers.:ets.new(@table,[:set,:protected,:named_table,read_concurrency:true]){:ok,nil}end@impltruedefhandle_call({:put,b,k,v},_from,state)do:ets.insert(@table,{{b,k},v}){:reply,:ok,state}endend{:ok,_}=KV.Cache.start_link([])KV.Cache.put("users","ada",:admin)KV.Cache.put("users","grace",:editor)KV.Cache.put("flags","dark_mode",true)IO.inspect(KV.Cache.get("users","ada"),label:"get")IO.inspect(KV.Cache.get("users","joe"),label:"missing")IO.inspect(KV.Cache.keys("users"),label:"users keys")