docs: 精简重复论证与过时流程表述,口径与实现双向对齐

This commit is contained in:
windyboy
2026-09-11 14:26:44 +08:00
parent a84751bf03
commit 632a5fe422
3 changed files with 57 additions and 85 deletions
+30 -43
View File
@@ -24,7 +24,7 @@
|---|---|---|---|---|---|
| `W`(水位) | 信箱 ID 的连续上界:`(min, W]` 已全部读入自有 PG | `INBOX_CURSOR.COMMITTED_UP_TO` | — | 初值 0 | 只随新 ID 成功入队推进(永久空洞放行是唯一例外);[message-lifecycle.md](message-lifecycle.md) §5.1 |
| `holeSince` | `W+1` 处空洞最早被观测到的时刻;无空洞时为 NULL | `INBOX_CURSOR.HOLE_SINCE` | — | NULL | 跨重启保留;旧空洞补齐后新空洞重新计时;[message-lifecycle.md](message-lifecycle.md) §5.1 |
| `R`(超期补写期限) | 回填长期失败时的强制补写上限(按接收时间计) | 判据用 `PROC_STATE.RECEIVED_AT` | `msgx.pipeline.overdue-backfill` | 30 天 | 仅 `R ≤ R_keep`**不保护重放窗口**回填由扫描驱动、不等 `R`,打标时刻与 `R` 解耦);[message-lifecycle.md](message-lifecycle.md) §5.2 |
| `R`(超期补写期限) | 回填长期失败时的强制补写上限(按接收时间计) | 判据用 `PROC_STATE.RECEIVED_AT` | `msgx.pipeline.overdue-backfill` | 30 天 | 仅 `R ≤ R_keep`**不保护重放窗口**完整论证唯一见 [message-lifecycle.md](message-lifecycle.md) §5.2 |
| `R_keep`(清除保留期) | 信箱行可被物理清除前的最短保留时间 | 库方侧 | 库方策略 | 待定(Q9) | `R_keep ≥ max(人工重放期限 + 人工处置期限, 审计期限, 回填重试上限)`**仅在"标记 + 保留期"清除语义下成立**;[message-lifecycle.md](message-lifecycle.md) §6 |
| `head-deadline` | 队头滞留上限,超过即毒丸升级 | 计时起点 `PROC_STATE.PROCESSING_STARTED_AT` | `msgx.pipeline.head-deadline` | 10 分钟 | 为空时以 `UPDATED_AT` 兜底;§3.2 |
| 队头 | 最小的未完成消息(`PENDING``FAILED` 都占位) | `PROC_STATE``ORDER BY MSG_ID`) | — | — | 单线程串行处理,后续消息不得越过;§3.2 |
@@ -40,7 +40,7 @@
| 记录 | 用途 | 关键约束 |
|---|---|---|
| `PROC_STATE` | 入站消息的处理状态、身份、重试次数、错误原因与回填事实 | `MSG_ID = CMINMSGS_ID` 主键防止重复入队;`IDENTITY_KEY` 唯一约束防止业务重复;按最小未完成消息 ID 取队头;`PROCESSING_STARTED_AT` HOL deadline 的稳定起点(为空时以 `UPDATED_AT` 兜底,见 §3.2);`BACKFILL_NEXT_AT` 非空 = 还欠一次回填(回填意图),`BACKFILL_AT` 非空 = 标记已确认,`BACKFILL_ABANDONED_AT/REASON` 非空 = 已停止自动重试(**不等于**标记已确认);`RECEIVED_AT` 复制自信箱接收时间,**可能为 NULL**,为 NULL 时 [message-lifecycle.md](message-lifecycle.md) §5.2 的超期兜底不生效。 |
| `PROC_STATE` | 入站消息的处理状态、身份、重试次数、错误原因与回填事实 | `MSG_ID = CMINMSGS_ID` 主键防止重复入队;`IDENTITY_KEY` 唯一约束防止业务重复;按最小未完成消息 ID 取队头;`PROCESSING_STARTED_AT` HOL 计时起点(口径见 §3.2);`BACKFILL_NEXT_AT` 非空 = 还欠一次回填(回填意图),`BACKFILL_AT` 非空 = 标记已确认,`BACKFILL_ABANDONED_AT/REASON` 非空 = 已停止自动重试(**不等于**标记已确认);`RECEIVED_AT` 复制自信箱接收时间,**可能为 NULL**,为 NULL 时 [message-lifecycle.md](message-lifecycle.md) §5.2 的超期兜底不生效。 |
| `MSG_EVENT` | 等待投递的事件(outbox) | `EVENT_ID` 决定投递顺序;`TARGET` 区分 `KAFKA:msg` / `KAFKA:schd``PARTITION_KEY` 恒为 `FLID``EVENT_TYPE` 区分 UPSERT 与 TOMBSTONE。 |
| `REQ_TRACK` | 上游请求及应答关联 | 保存请求类型、覆盖运营日、发送方、出站信箱 ID 与发送/完成时间;同类只允许一个开放请求。登记、超时与应答匹配尚未实现(§10)。 |
| `REF_MASTER` | 静态参考数据(目标表) | `(RTYPE, RKEY)` 唯一;尚未建表,客户端与刷新流程见 user-stories.md US-13/US-14US-14 两类映射的存储落点未定)。 |
@@ -62,7 +62,7 @@ SNDR | TYPE | STYP | SEQN
接收时只按信箱 ID 去重;解码后才首次绑定业务身份。重试保留原有绑定,不能把自己判为重复消息。身份被另一条记录占用时,当前消息转为 `SKIPPED`,记录 `duplicate-of:<id>`。是否加入日期边界取决于上游序号重置周期,默认关闭(`SEQN` 的取值范围与回绕已由 `SIS_AODB_RMS-V0.1.md` §2.8.1 定义,重置周期见 Q11);上线后不能随意更换身份算法。身份绑定是**独立的幂等单语句**(`WHERE IDENTITY_KEY IS NULL`),不参与业务事务,见 §3.3 表 1。
分派与落库由 `MessageProcessor` 统一协调:按 `MsgKind` 把已绑定身份的队头消息交给对应事务协调器(DNLD/RESP → `ScheduleProcessor`ADFT → `AdftProcessor`FLOP → `FlopProcessor`FDEL → `FdelProcessor`,其余 → `FAILED(UNSUPPORTED)`)。这些 Processor 在 `PIPELINE_LOCK` 事务内读取当前完整态,调用纯领域决策逻辑得到下一完整态与待发事件,再统一落库并登记回填意图;它们不直接触碰 Kafka。处理终态与业务变更在同一事务边界提交,**该结论只对处理器产出的「业务型终态」成立**`MALFORMED / PROTOCOL / SKIPPED(重复) / EXHAUSTED` 这类非业务型终态不涉及航班表,只需一条 `PROC_STATE` UPDATE(终态与回填意图同语句写入),不取 `PIPELINE_LOCK`。完整的事务边界见 §3.3 表 1。
分派与落库由 `MessageProcessor` 统一协调:按 `MsgKind` 把已绑定身份的队头消息交给对应事务协调器(DNLD/RESP → `ScheduleProcessor`ADFT → `AdftProcessor`FLOP → `FlopProcessor`FDEL → `FdelProcessor`,其余 → `FAILED(UNSUPPORTED)`)。这些 Processor 在 `PIPELINE_LOCK` 事务内读取当前完整态,调用纯领域决策逻辑得到下一完整态与待发事件,再统一落库并登记回填意图;它们不直接触碰 Kafka。处理终态与业务变更在同一事务边界提交,**该结论只对处理器产出的「业务型终态」成立**——非业务型终态不涉及航班表,不取 `PIPELINE_LOCK`。完整边界唯一见 §3.3 表 1。
### 2.3 状态与错误分类
@@ -96,56 +96,45 @@ SNDR | TYPE | STYP | SEQN
`InboxPoller` 默认每秒按 ID 升序、有限批次(`claim-batch`,默认 50)读取水位之后的信箱记录(`ID > W`,**不以处理标记为谓词**),在自有 PG 建立 `PENDING` 并把水位推进到连续上界;入队与水位推进在同一 PG 事务内提交,重复扫描幂等、中断后重扫补建。收报层不解析业务载荷,也不回填已处理标记。
**收报流程**(每轮 `pollOnce`;完整判据、代价与前提见 [message-lifecycle.md](message-lifecycle.md) §5.1,本节不重复):
1. 读游标 `(W, holeSince)`;信箱不可读时记日志、等下一轮,**不动水位**——这属于基础设施失败,不能当成「没有新消息」。
2.`ID > W` 的升序前 `claim-batch` 行。
3.`W+1` 起逐 1 数求连续上界;出现缺号时按「遇空洞即停 / 超期放行」处理,本批缺号之后的行本轮一律不入队(判据见 §5.1)。
4. 在同一个 PG 事务内:对水位以内的每一行 `insertIfAbsent(MSG_ID, RECEIVED_AT)`,并写回 `(W, holeSince)`;主键冲突表示已入队,不计入也不报错。
5. 提交。本批中因空洞或批次上限未入队的行留待下一轮——**每轮最多解决一个空洞**。
水位不是已处理标记,其有效性以 Q2 的 ID 单调承诺为前提;空洞老化阈值取 `msgx.pipeline.max-commit-delay`
**单实例前提**:信箱读取不加锁,水位是单行覆盖写。本设计只在单活动实例下成立([architecture.md](architecture.md) §5、D2);多实例并发收报会让水位互相覆盖,必须先有实例级排他。
收报流程、空洞判定(遇空洞即停 / 超期放行)及其代价与前提(Q2 承诺、`max-commit-delay` 老化阈值、单实例排他)**唯一定义于** [message-lifecycle.md](message-lifecycle.md) §5.1,本节不重复。水位不是已处理标记。
兼容 HTTP 入口执行“写入共享信箱 → PG 入队”。两步不在同一事务中:信箱成功而 PG 失败时,原文不能丢失,由轮询补建;客户端失败重试可能再次写信箱,业务身份去重仍然必需。
兼容入口只写 `PROC_STATE`**不参与水位**,因此它登记的行会**超出水位**主泵在水位追平前不领取(§3.2 步骤 2),顺序因此不受影响——代价是这类消息要等收报把 `W` 推到它的 ID 之后才开始处理(最长约一个空洞老化窗口)。**运维含义**:只使用兼容入口而不运行收报轮询时,这些行不会被处理,必须让 `InboxPoller` 运行(`msgx.pipeline.autostart=true` 或显式触发)。详见 [message-lifecycle.md](message-lifecycle.md) §5.1/§12
兼容入口只写 `PROC_STATE`、不参与水位登记的行因此超出水位主泵在水位追平前不领取(§3.2 步骤 2);完整语义与运维含义唯一见 [message-lifecycle.md](message-lifecycle.md) §5.1。
### 3.2 主泵调度
每次 `Pump.tick` 只围绕最小未完成消息(`PENDING``FAILED` 都占队头):
1. 无队头:按轮询间隔休眠。
2. 队头超出水位(`msgId > W`):**不领取**,休眠到下一轮。这类行只可能来自兼容入口的直接登记;允许领取会让它越过尚未入队的较小 IDG2。会打印一条**限流 WARN**(仅在水位值变化时打一次)。
3. 队头为 `FAILED` 且已毒丸(`attempts ≥ max-attempts`,或 `now 计时起点 ≥ head-deadline`):转 `DEAD(EXHAUSTED)`并立即尝试一次回填。**该分支只写 `PROC_STATE`,不取 `PIPELINE_LOCK`、不在处理器事务内,也不在 `MessageLifecycleGate` 内**(见 §3.3 表 1 与 §6.1)。
2. 队头超出水位(`msgId > W`):**不领取**,休眠到下一轮。这类行只可能来自兼容入口的直接登记;允许领取会让它越过尚未入队的较小 ID。会打印一条**限流 WARN**(仅在水位值变化时打一次)。
3. 队头为 `FAILED` 且已毒丸(`attempts ≥ max-attempts`,或 `now 计时起点 ≥ head-deadline`):转 `DEAD(EXHAUSTED)`终态与回填意图同一条 UPDATE 落库,**不做跨库写**。**该分支只写 `PROC_STATE`,不取 `PIPELINE_LOCK`、不在处理器事务内,也不在 `MessageLifecycleGate` 内**(见 §3.3 表 1 与 §6.1)。
4. 队头为 `FAILED` 且未到 `next_attempt_at`:休眠到可重试时刻,不处理后续消息。
5. 其余(新消息或退避到期的重试):记录 `PROCESSING_STARTED_AT`(仅首次)后调用 `MessageProcessor.processOne`,失败迁移在该边界内完成。
维护作业由独立 job 线程调度(§6.1),不占用消息循环;作业有界且不使到期消息无限饥饿。**所有取时统一经注入 `Clock`**(收报空洞老化、主泵调度、处理器落库时间、回填重试、作业切日),不使用系统时钟;HOL deadline 以首次处理时写入的 `PROCESSING_STARTED_AT` 为稳定起点,**该列为空时(V3 迁移之前的存量行)以 `UPDATED_AT` 兜底**;人工重放会清空 `PROCESSING_STARTED_AT`,由新一轮首次处理重新记录。
维护作业由独立 job 线程调度、不占用消息循环(§6.1)。**所有取时统一经注入 `Clock`**(收报空洞老化、主泵调度、处理器落库时间、回填重试、作业切日),不使用系统时钟;HOL deadline 以首次处理时写入的 `PROCESSING_STARTED_AT` 为稳定起点,**该列为空时(V3 迁移之前的存量行)以 `UPDATED_AT` 兜底**;人工重放会清空 `PROCESSING_STARTED_AT`,由新一轮首次处理重新记录。
### 3.3 单条处理
```text
processOne(head)
1. 入口守卫:head 已是 FAILED 且 attempts 达上限 → DEAD(EXHAUSTED),结束
2. 读原文:缺失 → DEAD(MALFORMED, raw-missing);读取异常 → FAILED(INFRA)
3. 解码:
1. 读原文:缺失 → DEAD(MALFORMED, raw-missing);读取异常 → FAILED(INFRA)
2. 解码:
报文非法(MALFORMED) → DEAD(MALFORMED),不重试
可修复解码错(CODEC_ERROR) → FAILED(CODEC_ERROR) 退避
4. 身份绑定(仅当 IDENTITY_KEY 为空):
3. 身份绑定(仅当 IDENTITY_KEY 为空):
已被本消息占用 → 继续
已被别的消息占用 → SKIPPED(duplicate-of:<id>),结束
空闲 → 写入 IDENTITY_KEY(独立单语句,不参与业务事务)
5. 按 MsgKind 分派:
4. 按 MsgKind 分派:
SCHD-DNLD / SCHD-RESP → ScheduleProcessor(快照事务)
SCHD-ADFT → AdftProcessor(单航班事务)
FLOP / FDEL → Flop / FdelProcessor(单航班事务)
Unsupported → FAILED(UNSUPPORTED) 退避
载荷缺失 → DEAD(MALFORMED)
整包协议拒绝 → DEAD(PROTOCOL),不落半包
6. 业务型成功:处理器在自己的事务内写航班变更 + 待发事件 + SUCCEEDED + 回填意图
7. 返回终态标记:只有终态才调用 backfill.attempt(msgId) 试写一次信箱标记
5. 业务型成功:处理器在自己的事务内写航班变更 + 待发事件 + SUCCEEDED + 回填意图
6. 结束:主泵不做回填;回填意图已随终态落库,由扫描补写信箱标记
```
**表 1 事务边界**(哪些动作在一个事务里、哪些不是):
@@ -156,7 +145,7 @@ processOne(head)
| 身份首次绑定 | 否 | 否 | 否 | 单语句 + `uk_proc_identity` |
| 业务型终态(处理器产出 `SUCCEEDED`) | 是 | 是 | 是 | 同库事务:航班变更 + 事件 + 终态 + 回填意图 |
| 非业务型终态(`MALFORMED` / `PROTOCOL` / `SKIPPED` / `EXHAUSTED`) | 否 | 否 | 否 | 单语句(终态与回填意图同一条 UPDATE) |
| 毒丸升级 `DEAD(EXHAUSTED)`(§3.2 步骤 2) | 否 | 否 | 否 | 单语句 |
| 毒丸升级 `DEAD(EXHAUSTED)`(§3.2 步骤 3) | 否 | 否 | 否 | 单语句 |
| 回填(信箱标记 + `BACKFILL_AT`) | 否 | 否 | 否 | 跨库两次单写;幂等可重跑 |
| 人工重放(批量改回 `PENDING`) | 否 | 否 | 否 | 单语句批量;`MessageLifecycleGate` 与回填互斥 |
@@ -164,8 +153,7 @@ processOne(head)
- 原文缺失归为 `MALFORMED`;读取异常不能伪装成“缺失”,应进入基础设施重试。
- 忽略规则(`LDM / REGN / RSTA / EROR``SKIPPED`)尚未实现(§10);不能因类型未覆盖就把合法忽略报文当非法报文处理。
- **主泵不回填**:终态与回填意图由同一条 UPDATE 落库,回填**一律由扫描驱动**(跨库写不能占用 FIFO 关键路径)。失败按退避重试,抵达超期期限 R 时强制补写,确认行不存在或达尝试上限则停止自动重试([message-lifecycle.md](message-lifecycle.md) §5.2US-09/Q7)。回填只需消息 ID因此缺 META 或解码失败的死信同样可补写`PENDING / FAILED` 禁止回填;影子环境禁写。
- 回填有三种结果:写入成功;**此前已被标记(视为成功,不覆盖已有值)**;信箱行不存在(视为失败,当前无终态,见 §10)。
- **主泵不回填**:终态与回填意图由同一条 UPDATE 落库,回填**一律由扫描驱动**(跨库写不能占用 FIFO 关键路径);四种结果、退避、超期强制补写与放弃恢复、调度周期口径全在 [message-lifecycle.md](message-lifecycle.md) §5.2US-09/Q7)。回填只需消息 ID,缺 META 或解码失败的死信同样可补写;【缺口】影子环境禁写(§10
## 4. 日计划快照与请求匹配
@@ -176,7 +164,7 @@ processOne(head)
1. **重放判定**`PROC_STATE` 已存在成功终态 → 幂等成功,仅追加留痕,不重复写入。
2. **整包校验**:声明记录数、航班标识与运营日推导等校验失败 → 整包 `DEAD(PROTOCOL)`,不写半包,既有状态保持不变。
3. **事务写入**:锁内按 `FLID` 点查归属日,发现同一航班跨运营日即整包回滚并 `DEAD(PROTOCOL)`;通过后合并写主表与资源明细。报文未携带的航班不因本次日计划报文被删除。
4. **提交结果**:同一事务保存 `KAFKA:schd` / `KAFKA:msg` 事件、置消息 `SUCCEEDED` 并预登记回填意图;事务提交后执行信箱回填,留痕在事务外追加。
4. **提交结果**:同一事务保存 `KAFKA:schd` / `KAFKA:msg` 事件、置消息 `SUCCEEDED` 并预登记回填意图;提交后信箱回填由扫描承接(§3.3,留痕在事务外追加。
单事务保证未提交变更整体回滚。消息重放由 `PROC_STATE` 的消息 ID 与业务身份控制;版本号不能单独证明消息身份。
@@ -221,19 +209,19 @@ REGISTERED → SENT → WAITING → DONE
### 6.1 失败、重试与重放
`ProcFailure``FailureScheduler` 统一处理侧失败落账,投递侧(`Dispatcher`)按同一套次数与退避规则迁移事件。默认最多 5 次(attempts ≥ 5 判耗尽);退避表配置为 1、2、4、8、16 秒、单档封顶 60 秒,但**耗尽判定与退避取值同源**(`attempts ≥ max-attempts` 即转 `DEAD`,不再计算下次重试),因此默认配置下实际只用 1、2、4、8 四档:**16 秒档与 60 秒封顶不会被触发**(要么调高 `max-attempts`,要么接受"5 次尝试 = 4 档退避"。时间经可注入 `Clock` 判定。
`ProcFailure``FailureScheduler` 统一处理侧失败落账,投递侧(`Dispatcher`)按同一套次数与退避规则迁移事件。默认最多 5 次(attempts ≥ 5 `DEAD`,不再计算下次重试);退避表默认 1、2、4、8 秒,**启动自检强制档位数 = max-attempts 1**"表里有档但永不触发"的配置不可能出现;单档封顶 `backoff-cap-ms`(默认 60 秒)仅对超过封顶的档位生效。时间经可注入 `Clock` 判定。
失败必须在持有具体消息、事件或批次的位置记录,外层循环只做兜底日志和等待,不重复增加次数。线程中断应恢复中断标记并向上传递;不把 JVM `Error` 当普通业务失败捕获。
`ReplayService` 只允许 `CODEC_ERROR / UNSUPPORTED / INFRA / EXHAUSTED``FAILED / DEAD` 回到 `PENDING`:重置 `ATTEMPTS=0``NEXT_ATTEMPT_AT=NULL``PROCESSING_STARTED_AT=NULL`**不重置 `IDENTITY_KEY`**(保留身份,避免重放时把自己判成重复消息)。它按错误类**全局批量**重放,尚无按记录预检、操作审计与管理入口(US-10)。重放与回填通过 `MessageLifecycleGate` 在同一实例内互斥,避免「旧回填给已重新入队的消息写标记」;【缺口】**毒丸升级路径不在该 gate 内**(§3.2 步骤 2),该竞态窗口登记于 §10。旧消息进入终态后后续消息可能已执行,**重新入队不等于恢复历史顺序**;人工重放前必须评估状态覆盖和版本保护,不能直接批量重放到生产。
`ReplayService` 只允许 `CODEC_ERROR / UNSUPPORTED / INFRA / EXHAUSTED``FAILED / DEAD` 回到 `PENDING`:重置 `ATTEMPTS=0``NEXT_ATTEMPT_AT=NULL``PROCESSING_STARTED_AT=NULL`**不重置 `IDENTITY_KEY`**(保留身份,避免重放时把自己判成重复消息)。它按错误类**全局批量**重放,尚无按记录预检、操作审计与管理入口(US-10)。重放与回填通过 `MessageLifecycleGate` 在同一实例内互斥,避免「旧回填给已重新入队的消息写标记」;【缺口】**毒丸升级路径不在该 gate 内**(§3.2 步骤 3),该竞态窗口登记于 §10。旧消息进入终态后后续消息可能已执行,**重新入队不等于恢复历史顺序**;人工重放前必须评估状态覆盖和版本保护,不能直接批量重放到生产。
**维护作业**`JobRunner` 用独立 daemon 线程每 30 秒触发 `BackfillService.sweep`**调度周期,不是回填完成时限**:批次积压、单行调用超时与历史作业耗时都会延长实际延迟;回填扫描自身的退避为 30 秒起步、封顶 15 分钟),每天机场时区 03:30 后触发一次 `HistorySweepJob`(§6.2)。回填意图在写终态的同一条语句里登记在 `PROC_STATE``BACKFILL_NEXT_AT/ATTEMPTS/ERROR`,不再有独立待办表。扫描谓词见 [message-lifecycle.md](message-lifecycle.md) §5.2;超过超期期限 `R` 后超期分支恒成立并覆盖退避,但**永久失败不会无限重试**:确认行不存在立即放弃、暂时性故障到 `backfill-max-attempts` 后停止自动重试(两类都告警且可人工恢复)。扫描按 `BACKFILL_ATTEMPTS, MSG_ID` **公平轮转**并排除已放弃行,最旧的一批永久失败行不会再占满批次饿死后续记录。作业不再经 `PUMP_JOB` 队列插队,不参与消息 FIFO,也不使到期消息饥饿。作业与回填通道的生命周期口径见 [message-lifecycle.md](message-lifecycle.md) §3/§4
**维护作业**`JobRunner` 用独立 daemon 线程每 30 秒触发 `BackfillService.sweep`(调度周期;扫描谓词、超期、放弃与公平轮转口径唯一见 [message-lifecycle.md](message-lifecycle.md) §5.2),每天机场时区 03:30 后触发一次 `HistorySweepJob`(§6.2)。回填意图在写终态的同一条语句里登记在 `PROC_STATE``BACKFILL_NEXT_AT/ATTEMPTS/ERROR`。作业不参与消息 FIFO,也不使到期消息饥饿
### 6.2 历史清理与归档
**航班历史清理**`HistorySweepJob`,每天 03:30 触发):按 `HistoryProps` 的保留期与终态/静默判据选出候选(含 `DELETED`),先写历史存储,成功后物理删除主行与明细;历史存储未接通或 `msgx.history.history-store-enabled=false` 时删除 0 条。未经 FDEL、由生命周期直接清除的航班,清除前补发一次删除事件。语义与红线见 flight-state.md §6,不在此重复。
**留痕清理**`SCHD_SNAP_LOG` 保留 90 天,在历史清理窗口内按 `(SCOPE_END, RECV_AT)` 删除。
**留痕清理**`SCHD_SNAP_LOG` 保留 90 天,在历史清理窗口内按 `(SCOPE_END, RECV_AT)` 删除;该清理不依赖历史存储开关,随作业每日执行
**处理终态归档**`PROC_STATE_HST` 仍是目标表(user-stories.md US-11),尚未建表;不得归档 `PENDING / FAILED`,也不能因移走身份记录而失去业务去重能力。ES 历史投影(阶段 B)不启用。自有记录归档与信箱原文保留的关系见 [message-lifecycle.md](message-lifecycle.md) §8/§9。
@@ -247,18 +235,18 @@ REGISTERED → SENT → WAITING → DONE
- `msgx.pipeline.autostart``msgx.stubs`:分别控制管道启动与内存适配器;生产禁止 stub,默认不自动启动。
- `msgx.pipeline.poll-interval / claim-batch / max-attempts / backoff-ms / backoff-cap-ms / head-deadline`:控制轮询节奏、批次、重试上限、退避与队头滞留;这些参数不能改变 FIFO。
- `msgx.pipeline.max-commit-delay / overdue-backfill / backfill-batch / backfill-max-attempts`:空洞老化阈值(Q2 的「ID 分配 → 事务可见时延上界」,同时决定目标补偿扫描的窗口宽度)、超期补写期限 R(Q6)、回填扫描批量与回填自动重试上限(达上限停止自动重试并可人工恢复);R 与老化阈值都不能为提速而下调。默认 5 分钟**只是缺少依据的占位假定值**——库方尚未给出该可见性时延上界,且它**不能由 SIS 报文的 `Expiry`480 分钟量级)推导**[message-lifecycle.md](message-lifecycle.md) §5.1),Q2 确认前属于上线门槛。`overdue-backfill`R)的完整约束见 [message-lifecycle.md](message-lifecycle.md) §5.2
- `msgx.pipeline.delivery-batch / delivery-drain-rounds`:普通事件(`KAFKA:msg`批量投递的批大小与每轮最多连取批数;把投递从"每条一次 DB 往返 + 一轮一次 sleep"提升到由下游决定,同时保留"队头失败即停止本轮"的目标内保序。
- `msgx.pipeline.max-commit-delay / overdue-backfill / backfill-batch / backfill-max-attempts`:空洞老化阈值(Q2 的「ID 分配 → 事务可见时延上界」;默认值依据缺口唯一见 [message-lifecycle.md](message-lifecycle.md) §12 G7)、超期补写期限 R(Q6;完整约束唯一见 [message-lifecycle.md](message-lifecycle.md) §5.2)、回填扫描批量与回填自动重试上限(达上限停止自动重试并可人工恢复);R 与老化阈值都不能为提速而下调
- `msgx.pipeline.delivery-batch / delivery-drain-rounds`:普通事件(`KAFKA:msg`单目标每轮领取条数上限,与每轮最多连取批数(连取后让出一次循环跑 `schd` flush,防长积压饿死状态通知);保留"队头失败即停止本轮"的目标内保序。
- `msgx.operation-day.zone / cutoff-hour`:运营日时区与切日边界,决定 `OPERATION_DAY` 推导(flight-state.md §2.1)。
- `mailbox.processed-value`:写回共享信箱的处理标记值,仅限库方认可的 legacy 值集(Q7)。
- `msgx.schd.flush-period / flush-limit`:控制状态通知的聚合延迟与批量大小。
- `msgx.identity.include-day-boundary`:影响去重语义,不能作为普通调优项切换;序号重置周期见 Q11。
- `mailbox.shared-mysql.enabled``msgx.history.history-store-enabled`:分别门控真实信箱与历史存储接线,默认关闭。
- `msgx.health.backlog-cache-ttl-ms`:积压快照缓存窗口(默认 30 秒,`/health``/metrics` 共用);设为 0 仅用于测试/排障,不作为实时性的替代。
- `msgx.pipeline.cutover-watermark`:**一次性、显式**的切流播种(默认不配置 = 不播种)取值 `min`(读当前全部现存行,`W=MIN(ID)1`)、`zero`(从 0 按空洞规则扫描)、`max`(跳过当前可见存量,`W=MAX(ID)`)或具体 ID。代码**不做默认选择**、也不会自动退化成 `max`;升级实例(已有水位或已有处理记录)**拒绝重新播种**,重新切流必须是显式操作;播种事实记在 `INBOX_CURSOR.SEEDED_AT`(与水位在同一条语句落库),而该列为 NULL **不等于**从未消费。非法取值由启动自检挡下。
- `msgx.pipeline.late-detect-period / late-detect-batch`**只读**迟到检测ACM2-41 阶段 0的周期与单轮复查量;周期设 0 即关闭。检测只计数与告警,不补入队、不改变处理语义。
- `msgx.pipeline.cutover-watermark`:**一次性、显式**的切流播种(默认不配置 = 不播种)取值 `min` / `zero` / `max` 或具体 ID;播种的动机、升级实例拒绝重播与 `SEEDED_AT` 语义唯一见 [message-lifecycle.md](message-lifecycle.md) §5.1。非法取值由启动自检挡下。
- `msgx.pipeline.late-detect-period / late-detect-batch`:**只读**迟到检测的周期与单轮复查量;周期设 0 即关闭。检测只计数与告警,不补入队、不改变处理语义。
日志关联消息 ID、事件 ID 和批次;失败记录错误分类、次数、下次执行时间。健康检查反映依赖实际可用性;队头滞留、积压、死信和补偿失败需要指标及告警。指标经 Micrometer 暴露(`PipelineMetrics`,启动时急切注册):`msgx.pipeline.backlog.unfinished``msgx.pipeline.backlog.oldest_unprocessed_seconds``msgx.pipeline.backfill.unmarked_terminal``msgx.pipeline.backfill.abandoned``msgx.pipeline.backfill.oldest_unmarked_seconds``msgx.pipeline.watermark.lag``msgx.pipeline.hole.aged_out.total``msgx.pipeline.late_arrival.detected.total`(迟到检测命中数——**> 0 表示上游提交确实晚于水位推进,需要与库方对契约**)。取数统一走 `BacklogSnapshotProvider``msgx.health.backlog-cache-ttl-ms`,默认 30 秒),`/health``/metrics` 共用同一份快照——`backlog()``PROC_STATE` 的全表聚合,不能被高频抓取打穿;无法取数时上报 `NaN`,不伪造 0。日志出口故障不得阻塞业务线程。
日志关联消息 ID、事件 ID 和批次;失败记录错误分类、次数、下次执行时间。健康检查反映依赖实际可用性;队头滞留、积压、死信和补偿失败需要指标及告警。指标经 Micrometer 暴露(`PipelineMetrics`,启动时急切注册):`msgx.pipeline.backlog.unfinished``msgx.pipeline.backlog.oldest_unprocessed_seconds``msgx.pipeline.backfill.unmarked_terminal``msgx.pipeline.backfill.abandoned``msgx.pipeline.backfill.oldest_unmarked_seconds``msgx.pipeline.watermark.lag``msgx.pipeline.hole.aged_out.total``msgx.pipeline.late_arrival.detected.total`(迟到检测命中数——**> 0 表示上游提交确实晚于水位推进,需要与库方对契约**)。取数统一走 `BacklogSnapshotProvider``msgx.health.backlog-cache-ttl-ms`,默认 30 秒),`/health``/metrics` 共用同一份快照——`backlog()``PROC_STATE` 的全表聚合,不能被高频抓取打穿;无法取数时上报 `NaN`无可比记录的年龄/滞后类仪表上报 `-1`,两者都不伪造 0。日志出口故障不得阻塞业务线程。
## 8. 验证要求
@@ -305,11 +293,10 @@ REGISTERED → SENT → WAITING → DONE
以下缺口直接影响上述设计是否成立,不能以类或接口已存在作为完成依据。收报与处理链路的逐条缺口另见 [message-lifecycle.md](message-lifecycle.md) §12。
- **事务与外部副作用**业务型终态(处理器产出的 `SUCCEEDED`)与航班变更、待发事件、回填意图在同一 PG 事务提交;非业务型终态(`MALFORMED / PROTOCOL / SKIPPED / EXHAUSTED`)与毒丸升级只写 `PROC_STATE`,终态与回填意图同一条 UPDATE,不涉及跨表一致性(§3.3 表 1)。回填意图落在 `PROC_STATE``BACKFILL_AT/NEXT_AT/ATTEMPTS/ERROR`),`BACKFILL_TODO` 已随 V2 迁移下线,[message-lifecycle.md](message-lifecycle.md) §4 的两个崩溃窗口(提交后回填前崩溃、回填意图二次落账失败)不再存在。回填本身仍是跨库单写,失败按退避重试并由超期期限 R 兜底;R 的取值待 Q6 确认。
- **收报与调度**:水位 W 与空洞计时 `HOLE_SINCE` 落库并与入队同事务推进,遇空洞即停、空洞超过 `max-commit-delay` 判定为永久(Q2 未书面确认前该阈值只是**缺少依据的占位假定值**——库方尚未给出「ID 分配 → 事务可见」的时延上界,且不能由 SIS 报文 `Expiry` 推导);旧空洞补齐后出现的新空洞会重置老化起点。**较小 ID 迟提交目前没有任何发现机制**:[message-lifecycle.md](message-lifecycle.md) §5.1 描述的窗口补偿扫描尚未实现,水位越过空洞后到达的较小 ID 不会被快路径(`ID > W`)发现,端到端顺序保证仍待与库方联合验证。HOL deadline 已改用 `PROCESSING_STARTED_AT`(为空时以 `UPDATED_AT` 兜底)和可注入 `Clock`;Q6 仍需确认长期积压与人工重放的期限口径。**阶段 0 只读迟到检测已实装**(监视被放行的空洞 ID,命中即计入 `msgx.pipeline.late_arrival.detected.total` 并告警,不补入队);补偿扫描的阶段 1(按类型安全补入队)与 Q2 契约仍待推进。
- **兼容入口与水位(已修)**`POST /cminmsgs/send` 直接写 `PROC_STATE`、不参与水位,因此登记行会超出水位;主泵只领 `msgId ≤ W`,在水位追平前不领取(`Pump` 已实现,端到端用例守住)。代价是这类消息延迟到水位追平,且**必须让收报轮询运行**([message-lifecycle.md](message-lifecycle.md) §5.1/§12)。
- **回填闭环(已修 · V4)**:三种结果已区分(写入成功 / 早已标记 / 信箱行不存在),并新增放弃语义:确认 `MISSING` 立即放弃、暂时性故障达 `backfill-max-attempts` 后停止自动重试,两者都告警且可由人工恢复;**放弃 ≠ 标记已确认**(`BACKFILL_AT` 仍为空,清除前提不成立)。扫描改为公平轮转,饥饿问题关闭。仍存在的相关风险是"打标即清除"语义下**`R` 无法保护重放窗口**,必须另行约定保留期或引入独立原文保留通道([message-lifecycle.md](message-lifecycle.md) §12 G10)。
- **重放互斥**`MessageLifecycleGate` 只覆盖回填与人工重放,毒丸升级路径不在其中,存在「先标 DEAD 并打标、再被重放拨回 PENDING」的窗口。
- **事务与外部副作用**事务边界以 §3.3 表 1 为准;[message-lifecycle.md](message-lifecycle.md) §4 的两个崩溃窗口已随 V2 迁移消除。回填本身仍是跨库单写,失败按退避重试并由超期期限 R 兜底;R 的取值待 Q6 确认。
- **收报与调度**:水位与空洞计时已与入队同事务推进([message-lifecycle.md](message-lifecycle.md) §5.1 口径)。**较小 ID 迟提交尚无补入队机制**:只读迟到检测(阶段 0)已实装,仅计数与告警;窗口补偿扫描(阶段 1,G1)未实现,端到端顺序保证仍待与库方联合验证。空洞老化阈值 `max-commit-delay` 的取值缺依据(§12 G7)。HOL 计时已改用 `PROCESSING_STARTED_AT` + 可注入 `Clock`(§3.2;Q6 仍需确认长期积压与人工重放的期限口径。
- **重放互斥**`MessageLifecycleGate` 只覆盖回填与人工重放,毒丸升级路径不在其中,存在「先标 DEAD 并打标、再被重放拨回 PENDING」的窗口(§12 G5)。
- **原文保留通道**:若库方清除语义为"打标即清除",回填成功后原文即可被清除,重放窗口失去保护;独立原文保留通道尚未设计(§12 G10)。
- **快照与业务能力**DNLD/RESP/ADFT 与 FLOP/FDEL 处理器、整包校验与跨运营日整包拒绝均已接入;但 RESP 应答守卫与出站请求未实现,忽略规则(US-04)未实现,29 类 FLOP 与参考应答的逐类矩阵未补全,ADFT 缺失字段与 `FLID` 重用语义待上游确认。
- **航班读写**:唯一写入口与权威读已落地;ROUT/ERUT 联合主键、空值/未知属性保真、事件在事务内只算一次、逐航班多次查询仍待修正(见 flight-state.md §6)。`/all/flights` 尚未实现。
- **请求、参考数据与归档**`REQ_TRACK` 表与仓储已建,但无运行时协调与 `COUTMSGS` 出站适配;`REF_MASTER` 未建表;`PROC_STATE_HST` 未建表。航班历史清理脚手架已实现,历史存储未接通时删 0 条。