feat: add durable relative task delays and outage recovery regression

This commit is contained in:
2026-09-17 23:43:57 +08:00
parent 522fe2ef3e
commit a664ec9370
34 changed files with 811 additions and 28 deletions

View File

@@ -0,0 +1,74 @@
# P3 相对延迟与重试全停恢复回归
验证日期:2026-09-17。本轮从[原范围核对](phase-3.md)发现并补齐相对等待时长的接口,同时补足“重试退避期间所有服务退出”的真实进程证据。全平台目标仍包含P4–P7,未宣称生产高可用或容量验收。
## 1. 实现与兼容
- 服务端和独立SDK新增可选delay_ms,默认0;首次受理在同一事务按数据库时间计算available_at,原等待时长参与请求摘要,重报先返回原身份回执,不重新计时。
- delay_ms小于任务总时限,非零不能与available_at同用;start_deadline_at仍校验计算后的最早执行时间。类型/溢出/非法组合明确拒绝。
- 零值omitempty维持历史请求摘要;[兼容测试](../../internal/storage/mysql/task_delay_test.go)以新增字段前的规范化JSON形状计算摘要作比较。批量复用同一受理逻辑;Outbox保留时长,不在业务发送端换算。
- 计划模板明确拒绝非零delay_ms,由已发布规则决定时间;人工重试使用原有新任务时限/绝对时间参数,不重新继承首次延迟。
- 不新增迁移、外键或持久化字段;仍为35个平台模型/289个直接持久化字段。新增命令字段有中文说明,执行后的退避继续使用既有available_at。
- [延迟SDK示例](../examples/delayed-task.md)执行实际Worker并查询开始时间;[查询示例](../examples/task-query.md)增加不同参数共享业务关联键及原输入保留核对。接口与持续计划同步。
## 2. 本轮测试的实质断言
| 测试 | 核对内容 |
| --- | --- |
| 规范化/解码 | 小于总时限边界、最大整数拒绝、相对与绝对互斥、null/负数/小数/字符串/溢出/重复字段拒绝;规范化不能提前将相对时间解析为客户端时刻 |
| HTTP/MySQL受理 | available_at精确等于created_at+5000ms;8个同键并发返回同一ID及原时间;改变时长或改为对应绝对时刻冲突;错误时间关系不受理;显式0兼容旧请求;批量每项使用自己的首次受理时间 |
| 真Worker延迟 | 相对1500ms及绝对时刻两项任务实际完成,尝试创建和开始时间均不早于保存的available_at;等待经过后原键重报仍取原回执,已完成任务不重开 |
| Outbox | 冻结文档保留delay_ms及精确数字,不创建available_at,不改变数据库依赖边界 |
| 改参数的关联任务 | 相同business_key、不同提交身份与不同format,GetTask确认两份输入各自保留;查询后取消三个样本,保留审计 |
进程测试:[TestIntegrationWorkerProcessRetryBackoffRecovery](../../sdk/go/retry_process_integration_test.go)。四个子场景都先确认第一次结果已入库并进入WAITING,保存完整旧尝试;Process.Kill终止唯一Orbit节点,Wait确认信号退出,然后启动两个不同node_id的新进程。
1. 明确可重试失败:8秒上限、半区间抖动,重启后完整任务快照不变;两个节点竞争只取得一次Epoch=2,实际领取时间不早于旧available_at,最终成功。
2. IDEMPOTENT未知效果:第二次明确可重试失败恰好耗尽2次预算,保留effect_uncertain,进入NEEDS_REVIEW,不能伪装为普通失败或创建第三次尝试。
3. 明确失败后退避,停机跨过5秒总时限:重启后FAILED,只保留首次尝试,不因恢复开启第二次。
4. 未知效果后退避,停机跨过总时限:重启后NEEDS_REVIEW且不确定性保留,同样只有首次尝试。
全部场景再次读取旧尝试,逐字段确认结果/分类/结束时间未被改写;核对冻结策略、总截止、输入、已用次数及队列未终结数。新节点保持在线并有容量,短窗口再次读取分配确认没有额外尝试。测试不修改数据库的available_at、deadline_at、lease_until;到期自然流逝,时间断言使用数据库记录。它验证进程/数据库逻辑,不验证跨故障域复制或外部系统副作用。
## 3. 环境、命令与实际结果
Darwin 24.6.0 arm64、12核/32 GiB宿主、Go 1.27.1、Docker内MySQL 8.4.10单实例。此硬件描述不是压测配置或容量结论。所有数据库为脚本创建的一次性容器,随机密码/回环端口,凭据仅存在私有临时文件;强杀仅影响测试拥有的子进程。
```sh
make fmt test vet build
make integration
```
make integration在停止初始服务后执行进程组,避免残留恢复节点掩盖接管;本轮新增测试已包含在原WorkerProcess匹配规则,新增delayed-task示例也进入正式脚本。
定向进程组在隔离库中使用已编译服务和专用Worker凭据执行:
```sh
cd sdk/go
go test -race -count=3 \
-run '^TestIntegrationWorkerProcessRetryBackoffRecovery$' -v .
```
需要隔离环境提供ORBIT_TEST_MYSQL_DSN、ORBIT_TEST_SERVER_BINARY、ORBIT_TEST_WORKER_URL、ORBIT_TEST_WORKER_CREDENTIALS_FILE;未提供时测试跳过,不能算数据库/进程证据。
| 执行 | 实际结果 |
| --- | --- |
| 四种进程场景重复3轮 | PASS,33.42 / 33.05 / 37.18秒,总包104.892秒,命令终态0;随后新增delay_ms再通过全量组验证 |
| 格式/竞态单元/静态检查/四模块构建 | PASS,make终态0;未设集成环境时的Integration跳过不计入真实数据库证据 |
| 全量集成/全部示例/进程组 | PASS,正式脚本终态0,18个核心SDK示例加Inbox/Outbox共20个,10个SDK节点/Worker进程测试(新增测试含4场景),外加Inbox/Outbox各1个进程故障测试 |
全量组记录:存储105.663秒;Worker91.765秒(新增相对/绝对延迟4.10秒);计划6.881秒;队列4.506秒;批量1.502秒;查询2.972秒;订阅管理33.485秒;事件消费54.737秒;人工恢复3.358秒;Inbox34.630秒;Outbox33.802秒;应用配额1.322秒;最终进程组95.151秒(新增四场景33.15秒)。所有组均完成,未根据中途PASS推断脚本结束。结束后确认脚本创建的容器已清理。
新增例子实际输出state=SUCCEEDED、attempts=1、no_early_start=true、original_receipt=true、worker_drained=true;改参数查询例子输出original_payload_preserved=true,均由程序断言后产生。SDK在GOWORK=off依赖图中无服务端、模型或MySQL依赖。
本轮没有发现需修改执行内核的故障;相对延迟是原范围核对发现的遗漏实现。新增回归和示例在同一次最终全量脚本执行前已经定稿,后续只更新文档证据。
本次修改前另执行零外键专项回归:MySQL 8.4.10、新建及v7升级、归属关系校验共11.738秒,终态0;本轮全量再次通过对应回归。所有耗时仅为回归耗时,不是性能指标。
本地执行日志位于/tmp/orbit-retry-process-focused.log、/tmp/orbit-delay-check-final.log及/tmp/orbit-delay-integration.log;临时日志不是SDK或部署依赖。本报告保留关键结果与可复现入口。
## 4. 限制
- delay_ms相对Orbit成功受理事务,不相对客户端发起或业务Outbox创建。可执行时间是下界,排队/暂停/故障可能推迟实际开始。
- 旧服务不支持该输入字段,启用前先升级服务;没有据此宣称混合版本滚动升级通过。
- 本轮没有MySQL主从切换、备份恢复、持续负载、重连风暴或生产容量数据;完整首版仍按[阶段核对](phase-3.md)与[持续计划](../08-development-plan.md)推进。

