Schedule Background Job in Elixir via GenServer. Part 2: Run at most once an hour (day/minute/etc)

Anastasiya Dyachenko
2 min readJan 3, 2021

All parts:

  1. Schedule Backgroun Job in Elixir via GenServer. Part 1: Run at least once an hour (day/minute/etc)
  2. Schedule Background Job in Elixir via GenServer. Part 2: Run at most once an hour (day/minute/etc)
  3. Schedule Background Job in Elixir via GenServer. Part 3: Run at the specific time of day (week/month/etc): coming soon
  4. 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.

--

--