Files
lislgosms/docs/long-sms-receipt-review-20260918.md

6.5 KiB
Raw Permalink Blame History

2026-09-18 长短信回执优化复核

范围:用户要求检查最近长短信回执优化仍有无Bug。本轮审查、隔离复现与预生产只读核对,不修改业务代码,不提交/推送/部署,不触发线上短信发送、补发、重投、账务或配置变更。实际应用1676cfe,关注phase-4-send-pipeline-redesign.md第10节。

确认问题(修复前1676cfe基线,行号为当时版本)

P1:最终失败后,矛盾成功回执可以改成成功,但不恢复账务或客户通知

位置:api/src/send-chain/send-receipt.service.ts:307356。当前只有RECEIPT_TIMEOUT终态和delivered→failed方向保护,没有failed→delivered终态规则。recordReceiptSegment直接覆盖分片状态,当同次尝试的失败分片随后变成成功、其余分片也成功时,aggregate成为delivered,主消息直接更新成功。成功分支不处理已退款账务;CMPP dedupeKey与HTTP eventId固定,旧失败通知继续保留。

独立真实PG复现:构造合法的“最终失败+refunded账单+已生成失败通知”快照;同次两分片随后各到成功回执,通过实际SendChainService/耐久工作协调器处理。结果消息delivered、账单refunded、通知payload.receiptStatus=undelivered。事务和通知唯一键无法修复互相矛盾的终态规则。本轮使用真实持久层、实际回执/通知代码,不运行网络投递进程。该复现从已退款快照开始,不宣称执行了实际线上退款。

建议:明确并固化最终失败后的矛盾回执策略。若最终结果不可逆,保留审计并生成异常;若业务允许纠正,必须设计账务与通知的完整补偿,不可只改消息状态。

P1:同一业务短信不同尝试复用上游Msg_Id时,回执可能跨通道匹配且同时更新两次尝试

位置:send-receipt.service.ts:604620 exactMessage分支,仅按messageRecordId+gatewayMessageId选最新分片,未限定incoming channel/上游身份;494519 recordReceiptSegment的updateMany同样未使用已传入submitRecordId或channelId。

独立PG复现两个不同通道、同业务短信两次尝试的相同gatewayMessageId,通过实际chain.handleReceipt入口:resolved.channelId指向另一通道,两次尝试的分片都被改成delivered(预期仅一条,无法唯一确认应拒绝匹配)。Msg_Id仅供应商作用域内有意义,不能以全平台无碰撞为前提。这会污染历史审计及当前成功汇总;工作表按sourceSubmitRecordId加锁不能修正此前错误归属。

建议:关联时结合逻辑通道/真实上游身份和唯一发送尝试;分片更新必须带resolved.submitRecordId(历史兼容使用可确认的submitId及身份),禁止一条回执批量跨尝试修改。

P2:已经最终失败的后续分片仍重新执行补发选路

位置:send-receipt.service.ts:334342、send-retry.service.ts:334406。仅存在retryOfSubmitRecordId时复用已有补发,未保存“本次已确认无路可补、最终失败”的不可重复决策。后续合法失败分片成为新事件,会再次执行findApplicationRoute/selectChannelForMessage,并重新走终态副作用。新工作revision不意味着应该重复执行同一终态决策。

独立PG复现两分片先后失败:首片处理后消息已经failed,第二片仍调用选路;选路计数2。该用例仅将选路边界隔离为确定的BadRequest“无可用通道”,其余实际SendReceipt/SendRetry、工作表及数据库/通知路径真实执行;不是全量真实路由配置验收,不启动供应商网络。

预生产证据:2026-09-18 10:1210:20,回调/发送worker stderr共719条sms_retry_route_failed、关联692条消息,错误均“无已报备通过且在线的可用通道”。21条消息重复2~3次,核验当前全部failed;逐条比对最早CMPP最终通知createdAt和日志秒时间,至少5条在最终通知已创建后仍出现选路失败日志。日志时间只有秒,不能据此否定其余同秒的重复。该问题增加查询/日志/事务开销,不能把1655次正常补发尝试全部归因于它,也不能量化其占CPU56.8%的比例。

建议:给发送尝试保存明确终态决策,后续事实仍审计/异常检测,但复用已提交的最终失败、退款及通知事实,不再选路。和第一项统一状态机处理,不能粗暴丢弃所有后到回执或unknown转明确结果。

已排除及线上边界

  • 怀疑“第二片回执先于其SubmitSegmentResult造成永久丢失”未复现:实际外层缺少可靠sourceSubmit关联时抛出404,由Inbox等待;补齐分片元数据后重处理可正确齐段成功。保留初次该断言失败日志,最终作为通过用例而非Bug。
  • 以昨晚21:24上线后为边界查询:当前delivered且billingStatus=refunded计0;新分片中同messageRecordId+gatewayMessageId跨submitRecordId碰撞组0。只能说该窗口当前数据未发现上述两类命中,不是永久不可能发生,也不是全历史完整审计。
  • 当时18136个SmsAttemptCompletionWork全部idle;上一轮CPU诊断未发现旧的重复补发/下游回执唯一键冲突。上一轮原子认领与防重复创建机制有效,但不等同所有状态机/关联边界正确。

验证和保护

新建本机独立PostgreSQL16集群与cmpp_qa_receipt_review数据库,监听127.0.0.1:16439,全部111迁移成功;实际API TypeScript构建通过。未启动Gateway、HTTP投递、API调度或真实短信发送。首次尝试复用旧隔离集群时发现默认端口/角色不同,未修改旧数据库,关闭本轮启动的旧集群,改建独立集群;两套本轮启动进程均已关闭,数据及失败日志保留。

隔离证据.local-data/receipt-review-20260918/repro-final.log确认3项缺陷及1项通过;online.json、repeated-routes-stderr.json、route-confirm.json为只读现场聚合。日志ANSI导致首次关联统计为0,去除ANSI后重新核验21组/至少5组,原失败与修正结果明确区分。未重跑与只读审查无关的全量前端/Go测试,不宣称修复完成。

后续整改(2026-09-18

用户后续授权修改并本地提交,三项按主设计第10.13节实施;上文保留原审查证据,不代表修复后实现。真实PG回归复现结果已改变:终态之后不重复选路,矛盾成功不改失败/退款/通知,跨尝试仅更新目标记录;详情、全部测试及未验证边界见testing-progress.md同日“长短信三项缺陷整改”。未推送、未部署,线上CPU改善尚未验证。