docs: message-lifecycle 审查修正——消解跨文档冲突、口径归口并登记 Q12

message-lifecycle.md:
- §5.3 日计划改为现行合并语义(缺席不删、缺失保留,引 flight-state §3.1),类型粒度统一 TYPE-STYP
- §5.1 明确两条扫描路径按 ID 区间取行、不以处理标记为谓词;水位定义与 Q2 绑定
- §4 两个缺口改与 design §10 同措辞;§5.2 的 R 构成挂 Q6/Q9,新增 Q12 积压跳过授权
- §6 方案 B 对账补“只补缺失行、标记单调不回退”;删除能力声明标注待 Q9 确认
- §7 改为“尚未实现”、统一 COUTMSGS_* 列名;§11 ID 不断链改为条件式不变量

user-stories.md:US-11 第 4 条限定为“本系统不写共享历史表/不清除外部信箱”;US-01 扫描谓词指向 lifecycle §5.1;US-01/09/11 前置补口径指针;新增 Q12;OPS-2 补积压与最老未处理信龄;Q10 列名统一。

architecture.md:共享 MySQL 红线限定主体;§6 中断恢复表改为指向 message-lifecycle §4;收报步骤补扫描谓词指针。

design.md:水位定义、回填死信处理、维护作业、归档、缺口恢复统一指向 message-lifecycle。

