Files
orbit/docs/07-data-model.md

163 lines
16 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.
# 数据模型与实现基线
更新:2026-09-17。数据库统一禁止外键。当前已实现任务执行、安全重试、运行中取消、计划调度、队列限速/背压和应用配额;优先级及节点公平领取已实现;可靠订阅核心、HTTP/WSS、管理与消费 SDK 已实现,独立 Inbox 场景已实现,流程编排继续开发。
## 1. 模块与依赖
- `model/`:ID、摘要、JSON、命名空间记录等公共原语。
- `platform/model/`:应用、命名空间、凭据、幂等、事件、订阅交付及审计。
- `pulse/model/`:队列、执行门控、进程/会话、任务、尝试、回执、迟到证据、并发许可和调度计划。
- `weave/model/`:定义及不可变版本、实例、步骤激活、等待、信号、查询轮次与恢复记录。
- `internal/storage/mysql/`:连接、事务与显式迁移,不向 SDK 暴露。
- `sdk/go/`:独立 Go module;对外 DTO 与服务端模型分别维护。
模型使用普通 Go struct 与 `db` 标签,模型与持久化字段提供中文注释,SQL 表和字段同步维护中文 COMMENT。SQL 是可审查的版本化迁移。目前未引入 ORM,也没有自动同步表结构。
## 2. 存储约定
| 项目 | 当前实现 |
| --- | --- |
| 数据库基线 | MySQL 8.4+,本轮在 8.4.10 验证 |
| Go 工具链 | Go 1.27.1;服务端和 SDK 分别编译测试 |
| 标识 | 128 位加密随机 ID;数据库 `BINARY(16)`,对外 32 位小写十六进制字符串 |
| 摘要 | SHA-256,数据库 `BINARY(32)`;执行令牌及凭据原文不进入模型 |
| 租户隔离 | 运行实体以 `(namespace_id, id)` 为主键;资源存在性、命名空间及父资源归属由服务端事务校验 |
| 外键 | 禁止数据库外键;保留关联字段、查询索引、唯一约束和 CHECK |
| 时间 | `DATETIME(6)`,数据库会话与驱动统一 UTC;运行授权依赖数据库时间 |
| 状态 | Go 类型常量 + SQL `CHECK`;状态版本独立于时间戳 |
| CAS | `revision` 从 1 开始;服务层必须带期望版本条件更新并检查受影响行数 |
| 暂停 | 实例执行状态与 `paused` 分开,控制版本与通用门控同事务协调 |
| 参数 | 版本化 JSON 文档或业务侧引用;业务内容使用 MEDIUMTEXT + JSON_VALID 保留数字精度,平台元数据使用原生 JSON |
| 删除 | 禁止级联删除;当前无公开硬删除接口,历史保留。后续清理须由服务端核验引用及保留窗口 |
| 不可变历史 | 流程版本、计划版本、状态事件、审计事件的 `UPDATE` 被数据库触发器拒绝 |
随机 ID 的索引成本需要后续容量测试,不将该选择描述为已经验证的大规模吞吐保证。运行账户应最小授权,迁移与保留清理使用不同身份;触发器不是对数据库管理员的防篡改保证。
## 3. 实体清单
当前共 35 个实体:初始迁移包含 30 个,000003 增加进程会话头和迟到证据,000010 增加应用共享额度,000013 增加结果流与不可变结果快照。迁移自身另有 `orbit_schema_migrations` 记录。
| 模块 | Go 模型 | 数据表 |
| --- | --- | --- |
| platform | `Application` | `orbit_applications` |
| platform | `ApplicationResources` | `orbit_application_resources` |
| platform | `Namespace` | `orbit_namespaces` |
| platform | `Credential` | `orbit_credentials` |
| platform | `IdempotencyKey` | `orbit_idempotency_keys` |
| platform | `Event` | `orbit_events` |
| platform | `EventStream` | `orbit_event_streams` |
| platform | `ResultEvent` | `orbit_result_events` |
| platform | `Subscription` | `orbit_subscriptions` |
| platform | `Delivery` | `orbit_deliveries` |
| platform | `AuditEvent` | `orbit_audit_events` |
| pulse | `Queue` | `pulse_queues` |
| pulse | `ExecutionGate` | `pulse_execution_gates` |
| pulse | `WorkerSession` | `pulse_worker_sessions` |
| pulse | `WorkerInstance` | `pulse_worker_instances` |
| pulse | `Task` | `pulse_tasks` |
| pulse | `Attempt` | `pulse_attempts` |
| pulse | `AttemptReceipt` | `pulse_attempt_receipts` |
| pulse | `AttemptEvidence` | `pulse_attempt_evidence` |
| pulse | `ConcurrencyLimit` | `pulse_concurrency_limits` |
| pulse | `Permit` | `pulse_permits` |
| pulse | `Schedule` | `pulse_schedules` |
| pulse | `ScheduleVersion` | `pulse_schedule_versions` |
| pulse | `Occurrence` | `pulse_schedule_occurrences` |
| weave | `Definition` | `weave_definitions` |
| weave | `DefinitionVersion` | `weave_definition_versions` |
| weave | `Instance` | `weave_instances` |
| weave | `Step` | `weave_steps` |
| weave | `Activation` | `weave_activations` |
| weave | `Wait` | `weave_waits` |
| weave | `Signal` | `weave_signals` |
| weave | `PollState` | `weave_poll_states` |
| weave | `PollRound` | `weave_poll_rounds` |
| weave | `InboxEvent` | `weave_inbox` |
| weave | `PendingAdvance` | `weave_pending_advances` |
`orbit_events` 保存原始状态事件,`orbit_result_events` 使用相同事件 ID 保存队列、提交序号和冻结快照,`orbit_event_streams` 串行分配命名空间内的结果序号;对外投递使用 `orbit_deliveries`,内部流程接续预留 `weave_inbox`。`weave_pending_advances` 保存暂停或分批推进后的续接检查点。
## 4. 已落实的数据库约束
- 幂等身份在命名空间、操作和资源作用域内唯一;保存请求摘要和原响应。
- 一个任务的执行代次唯一;当前尝试归属由执行事务另行校验。
- 计划触发按计划与 UTC 原触发时间唯一,计划修改不会绕过触发去重。
- 定义版本不可覆盖;实例保存版本 ID,版本归属校验随流程引擎实现。
- 步骤和激活保留父资源 ID 及关联索引,数据库不校验跨表归属。
- 查询轮次与任务分别唯一;轮次、单轮尝试预算和截止时间独立保存。
- 内部事件与外部订阅交付有去重键,租用状态要求有效租用字段。
- 终态必须有结束时间,待执行任务不能伪造有效执行租约。
- effect_uncertain=true 不能和成功、失败、取消终态共存;尝试/证据 retryable=true 只允许明确失败。
## 5. 服务层关联与事务规则
数据库禁止外键,关联一致性由应用负责;CHECK 仅保证单行约束。任务提交、计划创建和 Worker 注册在同一事务中按 namespace_id 校验队列存在性,校验失败回滚所有新增记录。鉴权校验凭据、命名空间及所属应用;执行入口核验进程、会话、执行代次、任务和尝试归属。接口不能仅相信调用方传入的资源 ID。
禁止“先查后另开事务写入”。需要防止并发状态改变的关联行在事务内持锁,统一锁顺序并保留必要索引。当前不提供硬删除;将来增加清理时,必须核验在途任务、引用、幂等恢复窗口与审计保留期,按有限批次处理,不能依赖数据库级联。直接绕过服务写 SQL 不受应用校验保护。
P1 已完成动作/队列授权、幂等摘要与原回执、受理事件及取消 CAS/审计事务;P3 扩展到运行中取消。订阅已实现服务层及持久化核心,包含事务内队列、所有者和交付归属校验;HTTP/WSS、管理与消费 SDK 已接入,业务 Inbox 示例已独立交付。流程目前仍只具备模型,后续引擎也必须补齐事务内父资源归属验证。
P2 服务端已新增会话隔离、两步派发、租约、并发许可、结果回执/证据和恢复扫描,见 [执行协议](api/worker.md)。底层及自动 Worker SDK、尝试/证据查询、Schema 与进程故障恢复已通过 [P2 阶段验收](reports/phase-2.md)。
1. 权限验证、凭据校验、命名空间过滤和队列消费授权。
2. 同一次事务更新任务、尝试、租约、并发许可、事件与流程交付记录。
3. CAS 竞争、数据库时间校验、门控与开始授权串行化。
4. 对幂等重复请求比较语义摘要,返回原响应或冲突。
5. 计划当前版本与版本内容原子发布,信号匹配正确的业务操作。
6. 查询轮次、预算、下一任务创建同事务提交;未知结果按证据核查。
7. 不可变版本发布前的流程图校验、表达式类型与结构化汇合验证。
局部模型方法不能被当作已经实现了 Worker 执行授权和业务效果幂等。P1 已实现范围与剩余边界见 [P1 报告](reports/phase-1.md)。
## 6. 迁移与回归
入口是 `orbit-migrate up|status`。应用启动及 readiness 检查迁移状态和 Orbit 表的外键数量,发现外键会拒绝启动/就绪,不执行 DDL。这不是全部手工结构漂移审计。迁移持有数据库级命名锁,逐语句应用;失败保留 dirty 标记。由于 MySQL DDL 不整体回滚,修复必须先对照语句和实际表结构完成核查,不提供自动清除 dirty 的命令。
已应用的迁移不得修改。后续使用新的前向迁移;回归环境使用独立临时数据库。初始迁移尚未在生产部署。
000002_task_api 增加任务分页索引,将业务参数、结果、回执、流程定义/输入/输出等字段改为校验过的 JSON 文本,避免原生 JSON 数字重编码。公共 Go JSON 扫描类型保持兼容,原模型职责不变。响应 DTO 与持久化模型分开;SQL 空结果先扫描到可空字节切片再转换,避免驱动与 json.RawMessage 的可空类型不兼容。
000004_execution_history 为已有证据表增加 `(namespace_id, attempt_id, created_at, id)` 分页索引,不新增实体或字段,不回改已有迁移。尝试列表复用执行代次唯一索引。查询 DTO 与持久化模型分离,只在详情读取业务正文,不返回执行秘密或内部摘要;接口见 [执行历史](api/execution-history.md)。
000005_safe_retry 增加任务 EffectUncertain、尝试与证据 Retryable 字段,Go 与 SQL 均附中文说明;实体数仍为 32。跨尝试的不确定效果必须保留,历史 NEEDS_REVIEW 保守回填 true,旧状态、版本和冻结策略不变。回执增加 `(namespace_id, attempt_id, operation, request_digest)` 索引,换消息身份重报结果也返回原已应用回执。字段语义、升级策略启用与限制见 [安全重试](api/retries.md),升级回归见 [阶段报告](reports/phase-3-safe-retry.md)。
000006_schedule_runtime 增加 Schedule.QueueID / ProcessingVersion / CheckAt 以及 ScheduleVersion.CatchupWindowMS / GraceMS。字段、单位、可空语义均有中文注释;CoveredUntil 明确为合并或跳过范围的排除边界。实体数不变,增加扫描、列表和任务来源索引。时间规则 JSON 冻结 CalendarVersion,服务内置相应时区快照。调度记录、生成任务、事件和进度同事务写入,旧版本积压不会被新发布覆盖;详见 [计划契约](api/schedules.md)。
000007_task_cancellation 允许尝试证据记录 CANCELLED,不新增实体;取消意图、结果和历史不确定效果的规则见 [取消契约](api/cancellation.md)。
000008_no_foreign_keys 移除历史 70 个外键,保留索引、唯一约束、CHECK、触发器和数据。旧迁移已在回归库应用,按不可变校验和约定保留原文件;新装按顺序运行至当前版本,最终结构同样为零外键。后续迁移禁止再引入外键。升级旧库时先停用旧服务,应用完整迁移再启动新版;迁移需要 DDL 锁,应在目标部署环境演练维护窗口。
000009_queue_resources 增加 Queue.MaxUnfinishedTasks、UnfinishedTasks、StartWindowUntil、StartsInWindow,Go 字段与 SQL COMMENT 均有中文语义,实体仍为 32。未终结任务按 WAITING/LEASED/RUNNING/NEEDS_REVIEW 回填;窗口初始为空、计数为零。运行计数与任务/开始授权同事务维护,不改变队列配置 revision,配置变更不能重置它们。降额允许已有使用量暂时超过新上限,不删除任务或撤销已有许可;详见 [队列契约](api/queues.md) 和 [资源回归](reports/phase-3-queue-resources.md)。
命令与验证记录见 [开发计划](08-development-plan.md)、[基础阶段验证报告](reports/phase-0.md) 与 [取消及无外键回归](reports/phase-3-cancellation.md)。历史阶段报告描述当时结构,当前约束以本页为准。
## 12. 应用共享额度
000010_application_resources 新增 ApplicationResources,使用 BINARY(16) 应用主键,12 个持久化字段均有中文注释和 SQL COMMENT,无外键。共享未终结任务数、活跃尝试数、未开始预占数与一秒开始窗口;配置 revision 不随用量变化。停止旧写入节点后回填所有应用,包含跨命名空间的既有在途状态。关联身份行与额度行分开,认证后按应用额度 → 队列 → 任务/尝试 → 许可顺序持锁。后台恢复不因应用停用而停止释放到期尝试。详细字段、权限、SDK 场景及并发边界见 [应用配额接口](api/application-resources.md)。
## 13. 显式资源键许可
000011_keyed_concurrency 增加 Task.ConcurrencyKey / ConcurrencyKeyDigest、Attempt.ConcurrencyToken、WorkerSession.ConcurrencyKeys,共四个中文注释与 SQL COMMENT 字段。历史值分别为空/空、零和 false,原 BusinessKey 语义不变;截至 000011 为 33 个实体、273 个直接声明的持久化字段,最终外键数保持零。
资源限制复用 ConcurrencyLimit:scope_key=key,key_digest 为域分隔 SHA-256,命名空间内唯一;每键最大许可固定 1。原始 UTF-8 字节精确匹配,不依赖 MySQL 的文本排序规则。一个有键尝试持有队列和资源键两条 Permit,应用活跃尝试只计一次;续租、释放、恢复与状态同事务完成。
键行 Revision 在领取和释放时递增,领取后的值保存到 Attempt.ConcurrencyToken;同尝试重连不改变序号,资源行不得删除或重置。达到整数边界时拒绝新领取,禁止回绕。SDK 交付序号,资源端负责原子比较与业务幂等;历史未知效果与执行许可释放分别维护。字段、升级、调用和异常语义见 [资源键契约](api/keyed-concurrency.md)。
## 14. 任务优先级和节点公平领取
000012_dispatch_priority 开放既有 Priority,范围 0–9,并增加对应 CHECK。新增只读生成列 DispatchAt(AvailableAt 减 Priority 乘30秒)及 `(namespace_id,queue_id,state,dispatch_at,available_at,id)` 排序索引;历史默认优先级 0 的排序时间等于 AvailableAt。Generated 字段随退避和恢复自动重新计算,不允许独立写入。33 个实体、274 个直接声明的持久化字段全部保留中文注释,新增 SQL 字段和 Priority 的 COMMENT 同步更新,无外键。
派发先检查实际到期、截止、能力、门控和资源键,再按排序时间选择。节点公平领取池根据认证后的 ApplicationID 分组,再按命名空间/队列轮转;内存队列和短期空探测缓存可在重启后重建,不保存授权事实。详细约束、SDK 和升级步骤见 [优先级与公平派发](api/priority-and-fairness.md)。
## 13. 可靠结果订阅核心
000013_result_subscriptions 新增 EventStream、ResultEvent,扩展 Subscription 和 Delivery。289 个直接声明字段均有中文注释。生产事务在独立流行锁下分配序号,与任务、原事件和结果快照一起提交;消费事务不锁流行、任务、队列或应用额度。交付记录和扫描位置一起落库,每条交付单独持有租约和证明。重复确认不重复扣减积压,耗尽保留记录,人工追加预算不重置历史。详见 [订阅核心](09-result-subscription-core.md)。对外 HTTP/WSS、管理与消费 SDK 已实现,持久化 Inbox 完整场景已交付。
## 14. 交付历史查询索引
000014_delivery_history 增加按订阅及可选状态过滤的创建时间分页索引,领取、确认和重试不改变排序键。不增加模型字段或外键,仍为35个实体、289个中文注释字段。查询按凭据所有者、命名空间和订阅校验,游标绑定过滤条件;详情按来源事件ID读取不可变快照且不返回交付秘密。见 [订阅查询接口](api/subscriptions.md)。
## 15. 独立接收方示例模型
[result-inbox示例](examples/result-inbox.md) 在业务方自己的数据库建立receiver_inbox和receiver_task_views,2个实体、17个Go/SQL中文注释字段,无外键。它们属于独立示例模块,不计入平台35个实体,也不加入平台迁移。Inbox去重记录与业务视图同事务提交;视图仅按更高任务版本推进,重复和迟到事件不能重复更新或回退业务结果。