Scheduler (cron, Airflow, Temporal timers)
Runs jobs at a time or interval — the simplest form of eventual asynchronous computation.
Why it exists
Some work must happen periodically: daily reports, weekly billing runs, hourly cache warmups, monthly cleanup. cron on a single machine works for small scale — but at scale, you need distributed schedulers that survive node failure, guarantee at-least-once (or exactly-once) execution, retry on failure, and give you observability (did the job run? how long? what did it produce?).
How it works
A scheduler reads job definitions (schedule + task) and triggers execution when the time arrives. For distributed schedulers, one node holds the 'is scheduler' lease and is the sole trigger (avoids double-fire). Complex schedulers (Airflow, Prefect) support DAGs — jobs that depend on other jobs. Advanced schedulers (Temporal, Cadence) support durable execution: the workflow's state is checkpointed to a database so it survives worker crashes and can wait days for a subsequent step.
Scaling characteristics
Simple schedulers (cron on one box) handle any single-node workload; the limit is worker fan-out, not the scheduler itself. Distributed schedulers scale by adding workers, keeping the scheduler thin. Airflow can trigger millions of jobs/day; Temporal handles millions of long-running workflows concurrently. Cost: scheduler state (DB) is small; worker pools scale linearly with job load.
When to use it
- Recurring reports and rollups
- Nightly ETL jobs
- Warmup or precomputation tasks (cache warming, feature computation)
- Delayed messages (send a follow-up email in 24 hours)
- Timeouts (a workflow that pauses for a user response, gives up after 7 days)
- Backfills of historical data
When NOT to use it
- Event-triggered work — use a queue, not a scheduler
- Sub-second recurrence — cron and most schedulers are minute-granular
- Ad-hoc one-off scripts — engineer running them manually is fine
Failure modes
- Double execution: two nodes both think they're the scheduler. Mitigate with distributed lock + heartbeat.
- Missed execution: scheduler node was down when the trigger time passed. Mitigate with catch-up mode (Airflow) or run-once semantics.
- Long-running job blocks the next scheduled run — always set a timeout + concurrency limit per job
- Job depends on data that isn't ready yet — Airflow has sensors; Temporal has waitFor
- Cron drift on a single machine (system clock issues) causes silent misfires
Alternatives
- cron on a single box — simplest for tiny scale
- Kubernetes CronJob — cron with pod isolation and retry; great for containerized workloads
- Airflow — for DAG-based ETL; heavy but powerful
- Temporal — for long-running, stateful workflows with retries built in
- AWS EventBridge / GCP Cloud Scheduler — managed cron for cloud native
Interview questions
- How do you prevent a scheduled job from running twice when you have 3 scheduler nodes?
- A daily job takes 26 hours some days and overlaps with the next run. What's your fix?
- Design a system to send 'follow-up in 24 hours' emails. Where does the scheduling state live?
- How would you retry a failed job — immediately, with backoff, or from a specific checkpoint?
- Explain how Airflow handles a DAG where one task fails.
- What's the difference between an idempotent job and one that just retries safely?
Systems that use this component
See how the real designs on this platform put scheduler to work — concrete usage context per system.
1975, AT&T: how Unix taught computers to keep an appointment
In 1975, Brian Kernighan and colleagues at Bell Labs shipped Unix v6 with a program called cron — from the Greek chronos, time. It read a text file describing "at 2AM run this script," and did so. Fifty years later, the same syntax runs in every Docker container, every Kubernetes CronJob, every cloud scheduler. 0 2 * * * means the same thing in 2024 as it did in 1975. Few pieces of software have this longevity.
The problem grew as computing distributed. A single-machine cron is trivial. A distributed scheduler — one that runs jobs across many machines, handles crashes, guarantees no-duplicate-runs, and coordinates across time zones — is very hard. Quartz (2001, Java) tried. Google's internal Borg (2003, publicly described 2015) built massive fleet-scheduling. Kubernetes CronJob (2016) brought K8s-native scheduling. And AWS EventBridge Scheduler (2022) offered serverless triggers at cloud scale.
Modern schedulers face problems Kernighan didn't: what if the machine is down at 2AM (missed fires)? What if two machines both fire? What time zone applies for a user in Tokyo? What if the job takes 6 hours and the next run is due before it finishes? Each of these is a distinct design decision, and every scheduler picks differently.
The core insight: a scheduler is "when to run," not "how to run." It fires an event; something else (a queue, a workflow engine, a K8s pod) does the work. Keep the scheduler simple + reliable, and complexity lives in the actual job. Confuse the two and you get either a fragile scheduler or a bloated job runner. The best modern designs (Temporal schedules, AWS EventBridge Scheduler, K8s CronJob) keep the concern surface small: they say "time is now" and delegate everything else.
Historical timeline
- 1975Unix cron v6Brian Kernighan et al. at Bell Labs. Reads text file, fires jobs. Syntax survives 50 years.
- 1987Paul Vixie's Vixie cronThe cron implementation that shipped in BSD + Linux. Sets the standard for decades.
- 2001Quartz Scheduler (Java)Enterprise Java scheduling library. Cron syntax + calendar rules + persistence. Powers thousands of Java apps.
- 2003Google Borg (internal)Massive-scale cluster + job scheduling. Publicly described in 2015 paper. Ancestor of Kubernetes.
- 2010Apache Oozie for HadoopWorkflow + scheduler for Hadoop. Complex XML config. Batch ETL era.
- 2011Chronos (Airbnb, later Mesos)Distributed cron built on Mesos. Handles cluster-scale scheduling. Fault-tolerant.
- 2015Apache AirflowAirbnb releases Airflow — DAG scheduling for data. Scheduler + orchestrator hybrid.
- 2016Kubernetes CronJobK8s brings cron into container era. Automatic pod creation, retries, ttl.
- 2017AWS CloudWatch Events SchedulerAWS adds rule-based scheduling. Trigger Lambda, ECS, Step Functions on schedule.
- 2019Cloudflare Cron TriggersCloudflare Workers get cron triggers. Sub-minute serverless scheduling at edge.
- 2020GCP Cloud SchedulerGoogle's managed cron service. Guaranteed at-least-once delivery.
- 2022AWS EventBridge SchedulerAWS replaces CloudWatch Events Scheduler with EventBridge — millions of scheduled events, one-time or recurring.
- 2023Temporal Schedules GATemporal adds first-class scheduling on top of durable workflows. Cron-style + interval, with backfill.
- 2024AI scheduling patterns emergeTime-based prompt scheduling. Agent triggers on cron. LLMs consume + emit scheduled events.
Four schedule types — pick your firing pattern
Not every scheduled job wants the same trigger. The four common patterns:
Cron expression
The classic. 5-field: minute, hour, day-of-month, month, day-of-week. Extend to 6 with seconds (Quartz).
Best for: daily/hourly/weekly reports, backups, cleanup.
Fixed interval
Simpler than cron for periodic tasks that don't care about calendar alignment.
Best for: health checks, metric flushes, cache refreshes.
One-time (at)
Fire exactly once at a specific timestamp. Unix at command. AWS EventBridge Scheduler one-time.
Best for: delayed jobs, reminders, scheduled emails, release windows.
Event + delay
Fire N hours after some event. Combines with event-driven system. Temporal timers, SQS delay queues.
Best for: onboarding sequences, follow-ups, delayed retries.
0 0 15 * MON fires the 15th of any month OR any Monday — not "Monday the 15th." Reason for many missed backups.Scheduling algorithms — how the wheel actually turns
Once you have N million scheduled jobs, "check every one every second" is unaffordable. The classic algorithms:
Sorted min-heap (priority queue)
Store all jobs by next-fire-time in a min-heap. Peek at head. Sleep until that time. Fire, reschedule, re-insert. O(log n) per operation.
Best for: single-machine schedulers, thousands to millions of jobs. Node.js setTimeout uses this.
Timing wheel (hashed wheel)
Ring of buckets, one per time unit. Insert into bucket for next-fire tick. Advance pointer. Fire everything in current bucket. O(1) insert.
Best for: massive-scale short-timers (network stacks, Kafka clients). Netty, Kafka use this.
DB polling with next_fire_time index
Store jobs in DB. Query "SELECT * WHERE next_fire_time <= NOW()" every N seconds. Fire, update next_fire_time.
Best for: distributed schedulers (Quartz, DB-backed cron). Trades latency for simple horizontal scale.
Delay queue (SQS-style)
Push messages with a visibility delay. Broker keeps them invisible until due. Then poll picks them up.
Best for: fire-and-forget delayed triggers. Millions of one-time schedules.
Time zones + DST + leap seconds: the scheduler's nightmare
Cron looks simple until you have global users. "Fire at 9AM Tokyo time" on a machine set to UTC. DST changes twice a year. Leap seconds happen. Time-zone rules change politically. Each is a well-known production bug.
Time-zone bug
A daily report scheduled at "9AM UTC" hits US-East at 5AM (fine) but hits Tokyo at 18:00 (business already ended). Store schedule with an explicit IANA time zone (Asia/Tokyo).
DST double-fire / skip
Spring forward: 2:00-2:59 doesn't exist. Fall back: 1:00-1:59 happens twice. Cron at 2:30AM misses in spring; fires twice in fall. Use tools that respect DST rules (Quartz Calendar, tz-aware cron).
Leap second confusion
2016: a leap-second broke Cloudflare's Go time API — negative durations. Modern advice: rely on smeared clocks (Google, AWS, Cloudflare all smear leap seconds across ~24h).
Clock skew between coordinator + workers
If coordinator's clock is 30s ahead, jobs fire early on the workers. If behind, late. NTP + PTP + clock quality checks.
Cron vs modern managed schedulers — what did we gain?
Unix cron works. But it has real limits in modern environments:
Unix cron
- + Simple, tested for 50 years
- + Free, no infra
- - Single-machine (machine down = missed jobs)
- - No retry, no observability
- - No time zones (uses machine local time)
- - Fire-and-forget (no history)
Managed scheduler (EventBridge, K8s CronJob, Temporal)
- + Distributed — no single point of failure
- + Retries + observability + history
- + Time-zone aware
- + Backfill for missed runs
- + Concurrent policy (skip / queue / replace)
- - Cost + vendor dependency
Product comparison
| Product | Model | Strength | Weakness |
|---|---|---|---|
| Unix cron | OSS | 50-year proven syntax. Zero infra. | Single-machine. No retry. No observability. |
| Kubernetes CronJob | OSS | K8s-native. Pod-per-run. Concurrency policy. Time-zone aware. | Requires K8s. Container startup latency. |
| AWS EventBridge Scheduler | Managed | Serverless. Millions of schedules. One-time or recurring. Deep AWS integration. | AWS-only. Cost at scale. |
| GCP Cloud Scheduler | Managed | GCP-native. HTTP + Pub/Sub + App Engine targets. At-least-once. | GCP-only. Fewer targets than EventBridge. |
| Azure Logic Apps + Timer | Managed | Timer triggers integrated with visual workflow builder. | Azure-only. Cost. |
| Cloudflare Cron Triggers | Managed | Runs at 300+ PoPs. Sub-minute triggers for Workers. | Cloudflare Workers-only. |
| Quartz Scheduler | OSS (Java) | Rich features: calendars, priority, misfire policy. Battle-tested in enterprise Java. | Java-only. Requires DB for persistence. |
| Apache Airflow (scheduler) | OSS | DAG-aware. Rich UI. Massive community. | Scheduler is a bottleneck. Not for high-frequency triggers. |
| Temporal Schedules | OSS + Cloud | Built on durable workflows. Backfill support. Rich policies. | Requires Temporal deployment. |
| Chronos (Mesos) | OSS | Distributed cron on Mesos. Fault-tolerant. | Mesos-dependent. Declining ecosystem. |
| Sidekiq Cron / Ent Sidekiq | OSS + Enterprise | Ruby-native. Simple cron addon for Sidekiq. | Ruby-only. Redis-dependent. |
| BullMQ (Node.js) | OSS | Repeatable jobs + delay queues on Redis. Node.js-native. | Node.js-only. Redis-dependent. |
How to choose: K8s shop → CronJob. AWS → EventBridge Scheduler. GCP → Cloud Scheduler. Cloudflare Workers → Cron Triggers. Java enterprise → Quartz. Data pipelines → Airflow. Long workflows → Temporal Schedules. Single VM → good old Unix cron.
12 real-world scheduler deployments
Millions of scheduled workflows
GitHub Actions supports on: schedule: cron triggers. Millions of workflows fire on schedule across repos. Deduplication + backfill handled by GitHub's scheduler infrastructure.
EventBridge Scheduler at cloud scale
AWS EventBridge Scheduler runs millions of one-time + recurring schedules. Powers many AWS services (Lambda, ECS, Step Functions triggers). At-least-once delivery + FIFO ordering.
CronJob for cluster maintenance
Every K8s cluster runs CronJobs for cleanup, backups, cert renewal, log rotation. Time zones added in K8s 1.24. Concurrency policy prevents overlapping runs.
Airflow: thousands of DAGs daily
Airbnb runs thousands of Airflow DAGs per day — hourly, daily, weekly. Data warehousing, ML training, marketing analytics. All driven by Airflow's scheduler.
Meson (retired) + custom
Netflix built Meson — internal scheduler for ML pipelines. Retired in favor of Argo Workflows + Metaflow. Complex data + ML orchestration.
Cadence timers at massive scale
Uber uses Cadence timers for millions of scheduled events — driver reminders, expiring offers, follow-up tasks. Durable across crashes.
Cron Triggers running at edge
Cloudflare Workers can be triggered by cron across 300+ edge PoPs. Serverless, no infra. Cheap for many small periodic tasks.
Sidekiq Cron + Enterprise
Shopify runs Sidekiq Cron Enterprise for periodic Ruby jobs — merchant analytics, inventory sync, notification batches. Powers vast fleet of background jobs.
Scheduled fine-tuning + batch API
OpenAI schedules fine-tuning jobs + batch API processing on internal orchestration. Runs continuously with retry + backoff.
GCP Cloud Scheduler for cleanup + reports
Discord uses GCP Cloud Scheduler for periodic tasks — TTL cleanup, weekly reports, cache warming. Simple, reliable, GCP-integrated.
Custom scheduler for payouts
Stripe runs custom scheduling infrastructure for monthly payouts, subscription renewals, invoice generation. Financial correctness demands exactly-once semantics.
Temporal Schedules for reminders
Linear (project management) uses Temporal Schedules to fire user reminders, sync integrations, run periodic queries. Durable + observable.
Key takeaways
- 1A scheduler is "when to run," not "how to run." Fire an event and delegate the work.
- 2Cron syntax is 50 years old and universal. Learn it. But use tools that respect time zones + DST.
- 3Distributed schedulers need to handle missed fires (machine down), duplicates (multiple firers), and concurrency.
- 4Common algorithms: min-heap (single-machine), timing wheel (Kafka-scale), DB polling (distributed), delay queue (fire-and-forget).
- 5Store schedules with IANA time zones, not UTC offsets. DST + political changes break offset-based storage.
- 6For most modern apps: K8s CronJob, AWS EventBridge Scheduler, GCP Cloud Scheduler, or Temporal Schedules. Rarely raw cron.
- 7Concurrency policy matters: what if the last run isn't done? Skip, queue, or replace? Every scheduler exposes this.
- 8For long-running jobs, pair a lightweight scheduler with a workflow engine (Temporal, Step Functions). Scheduler fires, workflow does the work.
References & further reading
- • Kernighan, B. (1975). Unix cron original documentation. Historical.
- • Vixie, P. (1987). Vixie cron implementation notes. The version that shipped everywhere.
- • Verma, A. et al. (2015). "Large-scale cluster management at Google with Borg." EuroSys. Google's internal scheduler.
- • Varghese, G. & Lauck, T. (1987). "Hashed and Hierarchical Timing Wheels." SOSP. Foundational timing-wheel paper.
- • Quartz Scheduler Documentation: Comprehensive scheduler concepts (misfire policy, calendars, priorities).
- • Kubernetes Docs: "CronJob" — concurrency policy, TTL, best practices.
- • AWS Docs: "Amazon EventBridge Scheduler." The modern managed scheduler paradigm.
- • Airflow Docs: "Scheduler" section. How DAG-aware scheduling works.
- • Temporal Docs: "Schedules." Modern durable scheduling on top of workflows.
- • IANA Time Zone Database: The source of truth for time-zone rules. Ships in every OS.