Featured Deep Dive
Dilamme Scheduler
My most technically dense project. A production deployed background job scheduler, built from scratch including the data structures.
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
Client sends POST /jobs with type, payload, priority (1 to 3), optional scheduled time, optional interval, and optional dependency IDs. Returns immediately.
Job persisted to PostgreSQL as pending. If scheduled for the future, it enters a futureJobIds tracking set, not the heap yet.
Scheduler ticks every second. Future jobs whose time has arrived are promoted into the MinHeap. Heap orders by priority, then scheduledAt, then createdAt.
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.
Job claimed via SELECT FOR UPDATE SKIP LOCKED inside a transaction. Atomic and duplicate claim proof across workers.
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