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_idUUID全局唯一标识,避免自增 ID 在分布式下的冲突
payloadJSONB灵活存储任务入参,支持 GIN 索引用于内容检索
status枚举状态机驱动,禁止直接 UPDATE 跳过合法状态转换
versionINT乐观锁,防止并发调度导致状态覆盖
schedule_atTIMESTAMPTZ支持延迟任务与定时调度
状态机约束: 建议通过触发器或应用层校验确保状态转换合法。例如: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_idBIGSERIAL自增序列,保证写入顺序
from_status / to_status枚举记录状态跃迁,支持回放任务状态机
detailJSONB存储重试次数、执行节点 IP、耗时等上下文
created_atTIMESTAMPTZ日志产生时间,用于 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 边表,决定任务执行顺序。
三表关系与数据流 task 任务实例主体 状态机驱动 task_log 不可变事件溯源 追加写入 task_dependency DAG 边表 依赖解析 状态变更 依赖关系 事务控制要点 状态变更 + 日志写入必须在同一事务内提交 依赖解析时使用 SELECT ... FOR UPDATE 防止并发调度

4.5 典型查询模式与索引策略

异步编排场景下,数据库查询模式高度集中,合理设计索引可避免全表扫描:

查询场景SQL 模式推荐索引
调度器拉取待执行任务WHERE status='pending' AND schedule_at <= now()idx_task_status_schedule
查询任务完整日志WHERE task_id = ? ORDER BY created_atidx_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_atidx_task_type_priority
覆盖索引优化: 对于高频调度查询,可考虑在 idx_task_status_schedule 中包含 task_id, payload, priority 字段,实现索引覆盖,减少回表开销。

4.6 数据模型设计总结

三张表的设计遵循以下原则:

  • 职责分离:任务主体、审计日志、依赖关系各自独立,避免单表过大。
  • 不可变日志:task_log 只追加不修改,保障审计链可信。
  • 显式依赖:task_dependency 使用外键约束与唯一索引,防止数据不一致。
  • 事务边界:状态变更与日志写入在同一事务内完成,依赖解析使用行级锁。