Hi all, I’m wondering if I can get some advice on basic OTP design:
I need to batch send API requests in batches of 5 but no request should wait for more than 10 seconds before being sent.
I have a flawed working version - a batch_server GenServer whose state is a map with a reference id key and value is a list of api tasks:
state = %{batch_id => [ task_1, task_2 ... ]}
The batch_id would allow us to identify the 10 second timeout via a handle_info call:
def handle_call({:new_task, task}, _from, state) do
current_batch = Enum.find(state, fn {k, v} -> length(v) < 5 end)
case current_batch do
nil ->
batch_id = gen_random_id()
Process.send_after(self(), {:batch_timeout, batch_id}, 10 * 1000)
{:reply, :ok, Map.put(state, batch_id, [task]}
{batch_id, tasks} when length(tasks) == 4 ->
# SYNCHRONOUSLY send batched API request
{batch_id, tasks} ->
# Map.update!(state, batch_id, & [task | &1])
end
end
def handle_info({:batch_timeout, batch_id}, state) do
if Map.get(state, batch_id) do
# SYNCHRONOUSLY send batched API request
else
# Current batch was already sent as it reached batch limit of 5
end
end
This works fine, but the design is flawed because the API call is synchronous and therefore blocks my GenServer.
I’m looking for an alternative way around this - I was wondering if I should create a new BatchServer instance for each new batch and parent GenServer who keeps a register of BatchServers and can therefore allocate tasks to a running BatchServer or spin up a new one if there are none open.
Any advice apprciated!