3. 调度任务元数据模型:核心表结构设计
调度平台的核心是围绕任务的生命周期构建元数据模型。一个健壮的模型需要覆盖任务定义、任务实例、执行历史与依赖关系四个维度。本节从关系型数据库视角给出表结构设计,并解释每个字段在索引选择与缓存策略中的角色。
3.1 任务定义表(task_definition)
任务定义是用户提交的“蓝图”,包含调度规则、执行命令、超时与重试策略。该表是查询最频繁的静态元数据,适合全量缓存。
| 字段名 | 类型 | 说明 | 索引建议 |
|---|---|---|---|
| id | BIGINT PK | 自增主键 | 聚簇索引 |
| name | VARCHAR(128) | 任务名称,业务唯一 | 唯一索引 |
| cron_expr | VARCHAR(64) | Cron 表达式 | — |
| timezone | VARCHAR(32) | 时区,默认 UTC | — |
| executor_type | TINYINT | Shell / HTTP / Docker | — |
| executor_config | TEXT | JSON 格式的执行参数 | — |
| timeout_sec | INT | 超时秒数,0 表示不限制 | — |
| retry_times | TINYINT | 失败重试次数 | — |
| retry_interval | INT | 重试间隔(秒) | — |
| status | TINYINT | 0=禁用, 1=启用 | 普通索引 |
| created_at | DATETIME | 创建时间 | — |
| updated_at | DATETIME | 最后修改时间 | — |
缓存策略: 任务定义表数据量通常小于 10 万行,变化频率低。推荐使用 Redis Hash 全量缓存,key 为
task:def:{id},TTL 设为 3600 秒。当 updated_at 变更时主动失效缓存。
3.2 任务实例表(task_instance)
每次调度触发生成一条实例记录。实例表是写入与查询压力最大的表,也是索引设计的关键战场。
| 字段名 | 类型 | 说明 | 索引建议 |
|---|---|---|---|
| id | BIGINT PK | 自增主键 | 聚簇索引 |
| task_def_id | BIGINT | 关联 task_definition.id | 联合索引 (task_def_id, scheduled_time) |
| scheduled_time | DATETIME | 计划执行时间 | 同上 |
| actual_time | DATETIME | 实际开始时间 | — |
| status | TINYINT | 0=等待, 1=运行, 2=成功, 3=失败, 4=超时 | 普通索引 |
| node_id | VARCHAR(64) | 执行节点标识 | 普通索引 |
| trigger_type | TINYINT | 0=定时, 1=手动, 2=依赖触发 | — |
| retry_count | TINYINT | 当前已重试次数 | — |
| created_at | DATETIME | 实例创建时间 | — |
索引选择避坑: 不要单独对 scheduled_time 建索引。调度平台最常见的查询是“某任务在时间范围内的实例”,联合索引 (task_def_id, scheduled_time) 能同时过滤任务和时间,避免回表。单独对 status 建索引在数据量超过 500 万行时效果急剧下降,建议配合分区表使用。
3.3 执行历史表(execution_history)
执行历史记录每次运行的详细日志、退出码与资源消耗。该表只追加、不更新,适合按时间分区存储。
| 字段名 | 类型 | 说明 | 索引建议 |
|---|---|---|---|
| id | BIGINT PK | 自增主键 | 聚簇索引 |
| instance_id | BIGINT | 关联 task_instance.id | 唯一索引 |
| exit_code | INT | 进程退出码 | — |
| stdout_log | MEDIUMTEXT | 标准输出 | — |
| stderr_log | MEDIUMTEXT | 错误输出 | — |
| cpu_usage | DECIMAL(5,2) | CPU 使用率百分比 | — |
| mem_mb | INT | 内存使用 MB | — |
| duration_ms | INT | 执行耗时(毫秒) | — |
| recorded_at | DATETIME | 记录时间 | 普通索引(用于清理) |
缓存策略: 执行历史不缓存全量数据。仅对最近 100 条实例的 duration_ms 做聚合缓存,用于实时监控面板。历史日志建议直接走对象存储(S3/MinIO),数据库只保留元数据指针。
3.4 依赖关系表(task_dependency)
DAG 调度场景下,任务之间的上下游依赖通过该表表达。依赖关系是调度引擎计算可执行性的核心输入。
| 字段名 | 类型 | 说明 | 索引建议 |
|---|---|---|---|
| id | BIGINT PK | 自增主键 | 聚簇索引 |
| upstream_def_id | BIGINT | 上游任务定义 ID | 联合索引 (upstream_def_id, downstream_def_id) |
| downstream_def_id | BIGINT | 下游任务定义 ID | 同上 |
| dependency_type | TINYINT | 0=成功依赖, 1=完成依赖, 2=输出匹配 | — |
| output_match | VARCHAR(256) | 输出匹配正则(仅 type=2 时有效) | — |
| created_at | DATETIME | 创建时间 | — |
缓存策略: 依赖关系在任务定义发布后极少变更。使用 Redis Set 缓存每个上游的下游列表,key 为
task:deps:down:{upstream_def_id}。调度引擎在触发上游完成事件时,直接读取缓存中的下游列表,避免每次查询数据库。
3.5 核心表关系与数据流
下图展示四张表之间的关联关系以及数据在调度周期中的流转路径。
3.6 索引选择与缓存策略总结
基于上述表结构,下表汇总索引与缓存的推荐配置,供架构设计时直接参考。
| 表 | 核心查询模式 | 推荐索引 | 缓存策略 |
|---|---|---|---|
| task_definition | 按 name 查询;按 status 列表 | name 唯一索引;status 普通索引 | Redis Hash 全量缓存,TTL 3600s |
| task_instance | 按 task_def_id + 时间范围查询;按 status 过滤待执行实例 | (task_def_id, scheduled_time) 联合索引;status 普通索引 | 仅缓存最近 1000 条活跃实例,使用 ZSET 按 scheduled_time 排序 |
| execution_history | 按 instance_id 查询详情;按 recorded_at 清理过期数据 | instance_id 唯一索引;recorded_at 普通索引 | 不缓存全量;仅聚合 duration_ms 到 Redis 计数器 |
| task_dependency | 按上游 ID 查询所有下游;按下游 ID 查询所有上游 | (upstream_def_id, downstream_def_id) 联合索引 | Redis Set 缓存下游列表,key 为 task:deps:down:{upstream_def_id} |
架构决策要点:
- 任务定义与依赖关系属于“读多写极少”数据,优先使用本地缓存 + Redis 二级缓存,减少数据库读压力。
- 任务实例表是写入瓶颈,建议按 scheduled_time 做月度分区,避免单表过大导致索引深度增加。
- 执行历史表超过 1000 万行后,将日志字段迁移到对象存储,数据库只保留元数据与指针。
- 依赖关系缓存必须在任务定义发布或更新时主动失效,避免调度引擎读到脏数据导致 DAG 死锁。