Cron Scheduling

View Source

Shigoto includes a built-in cron scheduler that checks configured entries every minute and inserts jobs for matching schedules. On startup, it catches up on any intervals missed while the node was down (up to 60 missed minutes).

Configuration

Erlang (sys.config)

{shigoto, [
    {pool, my_app_db},
    {cron, [
        {<<"daily_cleanup">>, <<"0 2 * * *">>, cleanup_worker, #{<<"days">> => 30}},
        {<<"hourly_sync">>, <<"0 * * * *">>, sync_worker, #{}},
        {<<"weekday_report">>, <<"30 9 * * 1-5">>, report_worker, #{}}
    ]}
]}

Elixir (config.exs)

config :shigoto,
  pool: :my_app_db,
  cron: [
    {"daily_cleanup", "0 2 * * *", :cleanup_worker, %{"days" => 30}},
    {"hourly_sync", "0 * * * *", :sync_worker, %{}},
    {"weekday_report", "30 9 * * 1-5", ReportWorker, %{}}
  ]

Each cron entry is a 4-tuple {Name, Schedule, Worker, Args} or a 5-tuple {Name, Schedule, Worker, Args, Opts} where Opts is a map.

FieldTypeDescription
NamebinaryUnique identifier
Schedulebinary5-field cron expression
WorkermoduleWorker implementing shigoto_worker
ArgsmapArgs map passed to perform/1
OptsmapOptional. Supports timezone (see below)

Timezones

By default schedules are matched against UTC. Set a timezone in the entry's Opts map to match against a local wall-clock time instead:

{cron, [
    %% 09:00 Stockholm time, DST-aware, every weekday
    {<<"morning_report">>, <<"0 9 * * 1-5">>, report_worker, #{},
        #{timezone => <<"Europe/Stockholm">>}},
    %% fixed offset, no DST
    {<<"utc_plus_2">>, <<"0 3 * * *">>, sync_worker, #{}, #{timezone => 2}}
]}

timezone accepts:

ValueExampleDST
utc (default)utcn/a
integer hour offset2, -5no
"+/-N" or "UTC" binary<<"+2">>no
named IANA zone<<"Europe/Stockholm">>yes

Named IANA zones are resolved against the operating system's time zone database ($TZDIR or /usr/share/zoneinfo) for each instant, so daylight-saving transitions are handled automatically. If a named zone cannot be resolved the entry falls back to UTC and a warning is logged.

Cron Expression Syntax

Standard 5-field format: minute hour day-of-month month day-of-week

 minute (0-59)
  hour (0-23)
   day of month (1-31)
    month (1-12)
     day of week (0-6, Sunday=0)
    
* * * * *
SyntaxDescriptionExample
*Any value* * * * * (every minute)
NExact value30 * * * * (at minute 30)
N-MRange0 9-17 * * * (hours 9 through 17)
*/NStep*/15 * * * * (every 15 minutes)
N-M/SStep within range0-30/10 * * * * (minutes 0, 10, 20, 30)
N,M,OList0,15,30,45 * * * * (specific minutes)

Missed Cron Catch-Up

When the cron scheduler starts, it checks the last_scheduled_at for each entry in the database. If any intervals were missed (e.g., the node was down), it inserts jobs for up to 60 missed minutes. Jobs are deduplicated via unique constraints to prevent double-execution.

How It Works

  1. shigoto_cron gen_server checks all entries once per minute
  2. Matching entries trigger a job insert into the default queue
  3. Jobs use a 60-second unique constraint to prevent duplicates
  4. Cron jobs follow the same retry, backoff, and pruning rules as regular jobs

Timezone notes

Named zones resolve against the operating system's IANA database (/usr/share/zoneinfo), so every node in a cluster should run a consistent tzdata version. A zone that cannot be resolved (missing or corrupt tzdata) falls back to UTC with a logged warning rather than failing.

Cron matches at minute granularity under at-least-once semantics: on a DST fall-back the repeated wall-clock minute may match twice (unique-job de-dup within the 60s window prevents a duplicate enqueue), and on a spring-forward the skipped minute does not match. Schedule around 03:00 local to avoid the DST window if exact-once firing at those minutes matters.