64
docs/reports/phase-3.md Normal file
View File

@@ -0,0 +1,64 @@
# P3 Pulse 功能范围核对与阶段验收
核对日期:2026-09-17。依据[原功能说明](../01-functional-spec.md)、[可靠性设计](../04-reliability-and-data.md)、[开发计划](../08-development-plan.md)及[全平台发布门槛](../05-production-acceptance.md)逐项检查。此报告区分Pulse功能交付和全平台生产验收;后者仍未通过。
阶段结论:P3平台功能范围已核对并通过本轮完整回归,可推进P4 Weave。新增相对延迟及四种重试全停场景已包含在最终脚本,20个可运行示例通过,命令终态0。下表明确保留首版尚未完成的生产门槛及示例交付项;不能用阶段验收替代它们。
## 1. Pulse功能与证据
| 原要求 | 当前行为与主要证据 |
| --- | --- |
| 立即、相对延迟、指定时间提交 | 立即受理、数据库时间计算delay_ms、绝对available_at;[HTTP事务用例](../../internal/storage/mysql/task_integration_test.go)核对同键并发、相对时长、非法组合与批量;[SDK真实执行](../../sdk/go/task_delay_integration_test.go)核对数据库创建/开始时间不早于保存的执行时间 |
| 批量受理、逐项恢复 | 最多32项/512 KiB,单项事务和身份复用,区分明确失败/未知/未尝试;[批量报告](phase-3-batch-tasks.md)记录部分已提交后的真实HTTP回执丢失及原键补交 |
| 幂等及不可变参数/策略 | 任务、身份回执、事件与审计同事务;同语义原回执、变更冲突;[任务用例](../../internal/storage/mysql/task_integration_test.go)核对16并发只创建一次和精确数字;新增可选字段的[旧摘要兼容](../../internal/storage/mysql/task_delay_test.go) |
| 改参数形成关联新任务 | 新提交身份和相同business_key,旧输入保持;[查询示例](../../sdk/go/examples/task-query/main.go)分别读取text/json任务核对输入。该关联不等于业务去重或retry_of,原任务需调用方决定如何处理 |
| 单次/总时限、最晚开始、安全重试 | 冻结策略、有限退避及未知效果;[决策](../../pulse/retry/decision_test.go)、[真实事务](../../internal/storage/mysql/worker_retry_integration_test.go)核对预算/回滚/迟到;[全停恢复](phase-3-delay-recovery.md)保留退避、次数和截止,过期不补发 |
| 等待/运行取消 | 持久化意图、预占阻止开始、运行收集结果、未知效果仍核查;[取消事务](../../internal/storage/mysql/worker_cancel_integration_test.go)验证开始竞争/回滚,[取消报告](phase-3-cancellation.md)包含自动SDK通知、丢通知及不协作处理器 |
| 人工核查与重试 | 专门权限、版本、原因/证据、安全声明;核查不改旧尝试,人工重试创建唯一直接后继;[恢复用例](../../internal/storage/mysql/task_recovery_integration_test.go)验证竞争、审计失败回滚、原输入/结果保留及管理所有权;[接口/故障报告](phase-3-task-recovery.md) |
| Cron/固定间隔/时区/边界 | 五段Cron、fixed-rate、显式时区快照、开始/结束;[规则测试](../../pulse/schedule/rule_test.go)和[调度报告](phase-3-schedules.md)覆盖夏令时缺失/重复时刻;不支持的fixed-delay明确拒绝 |
| 唯一触发、版本与漏跑 | SKIP/COALESCE/CATCH_UP及有限窗口/批次/持久化限速,任务与进度同事务;[调度事务](../../internal/storage/mysql/schedule_integration_test.go)竞争只生成3项、扫描冷却有效、版本和覆盖范围保留 |
| 计划暂停、重叠与恢复 | PARALLEL/SERIAL/SKIP,暂停不取消旧任务,串行遇未知效果继续阻塞;[调度进程恢复](../../sdk/go/worker_process_integration_test.go)先杀唯一节点,再由两个新节点恢复真实积压 |
| WSS、能力、容量、租约、排空 | 独立Worker及自动SDK;[P2验收](phase-2.md)和[执行事务](../../internal/storage/mysql/worker_integration_test.go)核对16个竞争请求只产生2个允许的许可、重连不重置容量、旧会话/旧尝试拒绝 |
| 队列与应用共享额度 | 应用额度→队列统一锁顺序,开始窗口、未终结数、限速及背压;[队列报告](phase-3-queue-resources.md)、[应用报告](phase-3-application-resources.md)含跨命名空间竞争、回滚、原回执及进程恢复 |
| 同资源互斥与旧执行隔离 | 显式concurrency_key、跨队列单许可、递增资源令牌;[资源键用例](../../internal/storage/mysql/keyed_concurrency_integration_test.go)验证竞争、释放一次、序号不回绕;[SDK/资源端示例](phase-3-keyed-concurrency.md)展示拒绝旧令牌 |
| 优先级、项目间派发与缓冲上限 | 0–9冻结优先级、等待补偿、节点内应用/队列轮转、有界池和发送缓冲;[优先级报告](phase-3-priority-fairness.md)包含真实数据库应用行锁阻塞时其他应用仍能领取。多节点不均衡下延迟和容量留P6 |
| 权限、隔离、敏感数据 | 身份/命名空间/动作/队列范围按事务检查;[任务](../../internal/storage/mysql/task_integration_test.go)、[来源](../../internal/storage/mysql/task_source_integration_test.go)及[历史](../../internal/storage/mysql/task_history_integration_test.go)回归验证跨范围拒绝、当前权限和不返回秘密;凭据安全轮换仍待P6 |
| 查询、审计与来源 | ID、状态、队列、精确业务键、创建时间和绑定筛选的分页;[查询](phase-3-task-queries.md)、[来源](phase-3-task-source.md)、[恢复审计](phase-3-task-recovery.md)。流程来源随Weave接入,损坏/未知所有者返回503,不能假装普通任务 |
| 持久化结果、订阅与接收方去重 | 结果流序号按提交顺序分配,有限交付/证明/ACK、暂停与人工补投;[订阅核心](phase-3-result-subscription-core.md)、[通信](phase-3-result-subscription-transport.md)、[消费器](phase-3-event-consumer-sdk.md)、[独立Inbox](phase-3-result-inbox.md)包含真实COMMIT和进程故障 |
| 业务事务到平台的可靠提交 | [独立Outbox](phase-3-task-outbox.md)冻结目标/操作/请求,业务记录同事务,HTTP事务外,原键恢复,24小时/100次预算,三处COMMIT响应丢失和真实发送进程退出 |
| SDK、接口文档与实例 | sdk/go为独立module;每项能力随附Markdown契约及可运行示例。文件转换样例目前只受理/取消,实际处理示例为文本规范化与对象任务;P7仍须交付计划要求的真实文件转换例子,不能把受理样例当作文件生成 |
| 数据模型、无外键 | 35个平台实体/289个直接持久化字段维持中文说明,SQL列同步COMMENT;[真实升级](../../internal/storage/mysql/no_foreign_keys_integration_test.go)核对70→0、原数据与323项未替换索引组件保留。后续禁止外键/级联 |
## 2. 全平台故障门槛的证据边界
“平台逻辑已验证”不表示对应场景在任意生产拓扑达标。此表保留原A编号,未由当前证据覆盖的部分不能打勾为完成。
| 门槛 | 当前证据及未覆盖部分 |
| --- | --- |
| A01–A02 | 单项/批量并发幂等已验证;批量、人工恢复、Outbox有真实响应截断后的原身份恢复。首次受理及业务事务COMMIT未知不能更换身份 |
| A03 | 两个逻辑节点的真实MySQL竞争已核对配额/许可;本轮两个真实新进程仅一个拿到重试尝试 |
| A04 | 未开始租约回收及旧证明拒绝有事务回归;尚缺“派发已提交但task.assign尚未到达Worker时强杀节点”的精确进程注入,列入P6,不能用普通节点重启替代 |
| A05 | 真实start/finish确认丢失并先后强杀节点,由其他进程恢复同一尝试/回执,处理器只运行一次 |
| A06 | Worker已写入同步外部标记后强杀,平台保留UNKNOWN/核查且释放许可;幂等声明下有限恢复有事务/SDK证据。真实接入方业务写入的幂等仍需接入验收 |
| A07–A09 | 旧会话/代次、迟到证据、续租/完成/回收竞争、跨节点重连与资源令牌隔离已验证;物理旧进程仍可运行,需要业务资源端防护 |
| A10 | 重复容量、SDK接收缓冲耗尽、有界请求/发送及断线恢复已验证;持续慢消费者和重连风暴负载仍待P6 |
| A11–A12 | 取消竞争和UNKNOWN安全分流已验证;模拟处理器的错误不证明任意外部渠道都满足幂等/核查协议 |
| A13–A14 | 真实MySQL触发竞争、真实服务全停后计划恢复、版本/时区/漏跑已验证;生产大规模集中补偿性能仍待P6 |
| A15 | 结果交付、HTTP/WSS确认丢失、Inbox事务及消费者强杀后去重已验证;保留期清理与真实持久化介质保障仍待P6 |
| A16–A17 | 跨队列资源键和跨命名空间应用额度竞争、当前权限隔离已验证;多节点混合负载下公平性及凭据安全重绑仍待P6 |
| A18 | 受控MySQL COMMIT响应丢失已验证;MySQL停机、复制主节点切换、持久化/复制确认组合尚未验收 |
| A19–A20 | 混合版本滚动升级、清理/归档与重报补投竞争尚未实现完整验收;保留原首版范围,P6必须补齐 |
| W01–W17 | 尚未完成Weave运行引擎。模型和约束测试不能证明流程发布、推进、信号、并行、暂停、恢复或轮询已实现 |
## 3. 后续仍需完成的原范围
1. P4:研发SDK定义与发布、不可变版本、实例、顺序/分支/有限并行汇合、信号与定时等待、同事务创建下一项任务、内部事件持久化接续。
2. P5:有界轮询、独立轮次/尝试预算、暂停门控、继续、取消、人工恢复、新激活隔离及审计;Weave管理任务不能绕过所属流程。
3. P6:上表未完成故障门槛、可轮换凭据和安全重绑、历史清理、完整指标/告警/日志策略、负载工具、单点/复制拓扑、备份恢复与发布运行手册。容量和RPO/RTO需真实拓扑证据。
4. P7:包括实际文件转换在内的完整接入场景、独立SDK远端取包/版本与兼容验证、全部命令/链接/API/示例及首版清单复核。
上述内容没有因Pulse阶段验收而移出首版目标。当前仓库的实现及本地回归不能被描述为已上市产品的生产验收。
## 4. 复现与最新结果
本轮环境、命令、新增测试、完整脚本终态及限制见[延迟与停机恢复报告](phase-3-delay-recovery.md)。全量脚本已确认终态0;历史子阶段报告的“下一步/未实现”描述各自当时状态,当前范围以本表和持续计划为准。