Current section

Files

Jump to
phoenix_analytics lib phoenix_analytics services batcher.ex
Raw

lib/phoenix_analytics/services/batcher.ex

defmodule PhoenixAnalytics.Services.Batcher do
@moduledoc """
A GenServer module for batching and inserting RequestLog entries into the database.
This module provides functionality to efficiently insert RequestLog entries by batching them
and inserting them in bulk. It uses a GenServer to manage the state of the batch and
periodically flush the batch to the database.
The batch is processed and inserted into the database either when it reaches 1_000 logs
or after 1 second, whichever comes first.
## Usage
To insert a RequestLog:
PhoenixAnalytics.Services.Batcher.insert(%PhoenixAnalytics.Entities.RequestLog{})
Alternatively, you can send an event to PubSub for request_log insertion:
PhoenixAnalytics.Services.PubSub.broadcast(:request_sent, %PhoenixAnalytics.Entities.RequestLog{})
The module will handle batching and inserting the logs automatically.
This approach allows for handling distributed app scenarios.
"""
use GenServer
alias PhoenixAnalytics.Services.PubSub
alias PhoenixAnalytics.Queries
alias PhoenixAnalytics.Repo
@batch_size 1_000
@timeout 1_000
# --- client callbacks ---
@doc false
def start_link(_) do
GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
end
@doc """
Inserts a RequestLog into the batch queue.
The batch is processed and inserted into the database either when it reaches 1_000 logs or after 1 second, whichever comes first.
## Parameters
- request_log: A PhoenixAnalytics.Entities.RequestLog struct to be inserted.
## Examples
iex> PhoenixAnalytics.Services.Batcher.insert(%PhoenixAnalytics.Entities.RequestLog{})
Note: You can also use PubSub to insert a RequestLog, which is useful for distributed app scenarios:
iex> PhoenixAnalytics.Services.PubSub.broadcast(:request_sent, %PhoenixAnalytics.Entities.RequestLog{})
"""
def insert(request_log) do
GenServer.cast(__MODULE__, {:insert, request_log})
end
# --- server callbacks ---
@doc false
@impl true
def init(_state) do
PubSub.subscribe()
:timer.send_interval(@timeout, :check_batch)
{:ok, %{batch: [], last_insert_time: :os.system_time(:millisecond)}}
end
@doc false
@impl true
def handle_cast({:insert, request_log}, state) do
# as for batch insert we use appender, there is no need for query
{_, params} = Queries.Insert.insert_one(request_log)
new_batch = [params | state.batch]
if length(new_batch) >= @batch_size do
send_batch(new_batch)
{:noreply, %{state | batch: [], last_insert_time: :os.system_time(:millisecond)}}
else
{:noreply, %{state | batch: new_batch}}
end
end
@doc false
@impl true
def handle_info({:request_sent, request_log}, state) do
GenServer.cast(__MODULE__, {:insert, request_log})
{:noreply, state}
end
@doc false
@impl true
def handle_info(:check_batch, state) do
current_time = :os.system_time(:millisecond)
time_diff = current_time - state.last_insert_time
if time_diff >= @timeout && length(state.batch) > 0 do
send_batch(state.batch)
{:noreply, %{state | batch: [], last_insert_time: current_time}}
else
{:noreply, state}
end
end
@doc """
Sends a batch of RequestLogs to be inserted into the database.
## Parameters
- batch: A list of PhoenixAnalytics.Entities.RequestLog structs to be inserted.
## Examples
iex> PhoenixAnalytics.Services.Batcher.send_batch([%PhoenixAnalytics.Entities.RequestLog{}, %PhoenixAnalytics.Entities.RequestLog{}])
"""
def send_batch([]), do: []
def send_batch(batch), do: Repo.insert_many(batch)
end