SWE / Backend · Virtual Onsite System Design

DoorDash Job Scheduler:Cron、到期队列与 Lease 重试

DoorDash 系统设计面经题:Cron 任务如何只在到期后入队,处理主从切换、重复投递、重试、时区与执行幂等。

题目重点:DoorDash 系统设计面经题:Cron 任务如何只在到期后入队,处理主从切换、重复投递、重试、时区与执行幂等。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • 可见题目未明确出吞吐、准点误差或任务运行时长。以下训练规模假设 100 万个已登记 cron 任务、平均每分钟 1 万个到期实例、容许秒级触发误差;面试时先用真实 SLO 替换。
  • Cron 用指定时区解释,创建时校验表达式;每次 occurrence 有稳定键 (job_id,scheduled_at)。任务可以至少一次执行,业务副作用要靠幂等键;不承诺跨外部系统 exactly once。
  • 取消、暂停与更新 schedule 有版本号。已排队 occurrence 是否执行由提交时读取的版本/状态决定,避免旧配置在更新后继续触发。

解题路线

  1. 持久层保存 jobs(job_id, cron, timezone, next_run_at, version, state) 与 executions(occurrence_key, state, attempt, lease_until, error)。索引 (state,next_run_at) 让调度器按时间取到期任务。
  2. 多个调度器用数据库行锁或带 lease 的原子 claim 抢到期 job;在同一事务插入唯一 occurrence、计算下次运行时间、推进 next_run_at,并写 outbox。重复 claim 由唯一键消解。
  3. 调度器只把已经到期的 occurrence 发布到执行队列。若要提前放入队列,队列必须支持可靠的延迟可见/定时消息,worker 仍核对 due_at;不能让普通 FIFO worker 提前执行后再 sleep。
  4. worker 领取 execution lease,心跳续租,按 occurrence_key 调用业务处理;超时后重试且设置退避/死信。ack 只能在副作用确认后进行,外部副作用需幂等或补偿。

手动走一遍

某 job 计划 10:05 触发:若 10:03 更新 Cron,事务必须推进 next_run_at,因此旧计划不会生成;若更新恰好与 10:05 的 claim 竞争,则比较 occurrence 的配置版本与业务约定的取消边界。若有效 occurrence 在业务调用成功后、ack 前崩溃,下一 worker 会重试同一 occurrence_key,下游幂等保证只产生一次效果。

规模与取舍

按 next_run_at 有序索引取每批 B 个到期任务约 O(log N+B) 数据库扫描成本,具体锁与写放大由数据库决定;生成与执行吞吐随到期实例数扩展。若每分钟 1 万个到期,平均约 167/s,但整点尖峰可能远高于平均,需分片 claim 和队列扩容。

易错点与追问

容易失分的边界

  • 把未来 job 直接塞进没有延迟语义的队列,会让 worker 提前执行;worker sleep 又占用消费槽且重启后难恢复。
  • 只在内存维护 next_run_at 会在调度器故障后丢触发点;只用分布式锁而没有唯一 occurrence 键,锁过期/主从切换仍可能重复生成。
  • Cron 的夏令时跳变会产生不存在或重复的本地时间;需定义跳过、顺延或运行两次的产品规则,并存时区标识与 UTC occurrence。
  • 失败重试和业务定时重复是两种不同维度:同一 occurrence 重试保留同一键,下个 schedule tick 是新键。

继续深挖

  • 如果要求严格不漏且误差 <1 秒,缩短扫描周期、分片时间索引并监控 due-to-start 延迟;数据库热点和批量整点任务会成为主要约束。
  • 如果 job 执行数小时,lease 与 heartbeat 要独立于调度循环;支持取消时需要 cooperative cancellation,而非仅删除队列消息。
  • 若使用工作流平台,应说明它如何持久化 timer、重放事件、给 activity 配重试与幂等,而非只报产品名。
✓已复制微信号!