README.md:核对日期更新至 2026-09-10(代码基线 99a0f5f,文档基线 328bbf8);维护规则补规范类文档一档。
This commit is contained in:
windyboy
2026-09-10 09:00:53 +08:00
parent 328bbf88d2
commit ffd3abd655
5 changed files with 48 additions and 49 deletions
+29 -24
View File
@@ -23,7 +23,7 @@
|---|---|---|
| 落信:报文进入信箱 | `CMINMSGS` 中存在该行 | 上游 |
| 入队:本系统开始处理 | 自有 PG 建立 `PROC_STATE` 记录 | `InboxPoller` |
| 处理完成:业务有结论 | `PROC_STATE` 到达终态(SUCCEEDED / SKIPPED / DEAD | 主泵 |
| 处理完成:业务处理完成 | `PROC_STATE` 到达终态(SUCCEEDED / SKIPPED / DEAD | 主泵 |
| 已回填:信箱写入处理标记 | 信箱行持有处理标记 | 回填与补偿通道 |
| 下游确认:下游已接收 | `MSG_EVENT``SENT` | `delivery`Dispatcher / flushSchd |
@@ -43,13 +43,13 @@ MSG_EVENT PENDING ──→ SENT
- `PENDING``FAILED` 是处理中的状态,`FAILED` 继续退避重试并占用 FIFO 队头;`SUCCEEDED / SKIPPED / DEAD` 是终态,到达后队列方可推进。
- `DEAD``FAILED` 不是不可逆:经人工批准,指定错误类别的记录可重新置回 `PENDING` 处理(放行范围见 §9 / design.md §6.1)。重放处理的是当前状态,不恢复历史处理顺序。
- 航班变更、待发事件、处理终态与回填待预登记四类写入位于同一个 PG 事务,一起提交或一起回滚;信箱处理标记写在该事务之后,依赖 `BACKFILL_TODO` 补偿记录最终写入。
- 航班变更、待发事件、处理终态与回填待预登记四类写入位于同一个 PG 事务,一起提交或一起回滚;信箱处理标记写在该事务之后,依赖 `BACKFILL_TODO` 补偿记录最终写入。
## 3. 处理阶段
| 阶段 | 输入 | 输出 | 幂等依据 | 失败处理 |
|---|---|---|---|---|
| 收报 | `DATE_PROCESSED IS NULL` 的信箱行 | `PROC_STATE(PENDING)` | `MSG_ID` 主键 | 下轮扫描补入队 |
| 收报 | ID 区间内尚未入队的信箱行(谓词见 §5.1) | `PROC_STATE(PENDING)` | `MSG_ID` 主键 | 下轮扫描补入队 |
| 取队头 | 最小未完成 `MSG_ID` | 本批处理消息 | 表状态即队列 | 无队头则等待 |
| 解码与身份 | 信箱原文 | `DecodedMessage``IDENTITY_KEY` | 身份唯一约束 | 报文非法→`DEAD`;处理能力不足→`FAILED` 退避 |
| 事务处理 | 当前完整状态 + 报文载荷 | 航班变更、终态、事件、回填待办 | 消息 ID + 业务身份 | 事务整体回滚重试 |
@@ -72,12 +72,12 @@ MSG_EVENT PENDING ──→ SENT
| 标记写入中途 | 标记仍为空 | 重新写入;重复写入同一值无副作用 |
| 下游已接收、`SENT` 未置 | 事件仍 `PENDING` | 允许重发,下游按事件身份去重 |
两个缺口在闭环前不能宣称恢复完整:
两个缺口在闭环前不能宣称恢复完整(口径与 design.md §10「事务与外部副作用」一致)
1. 事务提交后、补偿开始前仍存在崩溃窗口
2. 补偿重试尚未证明"最终一定完成"
1. 事务提交后、回填动作开始前的崩溃窗口(提交后回填前崩溃)
2. 回填待办二次落账失败(待办落账再次失败)
两项登记于 design.md §10(事务与外部副作用)与 user-stories.md US-09。
两项登记于 design.md §10 与 user-stories.md US-09;本表的恢复判定以这两个窗口为前提
## 5. 消费水位、超期补写与历史积压
@@ -92,13 +92,15 @@ MSG_EVENT PENDING ──→ SENT
日常执行方式:快路径从 `ID > W` 起按升序有限批次扫描;另按周期对窗口内可能迟到或空洞的行做补偿扫描。需要区分:水位表示"读取进度",与"已处理标记"是两个事实,不能互相替代。
两条扫描路径都按 **ID 区间**取行,不以处理标记为扫描谓词;标记只用于回填与库方清除,不参与消息发现。否则已入队但尚未回填的行会永久占据批次,这正是 US-01 条目 3 与 Q2 要求排除的场景。
### 5.2 超期标记补写
两类信箱行无法通过正常回填获得处理标记:报文残缺且缺少元数据的死信,以及回填通道长期失败的行。处理规则为:
**已达 `PROC_STATE` 终态、且接收时间超期(`DATE_RECEIVED < NOW R`)仍无标记的信箱行,由回填通道补写一个库方认可的"已处理"类标记。**
- 期限 `R` 必须不小于(人工重放期限 + 人工处置期限)之和。提前补写会使仍可重放的消息失去原件资格。
- 期限 `R` 必须不小于(人工重放期限 + 人工处置期限)之和;两项期限的取值口径分别见 Q6 与 Q9,确认前不得下调 `R`。提前补写会使仍可重放的消息先被库方清除、失去原件资格。
- 中间态(`PENDING` / `FAILED`)不适用本规则:处理未完成时不打标,也不允许被清除。
- 补写值仅限于库方认可的 legacy 值集(Q7);死信、业务重复等内部原因记录在 `PROC_STATE` 与审计日志,不在信箱新增枚举。
- 补写只针对空标记;已有值不回撤、不覆盖,重复执行无副作用。
@@ -109,36 +111,36 @@ MSG_EVENT PENDING ──→ SENT
"历史积压"指信箱中成规模的未处理存量:上线前遗留、停机期间累积、或消量未完成的批次。处理方案分三步,并约束四条行为红线。
**第一步:摸底。** 处理动作开始前,先确定积压的范围与构成:ID 区间、条数、时间跨度、报文类型分布(SCHD / FLOP / FDEL / ADFT 等),并与库方确认其中哪些仍需业务处理、哪些按约定跳过(跳过基调只在该批确认时成立,值集见 Q7)。
**第一步:摸底。** 处理动作开始前,先确定积压的范围与构成:ID 区间、条数、时间跨度、报文类型分布(`SCHD-DNLD/RESP/ADFT``FLOP-*``FDEL` 等),并与库方确认其中哪些仍需业务处理、哪些按约定跳过(跳过基调只在该批确认时成立,授权与留痕见 Q12,标记值集见 Q7)。
**第二步:先入队,后处理。** 两阶段执行,禁止边收边处理一辆长队:
1. 消化阶段只做入队:`InboxPoller` 按升序有限批次将历史行全部建为 `PROC_STATE(PENDING)`,水位随之推到积压末端。此阶段只写自有 PG,不触碰信箱标记。
1. 消化阶段只做入队:`InboxPoller` 按升序有限批次将历史行全部建为 `PROC_STATE(PENDING)`,水位随之推到积压末端(连续推进以 §5.1 的 Q2 承诺为前提)。此阶段只写自有 PG,不触碰信箱标记。
2. 入队完成后交给主泵按最小未完成 ID 顺序消化。顺序与阈限与日常完全相同:不加速、不分流、不走旁路。
这样做的理由:入队是便宜操作(可大批量),处理昂贵操作(解码 + 事务),把两者分开后,入库阶段的中断恢复只涉及重扫(§4 第一行),不会把半处理的事务复杂化;同时水位可以尽早到达完整上界,快路径与补扫窗口立即生效。
这样做的理由:入队廉价、处理昂贵;分开后入队阶段的中断恢复只涉及重扫(§4 第一行),水位也能尽早到达完整上界,快路径与补扫窗口立即生效。
**第三步:等待过程中的四个边界。**
- 不插队:主泵按 FIFO 消化,积压期间到达的实时消息排在积压之后。本设计不允许并行队头,也不允许实时通道跳过积压。
- 不失控:队头滞留上限(head-deadline,当前 10 分钟)对积压同样生效;队头长期失败按既有规则转 `DEAD(EXHAUSTED)` 并告警,不会因积压而延长容忍。
- 报文类型的处理方式不变:旧的全量日计划(SCHD-DNLD)按最一份覆盖即可正确收敛,但仍逐条执行;动态增量(FLOP / FDEL / ADFT)持有时序语义,必须逐条。
- 不失控:队头滞留上限(head-deadline,当前 10 分钟)对积压同样生效;队头长期失败按既有规则转 `DEAD(EXHAUSTED)` 并告警,不会因积压而延长容忍。在 Q6 收敛前,该上限的实际判据仍是 `updatedAt` 与系统取时(见 design.md §10),本条款是目标口径。
- 报文类型的处理方式不变:旧的全量日计划(`SCHD-DNLD`)按最一份**合并**即可收敛(缺席不删、缺失字段保留,见 flight-state.md §3.1,但仍逐条执行;动态增量(`FLOP-*` / `FDEL` / `ADFT`)持有时序语义,必须逐条。
- 可放弃但必须留痕:摸底批中经库方确认"不再处理"的行,处置方式为——`PROC_STATE``SKIPPED` 并记录跳过原因,到达终态后走 §5.2 通道补写标记;本方案不支持任何"整段 DELETE"的快速通道。
**验收口径**:积压消化期间持续输出三项指标——剩余积压条数、最老未处理信龄、预计消化时长;期间不允许出现 FIFO 越序、身份去重失效或头行滞留超时未告警。红线依据:architecture.md §5(消息严格 FIFO)、单写者约束。
**验收口径**:积压消化期间持续输出三项指标——剩余积压条数、最老未处理信龄、预计消化时长;期间不允许出现 FIFO 越序、身份去重失效或头行滞留超时未告警。红线依据:architecture.md §5(消息严格 FIFO)、单写者约束。三项指标并入 user-stories.md OPS-2 的可观测性验收。
## 6. 信箱数据清除
表结构变更与物理清除由库方执行或书面授权执行;本系统对共享 MySQL 不建表、不改结构(红线见 architecture.md §6)。本系统在清除事务中的义务只有一项:为处理完成的行及时写入处理标记,使可清除范围存在明确边界。
表结构变更与物理清除由库方执行或书面授权执行;本系统对共享 MySQL 不建表、不改结构(红线见 architecture.md §6)。本系统不执行 DDL、不写共享历史表:`CMINMSGS_HST` 的历史归档写入由库方执行,属 Q9 授权范围。本系统在清除事务中的义务只有一项:为处理完成的行及时写入处理标记,使可清除范围存在明确边界。
具体方案由库方选择(Q9)。以下两种方案均基于 MySQL 自身能力:支持 `RANGE` 分区与 `TRUNCATE / DROP PARTITION`,不支持 `EXCHANGE PARTITION`
具体方案由库方选择(Q9)。以下两种方案均基于 MySQL 自身能力:支持 `RANGE` 分区与 `TRUNCATE / DROP PARTITION`,不支持 `EXCHANGE PARTITION`(能力与现场版本待库方书面确认,Q9
**方案 A:按日分区(首选)**,适用于库方可以为表增加分区的场合。
1. `CMINMSGS``DATE_RECEIVED` 建立日粒度 RANGE 分区;
2. 某分区到达保留期时,确认该分区全部行已持有处理标记(§5.2 保证该条件在有限时间内满足);
3. 将该分区复制入历史表:`INSERT INTO CMINMSGS_HST SELECT``NOT EXISTS` 判重);
4. `TRUNCATE / DROP PARTITION` 执行清除:DDL 级操作,无行锁竞争,页外大字段(`CMINMSGS_CLOB_MSG` 列)整块释放,不产生碎片与 binlog 压力
4. `TRUNCATE / DROP PARTITION` 执行清除:DDL 级操作,无行锁竞争,页外大字段(`CMINMSGS_CLOB_MSG` 列)整块释放碎片与 binlog 影响待现场验证
前提:库方确认现场 MySQL 版本支持分区 DDL,并已授权执行。
@@ -147,7 +149,7 @@ MSG_EVENT PENDING ──→ SENT
1. `CREATE TABLE CMINMSGS_NEW LIKE CMINMSGS`,并将 `AUTO_INCREMENT` 种子设为 `max(ID) + 1`
2. 将保留窗内行(`DATE_RECEIVED ≥ NOW R_keep`)复制至新表;
3. 以单语句原子 `RENAME TABLE` 完成换名;
4. 对账:换名与复制之间新写入及新标记的行,从旧表幂等补回;
4. 对账:换名与复制之间新写入及新标记的行,从旧表补回;只补新表缺失的行,已存在行按标记单调取并集,不回退、不覆盖(§11)
5. 旧表中早于保留窗的部分追加写入 `CMINMSGS_HST``NOT EXISTS` 判重);
6. `DROP TABLE` 旧表:秒级完成,碎片与大字段一并释放。
@@ -162,14 +164,14 @@ MSG_EVENT PENDING ──→ SENT
## 7. 出站信箱(COUTMSGS
本系统一侧的规则(实现中:出站适配与请求协调尚未交付,机制描述见 design.md §4.2):
本系统一侧的规则如下(出站适配与请求协调**尚未实现**;请求生命周期的机制目标见 design.md §4.2,本节不重复):
出站前在 `REQ_TRACK` 登记 `REGISTERED`;写入 `COUTMSGS` 并确认落信后置 `SENT` 并关联出站记录 ID。"落信"指写入信箱成功,不等于下游已读取或已发送,交付承诺仅到落信为止(US-08)。
本系统写入的行由谁消费、按什么顺序消费,**当前未知**。以下事项在 Q10 关闭前按未知处理,不得作为已具备能力描述:
- 消费方与消费顺序;
- `COUTMSGS_ACK_DATE_RECV / ACK_RESEND_TIMES / DATE_SENT / ERROR` 各列的语义与写入责任;
- `COUTMSGS_ACK_DATE_RECV``COUTMSGS_ACK_RESEND_TIMES``COUTMSGS_DATE_SENT``COUTMSGS_ERROR` 各列的语义与写入责任;
- 出站行的清除责任与保留期;
- 落信成功但本地未置 `SENT` 时的重复写入风险及下游去重契约。
@@ -185,19 +187,22 @@ MSG_EVENT PENDING ──→ SENT
- 可重放的错误类别为 `CODEC_ERROR / UNSUPPORTED / INFRA / EXHAUSTED` 四类(以 design.md §6.1 为准);重放处理当前状态,不恢复历史顺序。
- 重放的前提是信箱原文仍可读取:`R_keep` 必须覆盖重放期限(§6),属于硬约束。
- 处理时无法读取原文,一律按 `MALFORMED → DEAD` 处理。若原文缺失源于库方违反保留契约提前清除,按契约违例走运维追责通道;该消息的死信处置本身不变。
- 处理时无法读取原文,一律按 `MALFORMED → DEAD` 处理。若原文缺失源于库方违反保留契约提前清除,按契约违例走运维追责通道(契约条款见 Q7/Q9;该消息的死信处置本身不变。
## 10. 开放问题索引
| 编号 | 待确认事项 | 本文相关章节 |
|---|---|---|
| Q2 | 信箱 ID 单调承诺;最大提交时延;空洞与迟到处理 | §5.1 |
| Q2 | 信箱 ID 单调承诺;最大提交时延;空洞与迟到处理 | §5.1、§6 |
| Q6 | 重放 deadline 与人工处置期限的取值(决定 §5.2 的 `R` | §5.2、§9 |
| Q7 | 处理标记值集与写权限;原文保留期;处理时间语义 | §5.2、§6 |
| Q8 | 逐类覆盖清单(积压摸底的类型分布依据) | §5.3 |
| Q9(新增) | 清除执行方与 DDL 授权;方案 A / B 选型 | §6 |
| Q10(新增) | 出站消费方、ACK 列语义、出站清理与去重契约 | §7 |
| Q11(新增) | 上游 `SEQN` 重置规则与业务身份的日期边界 | design.md §2.2 |
| Q12(新增) | 历史积压批次“不再处理”的确认主体、审批留痕与跳过值集 | §5.3 |
Q9Q11 的完整登记见 [user-stories.md](user-stories.md) §6 的 Q 表。
Q6Q12 的完整登记见 [user-stories.md](user-stories.md) §6 的 Q 表。
## 11. 不变量
@@ -206,5 +211,5 @@ Q9Q11 的完整登记见 [user-stories.md](user-stories.md) §6 的 Q 表。
- 处理标记单调:任何路径只将空标写为已处理,不回撤、不覆盖。
- 水位不越过未入队的 ID;遇空洞即停。
- 执行清除前,边界内全部行已持有处理标记;物理删除仅发生在归档成功之后(追加写入与分区留档均构成归档成功)。
- 信箱 ID 全局单调、不断链,在任何清除方案下成立
- 信箱 ID 全局单调、不断链:以 Q2 的 ID 单调承诺与 §6 各方案前提成立为条件(方案 B 依赖 `AUTO_INCREMENT` 种子)
- 对外投递按至少一次设计;端到端恰好一次不在交付范围内。