Schedule Background Job in Elixir via GenServer. Part 2: Run at most once an hour (day/minute/etc)
All parts:
- Schedule Backgroun Job in Elixir via GenServer. Part 1: Run at least once an hour (day/minute/etc)
- Schedule Background Job in Elixir via GenServer. Part 2: Run at most once an hour (day/minute/etc)
- Schedule Background Job in Elixir via GenServer. Part 3: Run at the specific time of day (week/month/etc): coming soon
- Schedule Background Job in Elixir via GenServer. Part 4: Run according to a complex schedule: coming soon
Some periodic jobs are vulnerable to the number of executions per time and should be run at most one time per interval.
Let’s improve our PeriodicTask GenServer from the first part! We can memorize the last run of the code and check how much time passed at the start.
I will save the timestamp in :dets
, but any other storage resistant to a process restart will do too. Remember that :ets
won’t work since it is bounded with the process and will be cleaned up after the process restart.
I will create the :periodic_task
table in dets:
{:ok, _} = :dets.open_file(:periodic_task, [])
and insert the record with :last_run_timestamp
id and current timestamp:
:dets.insert(
:periodic_task,
{:last_run_timestamp, DateTime.utc_now()}
)
At the start I would check if there is this record:
:dets.lookup(:periodic_task, :last_run_timestamp)
and if it exists, I gonna check if the time for the next run has come or how long should it wait till the next run:
case :dets.lookup(:periodic_task, :last_run_timestamp) do
[] ->
# run now [{:last_run_timestamp, last_run_timestamp}] ->
diff =
DateTime.diff(
DateTime.utc_now(),
last_run_timestamp,
:millisecond
) if diff >= @timeout do
# run now
else
timeout = @timeout - diff
end
end
The whole module:
Process flowchart:
The dotted area of the chart happens in theinit/1
callback.