Files
orbit/docs/reports/phase-3-safe-retry.md

75 lines
6.5 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.
# P3 安全重试阶段记录
验证日期:2026-09-17。此报告只完成 P3 中的安全重试部分;P3 及总目标继续进行,不代表全平台生产验收通过。
## 1. 本轮实现
- 按受理时冻结的次数、退避及截止时间决定是否新建下一次执行;同一 Task.ID、不同 AttemptID/Epoch,领取时消耗预算。
- 明确失败通过 WorkerFailure.Retryable / task.finish.retryable 显式声明可安全再试,默认 false;未知效果只有 IDEMPOTENT 且预算允许时重试。
- 保存跨尝试的 effect_uncertain,预算耗尽、后续失败、未获开始授权即到期、等待取消或截止都不会将旧未知效果伪装成普通终态。
- 抖动退避写入数据库 available_at;与尝试结果、许可释放、事件、结果审计及操作回执原子提交。后台回收保留含节点身份与决策的事件。
- 结果换消息身份重报也取原已应用回执;过滤早到的证据回执,避免借用后续尝试状态。
- 前向迁移 000005 增加三个中文注释字段和结果回执索引;实体仍为 32 个,旧迁移未改动。
- 同步独立 SDK、Worker Schema、查询字段、[接口手册](../api/retries.md) 与 [完整示例](../../examples/retry/main.go)。
## 2. 环境与执行命令
本机 macOS、Go 1.27.1、Docker 内单个 MySQL 8.4.10。隔离数据库使用随机密码和回环随机端口,测试结束删除临时容器;不连接业务数据库。升级测试在同一临时实例内另建独立数据库,从迁移 1–4 重建旧结构再运行正式 Migrate。
```sh
make fmt
make test vet build
make integration
cd sdk/go
GOWORK=off go test -race ./...
GOWORK=off go vet ./...
```
全部通过。根模块、SDK 的 make test 使用竞态检测;不设置集成环境时数据库测试跳过,真实数据库证据以 make integration 的记录为准。
最终完整集成输出:存储包 13.464 秒(含 v4 升级 3.19 秒、安全重试事务用例 1.51 秒);SDK 连接/运行器包 62.580 秒(含安全重试 7.80 秒);真实进程故障包 11.263 秒。时间为一次测试运行耗时,**不是吞吐或延迟基准**。此前还完成过一轮主链路回归,本次因新增升级、约束、早到证据回执及示例,再运行完整集成。
## 3. 实际覆盖的安全行为
| 层次 / 用例 | 核对结果 |
| --- | --- |
| 纯决策 | 默认失败不重试、显式失败重试、未知效果安全分流、预算耗尽、旧不确定性保留、总截止/最晚开始、等于截止拒绝 |
| 退避 | 256 个尝试身份的有界分布、同身份确定值、指数上限、1 毫秒边界、Duration 溢出保护 |
| MySQL 事务 | 失败 → WAITING → 新尝试成功;等待期间不占许可、无任务结果;旧尝试保留分类与结果 |
| 原回执 | 同消息重报不改变下一时间;改 retryable 冲突;后续成功后重报旧失败仍返回原 WAITING 回执 |
| 早到证据 | 同一结果在开始前作为证据、开始后正式应用,新身份再次重报取 APPLIED 回执 |
| 未知效果 | IDEMPOTENT 可有限重试,RECONCILE_ONLY 直接转核查;后续失败/未知/预开始失效/取消/截止均不抹掉旧不确定性 |
| 恢复竞争 | 两个执行存储节点并发回收同一尝试,只安排一次重试、一次许可释放和一次 requeued 事件 |
| 暂停与迟到 | 队列暂停阻止重试领取;迟到成功只保存证据,不清除不确定性;恢复队列后新有效尝试可成功 |
| 原子回滚 | 用测试触发器使结果审计写入失败,任务、许可、事件与回执整体回滚;原消息重报后提交成功 |
| 数据库约束 | 运行中的尝试不能声明 retryable,UNKNOWN 证据不能声明 retryable,终态不能标为 effect_uncertain |
| 升级兼容 | v4 NEEDS_REVIEW 回填 true,其他状态默认 false;状态、revision、冻结策略不变;重复迁移无二次修改 |
| 摘要兼容 | 省略或 false 的 retryable 与旧 finish 摘要结构完全一致 |
| SDK 真服务 | 明确失败后成功、幂等未知后成功、默认未知转核查、可重试失败耗尽;查询次数、Epoch 和历史分类 |
| SDK ACK 故障 | 第一次失败已提交并进入退避后丢失 finish ACK,运行器重连核对原回执,未重置预算或重复调用同一尝试 |
| 协议 | Schema 与实际校验接受 FAILED/true,拒绝 UNKNOWN/true;既有 WSS 消息契约回归通过 |
部分服务层用例将受控测试任务的 available_at/lease_until 改为到期值,以触发竞争路径;它们不用于证明真实计时精度。SDK 用例没有此类时间改写,核对第二次领取不早于第一次结束后的最小退避。
原 P0–P2 存储、权限、精确 JSON、历史、大载荷、WSS、自动续租及实际进程强杀回归同步通过。真实进程测试仍是原 P2 用例,验证同次 ACK 恢复及 RECONCILE_ONLY 的未知转核查;本轮未额外宣称已完成“新重试等待期间强杀所有服务节点”测试。
## 4. 六个可运行示例
make integration 已运行 health、tasks、worker-protocol、worker、history、retry。新增 retry 的实际输出(任务 ID 略):
```text
task=<任务ID> state=SUCCEEDED attempts=2 first_retryable=true result={"text":"SAFE RETRY"}
worker_drained=true started=2 confirmed=2 running=0
```
第一次故障在产生业务效果前注入,第二次执行文本规范化。Worker 的 confirmed=2 统计尝试报告,不代表完成两个任务。例子每次使用独立操作和处理器身份;正常排空,记录与审计保留。没有外部业务系统副作用。
## 5. 当前边界与后续
- 自动重试不提供端到端 exactly-once;声明 IDEMPOTENT 的处理器需在真实资源侧验证幂等和迟到执行隔离。Go 不能终止忽略 context 的旧 goroutine。
- Cron/固定间隔、计划版本与漏跑、运行中取消、rate_per_second/配额背压、可靠事件订阅尚未完成。人工核查/恢复入口也尚未实现。
- 本次只验证停旧服务后升级结构;未验证混合版本滚动升级、向旧二进制回退、大表在线 DDL 或生产数据库故障切换。升级会为旧的未结束多次尝试任务启用已冻结策略,详见 [兼容说明](../api/retries.md#6-回执与升级兼容)。
- 本地单 MySQL 与回环多进程不能代替跨主机高可用、复制确认、备份恢复、RPO/RTO 或容量验收。并发与重试风暴负载将在 P6 固定拓扑和负载模型后测量。
下一步继续 P3 的调度与控制闭环,按 [持续计划](../08-development-plan.md) 同步实现、SDK、示例与实际回归。