# P3 计划调度阶段记录 验证日期:2026-09-17。已交付计划管理与持久化调度闭环,P3 仍有运行中取消、配额/背压、可靠订阅待完成;本报告不替代全平台生产验收。 ## 1. 已实现行为 - 五段 Cron 与 fixed-rate;显式时区、开始和结束边界,固定间隔保持基准,不按任务完成时间漂移。 - 时区快照随定义冻结,服务从内置数据加载,不依赖宿主机或 ZONEINFO;兼容已有快照是后续升级约束。 - 创建、队列分页、详情、完整版本发布、历史版本、暂停/恢复、触发历史。每个命令有当前权限校验、幂等回执与管理审计。 - SKIP / COALESCE / CATCH_UP 漏跑策略与 PARALLEL / SERIAL / SKIP 重叠策略;明确补偿窗口、延迟容差、批量和扫描间隔。 - 发布新版本保留旧生效边界前的积压;跳过和合并范围不越过版本边界。已产生任务保留当时模板。 - 多节点用数据库锁与唯一触发身份竞争;触发决定、对应任务、事件及进度一起提交。 - 实际 orbit 命令持续扫描,独立 SDK 与 [完整周期快照示例](../../examples/schedules/main.go) 同步交付。 - 迁移 000006 增加运行字段与索引,实体仍为 32 个;新模型字段及 SQL COMMENT 均为中文;源码核对 32 个持久化模型、253 个直接声明的持久化字段均有中文说明。 完整字段、权限、状态与示例见 [接口手册](../api/schedules.md)。 ## 2. 环境、命令及结果 macOS 本机,Go 1.27.1,Docker 单实例 MySQL 8.4.10。临时随机凭据、回环随机端口,运行结束清理容器;不使用业务数据库。 ```sh make fmt make test vet build make integration cd sdk/go GOWORK=off go test -race ./... GOWORK=off go vet ./... ``` 上述检查均已通过。未配置集成环境时,普通 go test 会跳过真实数据库/进程用例;实际证据来自 make integration。 最终完整集成输出: | 测试组 | 本次结果 | | --- | --- | | MySQL 全部集成 | PASS,44.712 秒 | | 计划事务与管理子集 | PASS,31.62 秒 | | SDK Worker 连接/运行器及安全重试 | PASS,62.912 秒 | | SDK 计划生命周期 | PASS,4.43 秒,含包启动/竞态开销 5.675 秒 | | 真实进程故障组 | PASS,21.487 秒 | | 新增调度进程强杀恢复 | PASS,10.23 秒 | | 七个可运行示例 | health / tasks / worker-protocol / worker / history / retry / schedules 全部通过 | 时间为一次测试运行耗时,不能用作吞吐、延迟或容量指标。 ## 3. 时钟与规则证据 [规则测试](../../pulse/schedule/rule_test.go) 验证:五段/分钟边界、亚洲时区、纽约缺失/重复小时、Lord Howe 半小时夏令时变更、Apia 整天跳过、2096→2104 世纪闰日、月日/星期 OR、固定间隔微秒和跨 292 年计算、结束排除边界、规则副本隔离及无解日期拒绝。 [时区测试](../../internal/timezone/zone_test.go) 核对内置快照完整 SHA-256、禁用外部 ZONEINFO 后仍能加载、精确名称及缓存,以及未知快照拒绝。快照来自 Go 1.27.1,附 Go 许可。Cron 库版本固定为 v3.0.1,只用于解析和 UTC 日历计算,运行不依赖外部调度服务;SDK 未新增该依赖。 [有限决策测试](../../pulse/schedule/planner_test.go) 覆盖补偿窗口截断、跳过、合并、有限逐点补跑、串行阻塞、重叠跳过、未来点不处理,以及合并不越过版本生效边界。计算器不自行读取系统当前时间,执行使用资源锁定后的数据库时间。 ## 4. 实际数据库与 SDK 证据 [数据库用例](../../internal/storage/mysql/schedule_integration_test.go): - 两个不同节点身份并发扫描同一到期计划,只创建本批三个唯一任务;再次扫描不能绕过持久化冷却时间。 - 禁用命名空间后,即使计划已到期也不再产生任务。 - 创建原键重报、同键不同内容冲突、暂停禁止触发、恢复原回执、跨命名空间/队列范围拒绝。 - 暂停期间发布新模板,旧积压仍用旧版本;历史版本不可被新模板覆盖,旧点处理后游标推进到新版本。 - 连续发布三个有限版本:第二版本尚待初始化时再次发布,仍保存其积压;空历史版本之后的末版全部处理完时,不重新扫描已完成触发点。新增用例先复现了漏掉中间版本和重复写入冲突,再验证修复。 - 超过时间窗口的跳过范围、漏跑合并范围与任务关联实际落库,范围结束边界大于起点。 - SERIAL 在待执行任务存在时等待;明确取消前项后继续;前项进入 UNKNOWN / NEEDS_REVIEW 后再次停止推进。 - 通过测试触发器让 occurrence 写入失败,任务、触发记录及游标整体回滚;故障解除后正常提交。 - 触发游标分页无重复,不能跨计划复用游标;任务模板中的大整数文本保留。 上述用例以真实时间等待形成积压,不把原触发时间改到计划创建前。少量用例将受控行 check_at 设为当前时间,以加速核对连续历史版本或阻塞状态;冷却限制另有未改写 check_at 的竞争测试。 [SDK 用例](../../sdk/go/schedule_integration_test.go) 连接实际服务,通过独立 SDK 创建暂停计划、幂等核对、恢复、自动 Worker 成功、暂停、发布 Cron 新版本、读取两个版本、原发布回执重报、列表及暂停期间不新增触发。业务结果中的 `9007199254740993` 保持精确。 ## 5. 实际进程强杀恢复 新增 [TestIntegrationWorkerProcessScheduleRecovery](../../sdk/go/worker_process_integration_test.go) 使用构建后的 orbit 可执行文件和临时 MySQL: 1. 节点 A 创建一个每秒触发、共八个原触发点的有限计划,CATCH_UP / PARALLEL,每批上限二。 2. 观察到首个触发已经落库后,向 A 发送真实强杀并 Wait 确认退出。 3. 保留三秒无 Orbit 服务的间隙形成漏跑,不修改数据库时钟或计划游标。 4. 启动身份不同的 B、C 两个真实服务进程,从持久化进度竞争接续。 5. 查询至计划结束,严格核对恰好八条 FIRED、八个不同 TaskID、原 scheduled_at 序列及相同版本;每条都有完整 WAITING 任务与原模板。 这证明了本地单 MySQL 下服务进程消失后的持久化接续与多进程触发去重。本用例不启动业务 Worker,不宣称验证外部副作用 exactly-once。原开始/完成 ACK 丢失后强杀接管、Worker 进程中断转核查等 P2 用例也同步通过。 ## 6. 可运行场景与当前边界 周期快照示例输出两条不同的 occurrence/task 身份,均完成 SUCCEEDED,随后 `paused=true completed_tasks=2 current_version=1`、`worker_drained=true running=0`。示例不访问外部业务资源;计划有十秒结束边界,正常完成后暂停,历史保留。 仍需后续验证或开发: - 运行中任务取消、细粒度限流与队列配额、可靠结果订阅;流程引擎及人工核查控制。 - 调度期间数据库故障切换、提交 ACK 丢失的专门故障矩阵、跨主机网络分区、备份恢复和混合版本升级。 - 大表在线 DDL、历史人工计划行核查;000006 不自动启用旧的未绑定队列行。新建数据库与既有 v4 数据升级到当前结构的通用测试通过,但这不是旧人工计划内容的完整迁移验收。 - 大规模公平性、行锁竞争、重试/补跑风暴、历史保留与清理、租户配额。当前批量、扫描间隔和参数复制限制只建立有界基线,没有百万租户容量结论。 后续继续 [持续计划](../08-development-plan.md),不将本阶段闭环标为整个 P3 或最终目标完成。