4. 异步任务的数据模型设计:任务表、任务日志表与任务依赖表
在异步任务编排系统中,数据模型是支撑任务调度、状态追踪、故障恢复与依赖解析的基石。本章围绕三张核心表——任务表(task)、任务日志表(task_log)、任务依赖表(task_dependency)——展开 Schema 设计,确保在分布式场景下具备高并发写入、事务一致性及高效查询能力。
4.1 任务表(task):任务实例的持久化主体
任务表记录每个异步任务的元数据与运行时状态,是编排引擎的“主记录”。每一条记录对应一个任务实例,包含唯一标识、业务类型、当前状态、重试策略、超时控制等核心字段。
设计要点: 状态字段使用枚举类型(如
task_status),避免魔法字符串;使用 created_at 与 updated_at 双时间戳支持乐观锁与过期判断;retry_count 与 max_retries 配合实现指数退避重试。
CREATE TYPE task_status AS ENUM (
'pending', -- 等待执行
'running', -- 执行中
'success', -- 执行成功
'failed', -- 执行失败(可重试)
'timeout', -- 超时
'cancelled', -- 被取消
'dead' -- 超过最大重试次数,进入死信
);
CREATE TABLE task (
task_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
task_type VARCHAR(64) NOT NULL, -- 任务类型,如 'data_export', 'notification'
payload JSONB NOT NULL DEFAULT '{}', -- 任务输入参数
status task_status NOT NULL DEFAULT 'pending',
priority SMALLINT NOT NULL DEFAULT 0, -- 优先级,数值越大优先级越高
retry_count SMALLINT NOT NULL DEFAULT 0,
max_retries SMALLINT NOT NULL DEFAULT 3,
timeout_seconds INT NOT NULL DEFAULT 300, -- 超时阈值(秒)
schedule_at TIMESTAMPTZ, -- 定时执行时间,NULL 表示立即执行
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
version INT NOT NULL DEFAULT 1 -- 乐观锁版本号
);
-- 加速按状态与调度时间的查询
CREATE INDEX idx_task_status_schedule ON task (status, schedule_at)
WHERE status IN ('pending', 'running');
-- 支持按类型与优先级排序的调度器查询
CREATE INDEX idx_task_type_priority ON task (task_type, priority DESC, created_at);
| 字段 | 类型 | 说明 |
|---|---|---|
| task_id | UUID | 全局唯一标识,避免自增 ID 在分布式下的冲突 |
| payload | JSONB | 灵活存储任务入参,支持 GIN 索引用于内容检索 |
| status | 枚举 | 状态机驱动,禁止直接 UPDATE 跳过合法状态转换 |
| version | INT | 乐观锁,防止并发调度导致状态覆盖 |
| schedule_at | TIMESTAMPTZ | 支持延迟任务与定时调度 |
状态机约束: 建议通过触发器或应用层校验确保状态转换合法。例如:
pending → running → success/failed,不允许从 success 回退到 running。
4.2 任务日志表(task_log):不可变的事件溯源
任务日志表记录任务生命周期中的每一次状态变更、异常堆栈、重试记录与人工干预。日志采用追加写入模式,禁止 UPDATE 或 DELETE,保证审计链的完整性。
CREATE TABLE task_log (
log_id BIGSERIAL PRIMARY KEY,
task_id UUID NOT NULL REFERENCES task(task_id) ON DELETE CASCADE,
from_status task_status, -- 变更前状态,NULL 表示初始创建
to_status task_status NOT NULL, -- 变更后状态
message TEXT, -- 日志描述或错误信息
detail JSONB DEFAULT '{}', -- 结构化上下文,如重试次数、节点 ID
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- 按任务查询日志,按时间排序
CREATE INDEX idx_task_log_task_id ON task_log (task_id, created_at);
-- 按时间范围清理历史日志
CREATE INDEX idx_task_log_created_at ON task_log (created_at);
| 字段 | 类型 | 说明 |
|---|---|---|
| log_id | BIGSERIAL | 自增序列,保证写入顺序 |
| from_status / to_status | 枚举 | 记录状态跃迁,支持回放任务状态机 |
| detail | JSONB | 存储重试次数、执行节点 IP、耗时等上下文 |
| created_at | TIMESTAMPTZ | 日志产生时间,用于 TTL 分区清理 |
性能注意: 高频任务场景下,日志表写入量极大。建议按
created_at 做表分区(如按月分区),并设置 pg_cron 定期删除过期分区。
4.3 任务依赖表(task_dependency):有向无环图的边存储
任务依赖表定义任务之间的前置/后置关系,构成有向无环图(DAG)。每条记录表示一条依赖边,支持多父节点、多子节点的复杂编排。
CREATE TYPE dependency_type AS ENUM (
'hard', -- 硬依赖:前置任务必须成功,当前任务才能执行
'soft', -- 软依赖:前置任务完成即可(无论成功或失败)
'time' -- 时间依赖:前置任务超时后自动触发
);
CREATE TABLE task_dependency (
dependency_id BIGSERIAL PRIMARY KEY,
task_id UUID NOT NULL REFERENCES task(task_id) ON DELETE CASCADE,
parent_task_id UUID NOT NULL REFERENCES task(task_id) ON DELETE CASCADE,
dep_type dependency_type NOT NULL DEFAULT 'hard',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
-- 防止重复依赖
UNIQUE (task_id, parent_task_id),
-- 禁止自依赖
CONSTRAINT chk_no_self_dependency CHECK (task_id != parent_task_id)
);
-- 查询某个任务的所有前置依赖
CREATE INDEX idx_dep_task_id ON task_dependency (task_id);
-- 查询某个任务作为前置的所有后置任务
CREATE INDEX idx_dep_parent_task_id ON task_dependency (parent_task_id);
依赖解析策略: 调度器通过
task_dependency 表实时计算每个任务的入度(前置依赖数量)。当入度降为 0 时,任务进入可调度队列。此过程需在事务中完成,避免并发调度同一任务。
4.4 三表关系与事务控制
三张表通过外键与索引形成有机整体,在异步编排中承担不同职责:
- task:任务实例主体,驱动状态流转。
- task_log:不可变审计日志,支持故障排查与回放。
- task_dependency:DAG 边表,决定任务执行顺序。
4.5 典型查询模式与索引策略
异步编排场景下,数据库查询模式高度集中,合理设计索引可避免全表扫描:
| 查询场景 | SQL 模式 | 推荐索引 |
|---|---|---|
| 调度器拉取待执行任务 | WHERE status='pending' AND schedule_at <= now() | idx_task_status_schedule |
| 查询任务完整日志 | WHERE task_id = ? ORDER BY created_at | idx_task_log_task_id |
| 计算任务入度 | SELECT count(*) FROM task_dependency WHERE task_id = ? | idx_dep_task_id |
| 查找所有后置任务 | SELECT task_id FROM task_dependency WHERE parent_task_id = ? | idx_dep_parent_task_id |
| 按类型与优先级排序 | WHERE task_type=? ORDER BY priority DESC, created_at | idx_task_type_priority |
覆盖索引优化: 对于高频调度查询,可考虑在
idx_task_status_schedule 中包含 task_id, payload, priority 字段,实现索引覆盖,减少回表开销。
4.6 数据模型设计总结
三张表的设计遵循以下原则:
- 职责分离:任务主体、审计日志、依赖关系各自独立,避免单表过大。
- 不可变日志:task_log 只追加不修改,保障审计链可信。
- 显式依赖:task_dependency 使用外键约束与唯一索引,防止数据不一致。
- 事务边界:状态变更与日志写入在同一事务内完成,依赖解析使用行级锁。