Featured Deep Dive

Dilamme Scheduler

My most technically dense project. A production deployed background job scheduler, built from scratch including the data structures.

Live demo ↗API docs ↗GitHub ↗

The Problem

Most backend systems handle background jobs with a simple queue, jobs come in, jobs go out in order. That breaks down when you need priority levels, future scheduling, job dependencies, automatic retries, and protection against duplicate processing across workers. I wanted something that handles all of it properly, not just the happy path.

How It Works · Request Flow

01

Client sends POST /jobs with type, payload, priority (1 to 3), optional scheduled time, optional interval, and optional dependency IDs. Returns immediately.

02

Job persisted to PostgreSQL as pending. If scheduled for the future, it enters a futureJobIds tracking set, not the heap yet.

03

Scheduler ticks every second. Future jobs whose time has arrived are promoted into the MinHeap. Heap orders by priority, then scheduledAt, then createdAt.

04

Worker polls the heap every 2 seconds. Before claiming a job, it checks all dependencyIds are completed in the database. This is the DAG gate.

05

Job claimed via SELECT FOR UPDATE SKIP LOCKED inside a transaction. Atomic and duplicate claim proof across workers.

06

On success, status becomes completed. If recurring, next run auto schedules. On failure, retry with backoff. After 3 failures, dead letter queue.

The Heap Comparator

The MinHeap uses a three level comparator. In a min heap the smallest value rises to the top, so the comparator is designed so the most urgent job has the smallest value.

// level 1: priority (1=High beats 3=Low)
if (a.priority !== b.priority) return a.priority - b.priority

// level 2: earlier scheduledAt wins
if (aTime !== bTime) return aTime - bTime

// level 3: older job wins (FIFO)
return a.createdAt.getTime() - b.createdAt.getTime()

Starvation Prevention

The problem: a low priority job can wait forever if high priority jobs keep arriving. Classic starvation.

The fix, aging formula: every scheduler tick, each job's effective priority is recalculated.

effectivePriority = Math.max(1, originalPriority - Math.floor(waitMs / 30_000))

A low priority job waiting 30 seconds becomes medium. Waiting 60 seconds becomes high. Threshold is 30 seconds, documented and configurable.

Heap vs Timing Wheel · Benchmark

I implemented both algorithms and benchmarked them at 1,000 and 10,000 jobs.

MinHeap · 10k insert

11.7ms

TimingWheel · 10k insert

6.8ms ✓

MinHeap · 10k retrieval

21.6ms

TimingWheel · 10k retrieval

0.05ms ✓

MinHeap · 10k memory

649KB ✓

TimingWheel · 10k memory

956KB

The timing wheel wins on speed but cannot guarantee priority ordering within a time slot. The heap is used as the primary scheduler. The timing wheel would be better for millions of uniform recurring jobs where within window ordering does not matter.

“Idiomatic, considered, sharp.”

— Rust Cohort Demo Day judge feedback on RustQueue