# 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) 与 [完整示例](../../sdk/go/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、示例与实际回归。