Files
orbit/docs/04-reliability-and-data.md

163 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Orbit 可靠性与数据模型
> 版本 0.2 · 2026-09-17。本文定义首版设计约束与建议状态机,不是已实现的可靠性保证。
## 1. 权威状态与核心不变量
MySQL 保存任务、执行权、计划进度、流程、步骤、信号、结果与幂等索引的权威状态。内存队列、连接表与时间索引是加速结构,丢失后可以从权威状态重建。
必须维持以下不变量:
1. 确认任务受理前,任务、提交幂等记录及必要事件已经按约定持久化。
2. 在一个任务当前状态版本中,至多存在一个有效执行授权;跨节点领取使用事务与条件更新协调。
3. 新操作必须携带当前有效的执行尝试和租约凭据;旧执行不能覆盖新状态。
4. 任务终态、尝试结果和必要的结果事件在同一个数据库事务中更新。
5. 调度触发记录、对应任务和计划进度的推进原子提交。
6. 重复提交、重复派发、重复结果和重复事件交付都有明确去重范围。
7. 没有证据确定业务副作用时,不将通信失败直接解释为业务失败并无条件重跑。
8. 人工干预保留历史、原因与身份;不能通过直接修改数据库状态绕过业务确认。
9. 流程步骤推进与下一步任务创建共享事务;异步接续的内部事件必须持久化并可去重。
10. 流程绑定的定义版本不被发布操作替换;信号和任务结果只能推进匹配的步骤激活。
11. 有界查询轮次、对应任务与唤醒关联持久化并去重;重启、暂停继续及执行重试不重置预算。
12. 按暂停设计,流程控制版本与通用执行门控一致更新;阻止新的授权,同时保留在途结果及可恢复的待续推进记录。
平台允许按策略重复投递或恢复执行,不承诺任意外部业务效果“恰好一次”。业务幂等和资源端执行代次校验仍由接入方落实。
## 2. 建议任务状态机
| 状态 | 含义 |
| --- | --- |
| `WAITING` | 等待执行,包含未来执行与退避等待,以 `available_at` 决定最早派发时间 |
| `LEASED` | 已分配给一个执行尝试,等待开始确认 |
| `RUNNING` | 已授权处理器开始,租约管理执行权 |
| `NEEDS_REVIEW` | 结果未知或需要人工/业务核查,不自动重新产生副作用 |
| `SUCCEEDED` | 已保存处理器约定的成功结果 |
| `FAILED` | 已知失败、不可重试,或符合结束条件 |
| `CANCELLED` | 已阻止开始,或 Worker 已报告停止且取消结果可确认 |
```mermaid
stateDiagram-v2
[*] --> WAITING
WAITING --> LEASED: 原子取得执行授权
LEASED --> RUNNING: 确认开始
LEASED --> WAITING: 未授权开始且领取失效
LEASED --> FAILED: 未授权开始且时限或策略已耗尽
WAITING --> CANCELLED: 取消生效
LEASED --> CANCELLED: 开始前取消生效
WAITING --> FAILED: 时限或策略不再允许执行
RUNNING --> SUCCEEDED: 结果可靠保存
RUNNING --> WAITING: 满足安全重试条件
RUNNING --> FAILED: 已知失败且结束
RUNNING --> NEEDS_REVIEW: 结果未知且不可安全重试
RUNNING --> CANCELLED: 停止已确认
NEEDS_REVIEW --> SUCCEEDED: 核查确认成功
NEEDS_REVIEW --> FAILED: 核查确认失败并结束
```
`cancel_requested` 是独立的持久化意图,不直接把运行任务变为 `CANCELLED`。取消请求与成功上报竞争时按事务确定的有效转换处理;不能覆盖已经确认的成功结果。
单次超时、总时限和租约过期都要结合当前阶段与任务安全等级判断。运行中无法确认效果的任务应进入核查;不能仅因为超时就声称业务已停止。
核查操作需要证据引用、权限和审计。核查前的迟到结果可以作为证据保存,但无效尝试不能直接改写任务终态。
人工重试已冻结为创建retry_of关联新任务,旧任务终态不回退;每个原任务最多一个直接后继。核查和重试分别要求显式执行结束/安全重试声明,具体权限、证据、版本与预算见[人工恢复接口](api/task-recovery.md)。
管理审计随对应状态变更同事务追加,应用日常权限不允许改写历史审计。更正通过新的关联记录表达;归档、导出和到期清理另设权限与审计。数据库管理员级别的防篡改证据及长期留存方案在生产审计要求确定后另行设计,不能将普通日志等同于业务审计证据。
## 3. 执行尝试与租约
一次尝试记录独立 ID、所属任务、授权代次、Worker 实例、当前会话、有效期、批准开始时间、结束时间、错误与结果摘要。终结的尝试不被下一次尝试覆盖。
- 使用数据库时间作为租约判定依据;数据库及运行环境需要时钟监控。
- 领取、开始、续租、完成、取消与恢复采用明确的条件更新和受影响行数判断。
- 处理器在数据库事务之外运行。不得持锁等待网络、执行业务逻辑或等待人工。
- 过期回收必须再次校验租约和状态版本,避免与刚成功的续租或完成操作竞争。
- 租约过期与重试安全性分别判断;失联不会自动证明旧处理器没有产生副作用。
- 执行凭据应具备不可猜测性;顺序代次与秘密凭据职责分开,具体表示在协议评审中确定。
同业务键的并发限制只约束平台授予的有效许可。旧进程或已经发出的外部请求无法被数据库锁自动撤回,接入方仍需业务唯一约束、幂等或资源端 fencing。
## 4. 数据分组建议
下表是概念分组。实际 Go 模型、表名和已落实约束见 [数据模型基线](07-data-model.md),初始 DDL 已建立:
| 数据组 | 建议实体及关键约束 |
| --- | --- |
| 接入与资源 | 应用、命名空间、凭据引用、队列、配额;作用域校验覆盖读写 |
| 任务 | `tasks`;保存当前状态、状态版本、处理器与参数快照、到期时间、当前尝试 |
| 提交幂等 | `submission_keys`;命名空间与键唯一,保存请求语义摘要及原任务引用 |
| 执行尝试 | `task_attempts`;尝试 ID 唯一,保留每次授权与结果 |
| 关键状态事件 | `task_events`;状态转换追加记录,与任务状态版本关联 |
| Worker 与执行许可 | 会话代次、恢复所需归属、可回收的并发许可;连接对象本身仅在内存 |
| 通用执行门控 | Pulse 管理的门控标识、允许/暂停标记和控制版本;流程任务关联门控,领取/开始与控制更新按统一锁定或版本协议串行化 |
| 计划 | `schedules`;定义版本、时区、生效边界、下一触发点与策略 |
| 触发 | `schedule_occurrences`;建议同计划同一 UTC 计划触发时间唯一 |
| 结果交付 | `result_events`、`event_deliveries`;事件与订阅交付唯一,确认和补投状态持久化 |
| 流程定义 | `workflow_definitions`、`workflow_definition_versions`;发布版本不可变,记录定义摘要及发布审计 |
| 流程实例 | `workflow_instances`;实例、启动幂等身份、定义版本、状态版本、暂停标记、控制版本、门控关联与策略快照 |
| 步骤与激活 | `workflow_steps`、`workflow_activations`;实例内步骤/激活唯一,绑定当前 Pulse 任务 |
| 信号与等待 | `workflow_signals`、`workflow_waits`;实例内信号身份唯一,保存目标关联、截止时间与消费状态 |
| 有界查询 | 查询节点检查点与 `workflow_poll_rounds`;命名空间/实例/步骤激活/轮次唯一,保存任务关联、已用轮次、截止时间、下次查询时间及结果 |
| 流程接续 | `workflow_inbox`、待续推进记录;源事件/消费者唯一,推进与消费确认同事务 |
| 管理审计 | 操作主体、原因、资源版本、前后摘要和证据引用 |
活跃任务访问路径与历史归档分离。索引围绕命名空间、队列/逻辑分区、状态、到期时间和租约到期时间设计,并以真实执行计划验证。
同一任务、流程及步骤任务需要原子更新的数据必须处于同一事务可覆盖的数据库中。未来物理分片时先明确路由单元及全局配额边界,不能让跨库更新沿用单库原子性假设。流程状态模型以 [Weave 设计](06-workflow-spec.md) 为准,不能混用任务与流程状态。
## 5. 关键事务
| 事务 | 必须一起提交的内容 |
| --- | --- |
| 受理 | 提交幂等记录、任务、受理事件 |
| 领取 | 校验可执行条件与有效门控、取得额度、创建尝试、更新当前执行归属 |
| 开始 | 校验当前授权、门控与取消意图、记录批准开始状态 |
| 续租 | 校验当前尝试/会话/版本、更新有效期;批量操作逐项给出结果 |
| 完成 / 失败 / 转核查 | 尝试结果、任务状态、额度释放、关键事件、必要结果通知与内部流程交付记录 |
| 取消 | 取消意图或开始前取消终态、管理审计、必要通知 |
| 调度触发 | 触发去重记录、任务创建、计划进度推进 |
| 流程启动 / 推进 | 实例与步骤变化、待续推进检查点、下一步任务或等待建立、流程事件 |
| 内部事件消费 | 去重校验、步骤与流程推进、后续任务创建、消费确认 |
| 信号受理 / 消费 | 受理时保存幂等与待处理记录;消费时原子校验等待、推进并确认消费 |
| 流程暂停 / 继续 | 实例控制版本、暂停标记、Pulse 通用执行门控、管理审计与必要待续推进记录 |
| 查询下一轮 | 校验门控与预算、写入唯一轮次、计数、创建对应任务、确认内部唤醒消费 |
任务领取可评估短事务配合 `SELECT ... FOR UPDATE SKIP LOCKED`;其适用场景是队列类访问,不应被用于跳过业务一致性校验。[MySQL 锁定读取说明](https://dev.mysql.com/doc/refman/8.4/en/innodb-locking-reads.html)
所有事务明确锁定顺序,处理死锁和提交结果不明。提交结果不明时先通过稳定操作身份核对,不能换身份重试造成额外任务。
## 6. 可靠衔接
### 6.1 业务系统到平台
接入方保存业务数据与向 Orbit 提交任务、启动流程或发送信号之间跨越两个数据库边界。业务侧可在本地事务内写入业务变更和 Outbox,再以稳定幂等键提交对应接口,受理后标记已送达。
Orbit 数据库事务不能自动包含 接入方的业务事务。SDK 的事务接入组件只辅助接入方在自己的数据库写入待发请求,核心 HTTP / WS 客户端不绑定业务 ORM。[事务 Outbox 参考](https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/transactional-outbox.html)
当前[任务Outbox示例](examples/task-outbox.md)已提供独立Go module、业务写入与待发请求同事务、有限租约/退避/预算、原身份核对及CLI。发送前核对命名空间和队列,HTTP在事务外,受理确认与业务任务关联同事务保存;流程启动/信号命令类型随Weave实现。已实现的是业务适配参考,核心SDK不引入MySQL驱动。
### 6.2 平台到结果接收方
结果事件与终态同事务保存,投递状态独立跟踪。接收方可靠处理后确认;确认丢失则重复交付,由接收方去重。
事件投递不能仅依赖内存通知,也不能未经处理提交次序问题就用自增 ID 的最大值作为“此前全部已处理”的游标。首版建议使用明确的订阅交付记录。
### 6.3 Pulse 到 Weave
任务结果和待核查等关键状态变化,原子产生持久化内部事件与交付记录。Weave 使用事件身份、任务状态版本、当前步骤激活去重,在推进事务中确认消费。事件处理失败可再次消费;重复消费不得创建第二份后续任务。
内部事件暂时未消费时,流程详情应能区分“任务已有结果”和“编排尚未推进”,积压需要监控及恢复扫描。具体事务边界见 [流程协作](06-workflow-spec.md#5-pulse-与-weave-的事务协作)。
暂停不停止内部结果的可靠记录。若消费事件时暂不允许新步骤推进,应同时保存待续推进检查点;只标记事件已消费却没有后续恢复记录,会导致流程在继续后停滞。有界查询同样不能仅在内存增加轮次、设置定时器或保存未完成的下一轮计划。
## 7. 保留、归档与恢复
- 任务与流程历史、幂等键、信号、内外部事件、管理审计分别制定保留策略。
- 清理任务主体不自动清理幂等索引。去重窗口须覆盖约定的补发与恢复窗口,长期业务幂等由业务系统维护。
- 有未确认交付的事件不能被普通归档清理无声删除;保留到期应告警并执行已定义的补偿流程。
- 备份需覆盖任务、计划、幂等、执行门控、流程定义、实例、查询轮次与预算、信号、事件及配置的一致恢复范围,包含必要的日志与密钥管理安排。
- 恢复旧备份后,可能已有外部业务效果而平台记录回退。必须停止派发、使恢复前的会话与租约失效,并完成必要对账后再开放执行。
- 数据库故障切换与备份恢复是不同故障模型,分别定义验证和可接受损失范围。
MySQL 持久化建议基线为 `innodb_flush_log_at_trx_commit=1`,启用 binlog 时采用 `sync_binlog=1`。副本故障切换的确认语义、RPO / RTO 仍要由具体高可用方案决定。[MySQL 持久化配置](https://dev.mysql.com/doc/refman/8.4/en/innodb-parameters.html#sysvar_innodb_flush_log_at_trx_commit)