Task Scheduler — Masterclass
Three additional artifacts a Staff/Principal candidate should be able to produce for this problem: an Architecture Decision Record, a business-driven design exercise, and a production incident scenario.
1. Architecture Decision Record
The format working architects use to document a decision so future teams understand context, options, and reversal conditions.
Kafka priority topics + Temporal workflow orchestration + Postgres state store + separate worker pools per priority tier + at-least-once semantics with idempotent tasks + DLQ with alerting — over Celery + Redis (current), pure Airflow, SQS + Lambda, or AWS Step Functions.
- 1M tasks/hour baseline, 10M/hour peak (monthly billing run + Black Friday spikes)
- 200+ task types, 500K unique task instances active at any time
- 3 priority tiers with distinct SLAs: urgent (500ms), normal (30s), bulk (hours)
- Workflows require durable state + resumption on worker crash
- At-least-once semantics with idempotent task design — no duplicates for critical tasks (payments)
- 12-18 month migration budget from Celery + Redis to Kafka + Temporal
- Team: 40 engineers total, 15 on Task Platform team
- Existing infrastructure: Celery + Redis (2 years old), Kafka (used for events)
Kafka + Temporal workflow orchestration + Postgres state (chosen)
- Temporal: durable workflow state, resumption on failure, exactly-once semantics via idempotency
- Kafka: priority topics per tier, 1M+ events/sec ingest, replayable
- Uber Cadence + Temporal (Uber spin-off) battle-tested at hyperscale
- Workflows: multi-step tasks (video → transcode → moderate → publish) with checkpoints
- Long-running tasks: workers can crash + resume from last state (no more 30-min transcoding lost)
- Temporal is new tech (learning curve ~3 months for team)
- 3 stateful surfaces (Kafka + Temporal + Postgres) — dedicated Data Platform SRE required
- Workflow semantics require rewriting existing tasks (12-18 month migration)
- Temporal SDK ties us to specific language runtimes (Go, Java, Python, TypeScript)
Continue Celery + Redis (scale up)
- Team knows Celery
- No migration risk
- Zero new operational surfaces
- Single Redis ceiling ~500K ops/sec — we hit this at 1M tasks/hour peak
- No workflow semantics — chained tasks fail-and-restart from scratch
- No exactly-once guarantee — duplicates possible on retry
- Long-running tasks (30-min video transcoding) lose state on worker restart
Airflow for workflows + Celery for one-off
- Airflow handles DAGs + scheduling
- Celery handles high-volume async
- Airflow's centralized scheduler is bottleneck at 100K+ tasks/hour
- Two systems to operate + coordinate
- Airflow at 2024 vintage doesn't have exactly-once workflow guarantees
SQS + Lambda (managed)
- Zero ops burden — AWS handles scaling
- Auto-scale per queue
- $0.40 per million SQS requests × 1M tasks/hour = $700/day = $250K/yr for messaging alone
- Lambda 15-min max execution — video transcoding needs Fargate
- Priority queues require multiple SQS queues + client-side routing (kludge)
- AWS lock-in on foundational async infrastructure
- No workflow orchestration (would need Step Functions on top = more cost)
AWS Step Functions
- Managed workflow orchestration
- AWS-native integrations (Lambda, SQS, DynamoDB)
- $0.025 per state transition × billions of transitions/mo = $50K+/mo
- Vendor lock-in more severe than SQS+Lambda
- Limited to AWS-supported activities
Kafka + Temporal workflow orchestration + Postgres state + separate worker pools per priority tier
- Celery + Redis scaled up — architecturally ceilinged at 500K ops/sec + no workflow + no exactly-once
- Airflow + Celery — centralized scheduler bottleneck at 100K+ tasks/hour, two systems to coordinate
- SQS + Lambda — $250K/yr messaging alone + AWS lock-in + no workflows + 15-min Lambda limit
- AWS Step Functions — $50K+/mo state transitions + severe AWS lock-in on foundational async infra
- Accept 3 stateful surfaces (Kafka + Temporal + Postgres) — dedicated Data Platform SRE team
- Accept 12-18 month migration from Celery + Redis (dual-run + gradual cutover)
- Accept Temporal learning curve (~3 months for team to get productive)
- Accept Temporal SDK language-specific lock (Go, Java, Python, TypeScript — not shell scripts)
- Accept over-provisioned urgent worker pool (cost of idle < cost of late payment webhook)
- Accept task idempotency discipline — every activity must handle 'was I already run?' correctly
- Accept 15-20 engineer Task Platform team — foundational infrastructure warrants first-class ownership
- Task platform becomes horizontally-scalable — 1M → 10M tasks/hour requires more workers, not architecture change
- Workflow patterns emerge as reusable primitive (payment saga, video upload, ML training pipeline)
- Long-running tasks (30-min transcoding) survive worker crashes — massive reliability win
- Priority tiers enforce SLAs — urgent payment webhooks NEVER wait behind nightly billing
- Temporal + Kafka expertise becomes valuable — team can hire against this stack
- DLQ + alerting discipline flows through all task types — no more silent failures
- Migration from Celery generates 2 quarters of dual-run infrastructure cost — factor into budget
- If Temporal cluster ops cost exceeds 3 engineers dedicated → managed offering (Temporal Cloud) at 2x infrastructure cost
- If task volume drops below 100K/hour sustained → simplify back to Celery + Redis (accept regression)
- If AWS Step Functions matures to L6-scale (currently ceilinged) → managed migration becomes attractive
- If competing workflow engines emerge (Restate, Dapr, etc.) with better economics → evaluate at 3-year checkpoint
2. Business constraint exercise
Given real-world constraints (team size, budget, deadline), what architecture do you propose — and how do you push back when leadership asks for the wrong thing? This teaches engineering judgment.
You are the Task Platform lead at a $100M ARR SaaS company. CTO says Monday: 'Temporal Cloud (Temporal's managed offering) costs $150K/yr. Self-hosted Temporal is 2 engineers-worth of ops overhead (~$400K/yr fully loaded). Celery + Redis works today but ceilings at 1M tasks/hour. We're at 400K tasks/hour now, projected 2M in 18 months. Which migration path: (1) migrate to Temporal Cloud (managed), (2) migrate to self-hosted Temporal, (3) stay on Celery + Redis until it breaks, or (4) hybrid — Celery for simple tasks + Temporal for workflows?'
- 1$100M ARR SaaS company, 300 engineers, 15 on Task Platform
- 2Current: Celery + Redis, 400K tasks/hour, 30% workflow tasks (chained), 70% simple async
- 318-month growth projection: 2M tasks/hour (5x growth)
- 4Team can absorb 1 major migration or 2 minor migrations in next 18 months (not both)
- 5Existing pain: 5-10 workflow failures/week where video-transcoding jobs lose state on worker restart, 2 payment webhook duplicates/month, monthly billing runs taking 8 hours instead of 4
- 63-year budget: $2M available for platform investment
- 7Board wants 'foundational infrastructure' story for Series D pitch
Recommend the path Wednesday. Show 3-year TCO for each option + team capacity math + strategic risk. Address CTO's implicit question: 'is Temporal Cloud worth 2.7x self-hosted?'
3. Production incident scenario
You are on-call at 3:47am. p99 has spiked. Walk through the investigation, hypothesis, mitigation, and postmortem. This teaches real production reasoning — not just design.
PagerDuty alert at 2:34am PT on the 1st of the month (monthly billing run). Bulk task queue depth 2.4M (baseline 5K). Urgent payment webhook latency spiked from 200ms to 45 seconds. Redis Cluster memory at 91% and rising. Downstream services (billing, notifications, ledger) reporting connection pool exhaustion. Marketing dashboard shows $840K/hour in delayed payment processing. On-call: 'billing run started 34 minutes ago, is 2 hours behind schedule, and taking down urgent tier'.
- task.bulk_queue_depth: 2,400,000 (baseline 5,000 = 480x)
- task.urgent_queue_p99_latency_ms: 45,000 (baseline 200 = 225x)
- task.urgent_success_rate: 68 pct (baseline 99.5 pct)
- task.workers.urgent_pool_util: 12 pct (workers idle — but tasks stuck behind Redis backpressure)
- redis.memory_utilization: 91 pct (rising ~1 pct/min)
- redis.evictions_per_sec: 2400 (was 0 baseline)
- task.bulk_success_rate: 45 pct (retry storm on downstream failure)
- task.workers.bulk_pool_util: 100 pct (saturated)
- downstream.billing_service.connection_pool_wait_p99: 8500ms (target <100ms)
- downstream.billing_service.cpu_percent: 96
- downstream.notification_service.connection_pool_wait_p99: 6200ms
- delayed_payment_revenue_per_hour: $840,000
- 02:00:03 scheduler: 'monthly-billing-run kicked off (2M customer invoices)'
- 02:15:27 task-worker-bulk: 'bulk queue depth 500K, exceeds baseline threshold'
- 02:22:14 task-worker-bulk: 'billing service returning 5xx at 40 pct, entering exponential backoff'
- 02:28:00 task-worker-bulk: 'retry storm from backoff — bulk queue growing to 1.5M'
- 02:31:12 redis-cluster: 'memory 85 pct, LRU evictions engaged'
- 02:33:45 task-worker-urgent: 'urgent tasks queuing behind Redis backpressure — evictions affecting Redis latency'
- 02:34:03 payment-webhook-handler: 'PagerDuty alert — 6 payment webhooks timed out (Stripe will retry)'
- 02:35:18 downstream.billing-service: 'connection pool exhausted, rejecting new requests'
- 02:37:00 downstream.notification-service: 'connection pool exhausted, cascade beginning'
- 02:39:22 executive-slack: 'why is 50 pct of our platform down?'
- Redis Cluster: DEGRADED — 91% memory, evictions engaged, latency affected
- Task Workers (bulk pool): SATURATED — 100% utilization
- Task Workers (urgent pool): IDLE-BUT-BLOCKED — 12% util, tasks stuck behind Redis backpressure
- Billing Service (downstream): SATURATED — 96% CPU, connection pool exhausted
- Notification Service (downstream): DEGRADED — connection pool wait 6+ seconds
- Ledger Service (downstream): DEGRADED — cascade from billing
- Payment Webhooks (Stripe): DEGRADED — 32% timing out, Stripe queuing retries
- Postgres: HEALTHY at 55% CPU — not the bottleneck
- Kafka: HEALTHY — event stream flowing
It's 2:39am. Bulk queue at 2.4M, urgent tier degraded, downstream services cascading. Payment webhooks timing out. What's your SINGLE highest-priority action in the next 5 minutes to stop the cascade?
Bulk queue stabilized at 2.4M (not growing anymore, thanks to disabled retry). But the queue is STILL there — 2.4M billing tasks. Urgent tier is degraded because Redis is at 91% memory. What's the highest-leverage action to drain Redis memory pressure?
It's 3:15am — 41 minutes since alert. Redis stabilized, urgent tier recovered, bulk backlog draining from Postgres at 3K/sec. Billing service still degraded at 88% CPU. If we don't slow down bulk processing, billing service will re-cascade. What's your action?
Postmortem the next day. Name 3 action items ranked by impact reduction for future monthly billing runs.
Draft the customer-facing status page update at T+90 minutes (4:04am — after urgent tier recovered but bulk backlog still draining). Customers are asleep for the most part, but B2B customers will see impact when they log in in the morning. Constraint: honest, no jargon, sets expectation for what they'll see when they wake up.
Learn these first
- Kafka priority topics + consumer groups
- Temporal workflow orchestration + durable state + resumption
- At-least-once semantics + idempotent task design
- Priority tiers with dedicated worker pools (urgent vs bulk)
Where this appears in the curriculum
This is the Gold Standard.
Every other system will eventually have a masterclass tab like this one. The pattern proven here — ADR + business exercise + incident scenario — scales to all 50+ problems on the platform.