A system that:
The hardes non-functional constraint is the at least once run under failures.
Caller: UI + internal services
As a minimal surface API for now:
POST /tasks
{schedule, payload} -> {task_id}
PUT /tasks/{task_id}
{schedule, payload}
DELETE /tasks/{task_id}
GET /tasks/{task_id}
{status, last_run, next_run}
As shown in the high-level diagram, UI and APIs interact with the Scheduler Service, which persists tasks and dispatches them to workers via a queue.
Task {
task_id,
schedule,
payload,
status,
next_run_at,
attempts,
created_at
}
Problem
Multiple dispatcher instances may pick the same due task, causing duplicate execution.
Solution
Dispatchers atomically claim tasks using a state transition (SCHEDULED → IN_FLIGHT) with a lease (locked_until).
Only successfully claimed tasks are enqueued for execution.
Trade-off
Slightly higher DB contention
Strong correctness guarantees (prevents double dispatch)
Problem
Recurring tasks must be rescheduled reliably without drifting or missing executions.
Solution
After execution, compute next_run_at based on the cron expression.
Persist the next execution time atomically with the execution result.
Trade-off
More scheduler logic
Simple, predictable recurring behaviour with durability