When a GenServer performs database work, doing that work inside the server process can create a scalability bottleneck. A synchronous handler can spawn a worker, return {:noreply, state}, and defer the response until the worker finishes. section 7.3.4
def handle_call({:get, key}, caller, state) do
spawn(fn -> #1
data = case File.read(file_name(key)) do
{:ok, contents} -> :erlang.binary_to_term(contents)
_ -> nil
end
GenServer.reply(caller, data) #2
end)
{:noreply, state} #3
end
A runnable handle_call/3 pattern shows the worker reading data, sending it with GenServer.reply/2, and letting the server accept further requests. section 7.3.4
The pattern works as follows:
- Use the second handle_call/3 argument to retain the caller PID and unique request identifier. section 7.3.4
- Spawn a worker for the database operation, then call GenServer.reply/2 with the result. section 7.3.4
- Keep the server process responsive, but bound concurrency separately when simultaneous clients could overwhelm disk I/O. section 7.3.4
