docs(acm2-63): 收敛文档口径并补齐作业健康指标登记

含:MAFL 派生投影与 SRVT/VIPF 无界集合口径、schd EVENT_ID 写代次与 DEAD 代次替换、运营日时区 fail-fast、缺口索引增删(G-MAFL/G-SRVT-VIPF/G-COMPAT-HTTP/G-REQ-OPEN-UNIQUE,移除 G-JOB-HEARTBEAT)、job 心跳/扫描积压/最近失败指标登记、INV-18 验证映射改为实际测试名。
This commit is contained in:
windyboy
2026-09-12 21:00:01 +08:00
parent 8631ec0b20
commit 8093d20f9b
5 changed files with 28 additions and 18 deletions
+1 -1
View File
@@ -89,7 +89,7 @@ CIIMS / AODB 等上游
| 存储 | 承载内容 | 职责说明 |
|---|---|---|
| 自有 PostgreSQL | 单行锁 `PIPELINE_LOCK`、处理状态与回填事实 `PROC_STATE`、消费水位 `INBOX_CURSOR`、待发事件 `MSG_EVENT`、请求跟踪 `REQ_TRACK`、航班当前态 `FLIGHT_SCHD` + 8 张资源明细表 + `FLIGHT_ROUTE_POINT`、留痕 `SCHD_SNAP_LOG` | 本系统唯一业务数据库。消息处理、状态推进、处理终态、回填意图与待发事件在单事务内原子提交;本地事务只在此库。 |
| 自有 PostgreSQL | 单行锁 `PIPELINE_LOCK`、处理状态与回填事实 `PROC_STATE`、消费水位 `INBOX_CURSOR`、待发事件 `MSG_EVENT`、请求跟踪 `REQ_TRACK`、航班当前态 `FLIGHT_SCHD` + 现有 8 张资源明细表 + `FLIGHT_ROUTE_POINT``SRVT`/`VIPF` 专用明细尚未实现 `[G-SRVT-VIPF]`、留痕 `SCHD_SNAP_LOG` | 本系统唯一业务数据库。消息处理、状态推进、处理终态、回填意图与待发事件在单事务内原子提交;本地事务只在此库。 |
| 共享 MySQL | `CMINMSGS` 入站信箱、`COUTMSGS` 出站信箱 | 外部系统所有。本系统仅执行约定的信箱读写与处理标记回填,不建表、不迁移 schema、不写历史表;由库方按 `Q9` 执行的清除与历史归档见 contracts.md「保留与清除」。兼容 HTTP 入口可按既有契约写入入站信箱。 |
**不使用跨库事务。** PG 事务只能保证“处理结果与待发事件一起提交”(`INV-17`),不能覆盖 MySQL 回填或 Kafka 发送等外部副作用。跨存储依靠幂等、重试和持久化补偿恢复;各中断位置的判定与恢复动作见 design.md「中断恢复」。
+7 -5
View File
@@ -24,10 +24,10 @@
| 记录 | 用途 | 关键约束 |
|---|---|---|
| `PROC_STATE` | 入站消息的处理状态、身份、尝试次数、错误原因与回填事实 | `MSG_ID = CMINMSGS_ID` 主键防重复入队;`IDENTITY_KEY` 唯一约束防业务重复;按最小未完成 `MSG_ID` 取队头;`BACKFILL_NEXT_AT` 非空 = 还欠一次回填,`BACKFILL_AT` 非空 = 标记已确认,`BACKFILL_ABANDONED_AT/REASON` 非空 = 已停止自动重试(**不等于**标记已确认);`RECEIVED_AT` 复制自信箱接收时间、**可能为 NULL**、仅用于对账与展示;`ENQUEUED_AT` 是本地入队时间、非空、是超期判据的唯一依据。 |
| `MSG_EVENT` | 等待投递的事件(outbox) | `EVENT_ID` 决定投递顺序(全局串行分配,见投递);`TARGET` 区分 `KAFKA:msg` / `KAFKA:schd``PARTITION_KEY` 当前取 `FLID``Q4` 定案前为假定,见 `C-29`);`EVENT_TYPE` 区分 UPSERT 与 TOMBSTONE。`KAFKA:schd``FLID` 单行 upsert,只保留最新 `STATE_VERSION`。 |
| `MSG_EVENT` | 等待投递的事件(outbox) | `EVENT_ID` `KAFKA:msg` 是稳定事件身份并决定投递顺序;对 `KAFKA:schd` 是每次接受 upsert 时替换的写代次。`TARGET` 区分 `KAFKA:msg` / `KAFKA:schd``PARTITION_KEY` 当前取 `FLID``Q4` 定案前为假定,见 `C-29`);`EVENT_TYPE` 区分 UPSERT 与 TOMBSTONE。`KAFKA:schd``FLID` 单行 upsert,只保留最新 `STATE_VERSION`。 |
| `REQ_TRACK` | 上游请求及应答关联 | 状态 `PENDING / SENT / DONE / EXPIRED`;保存请求类型、覆盖运营日、发送方、出站信箱 ID 与发送/完成时间;**「同类只允许一个开放请求」的唯一键 = `(请求类型, 覆盖运营日, 发送方)`,且仅对开放状态生效**。登记、超时与应答匹配尚未实现 `[G-REQ-TRACK]`。 |
| `REF_MASTER` | 静态参考数据(目标表) | `(RTYPE, RKEY)` 唯一;尚未建表,客户端与刷新流程见 user-stories US-13/US-14US-14 两类映射的存储落点未定。 |
| `FLIGHT_SCHD` | 航班标量及单值异常字段 | `FLID` 主键;`OPERATION_DAY` 一经确定不可变;版本与最近消息 ID 用于追踪。变长集合存于 8 张资源明细表与 `FLIGHT_ROUTE_POINT`,规则见 flight-state.md。 |
| `FLIGHT_SCHD` | 航班标量及单值异常字段 | `FLID` 主键;`OPERATION_DAY` 一经确定不可变;版本与最近消息 ID 用于追踪。现有变长集合存于 8 张资源明细表与 `FLIGHT_ROUTE_POINT``SRVT`/`VIPF` 专用明细尚未实现 `[G-SRVT-VIPF]`,规则见 flight-state.md。 |
| `INBOX_CURSOR` | 消费水位 `W`、空洞计时 `holeSince`、播种事实 `SEEDED_AT` | 单行游标;`W` 只随新 ID 成功入队推进,遇空洞即停;`HOLE_SINCE` 持久化空洞观测时刻,进程重启不丢计时。`SEEDED_AT IS NULL` **不等于**从未消费(已有库新增列后同样为 NULL)。 |
| `SCHD_SNAP_LOG` | 日计划处理留痕 | 只追加、可重建,不参与状态决策;保留期见 reference。 |
| `PROC_STATE_HST` | 终态处理记录的归档目标 | 尚未建表 `[G-PROC-HST]`;只归档到自有 PG 的目标表,不落共享库历史表。 |
@@ -61,8 +61,8 @@
| 错误类别 | 处理方式 |
|---|---|
| `MALFORMED` | 报文非法原文缺失,直接 `DEAD`,不在重放白名单内。 |
| `PROTOCOL` | 整包协议拒绝(运营日冲突、声明数量不符、缺载荷等),立即 `DEAD`,整包不落地、不重试。 |
| `MALFORMED` | 报文非法原文缺失或解码结果缺少该类型必需的业务载荷,直接 `DEAD`,不在重放白名单内。 |
| `PROTOCOL` | 载荷存在但整包违反业务协议(运营日冲突、声明数量不符等),立即 `DEAD`,整包不落地、不重试。 |
| `CODEC_ERROR` | 解码能力问题,退避重试;修复后允许重放。 |
| `UNSUPPORTED` | 处理器或快照能力未实现,按可恢复失败处理,不直接当作非法报文;仍受重试上限约束。 |
| `INFRA` | 基础设施或执行异常,退避重试。 |
@@ -278,6 +278,8 @@ PENDING → SENT → DONE
`KAFKA:schd` 只提供最新状态通知,不保留每次中间变化,因此 outbox 按 `FLID` 单行 upsert:同一 `FLID` 只保留最新 `STATE_VERSION` 的事件与投递状态。两条写规则:
`KAFKA:schd` 行的 `EVENT_ID` 不是跨代次稳定的事件句柄:每次接受更新都从全局序列取得新值并替换原主键,用作条件确认的写代次。重放和人工处置只能针对当前 `(TARGET, PARTITION_KEY, EVENT_ID)`;旧代次被替换后不再能按旧 ID 寻址。升级时若已有重复行,按 `STATE_VERSION DESC, EVENT_ID DESC` 保留一行,使迁移与运行时只进不退规则一致。
- **只进不退**:仅当新事件的 `STATE_VERSION ≥` 行内现有版本才覆盖,防止迟到的旧事件把新状态压回去。该合并规则以 `C-21``FLID` 在保留期内不复用)为前提。
- **条件标记**:发送成功后按**读取时刻的版本**做条件标记(`WHERE STATE_VERSION = <本批版本>`);该行若期间已被更新的版本覆盖,则不标记,留待下一轮重发。
@@ -291,7 +293,7 @@ PENDING → SENT → DONE
### 8.3 清理
`SENT` 的事件行按 `PARAM:msgx.pipeline.event-retention` 由维护作业清理`DEAD` 行保留作 DLQ,人工处置后再清理。没有这条规则时 outbox 会无限增长
`SENT` 的事件行按 `PARAM:msgx.pipeline.event-retention` 由维护作业清理`KAFKA:msg``DEAD` 行保留作 DLQ,人工处置后再清理`KAFKA:schd``DEAD` 行只保留到同一 `FLID` 出现新的、可接受的状态代次,新代次会把单行投影重置为 `PENDING` 并清空旧错误。该取舍服从 schd 只保存最新状态的契约,因此被替换的 schd DEAD 代次不再由 `MSG_EVENT` 提供持久审计句柄
## 9. 失败恢复与维护作业
+3 -3
View File
@@ -18,7 +18,7 @@
| 对象 | 职责 |
|---|---|
| `FLIGHT_SCHD` | 一行一个 `FLID`,保存标量字段、`STATE``STATE_VERSION``OPERATION_DAY`、最近消息 ID 和审计时间。 |
| 8 张资源明细表 | 登机门、值机柜台、转盘、计划机位、滑槽、延误、靠撤桥、轮挡等变长集合;主键为 `(FLID, ORDINAL)`。 |
| 资源明细表 | 当前 8 张表保存登机门、值机柜台、转盘、计划机位、滑槽、延误、靠撤桥、轮挡等变长集合;`SRVT`/`VIPF` 专用明细尚未实现 `[G-SRVT-VIPF]`主键为 `(FLID, ORDINAL)`。 |
| `FLIGHT_ROUTE_POINT` | ROUT 与 ERUT 两类路线点,使用 `ROUTE_KIND` 区分;主键应包含该列,避免两类路线的序号冲突。 |
| `PROC_STATE` | 信箱消息的处理终态、业务身份幂等记录,以及回填事实(`RECEIVED_AT` / `BACKFILL_*`)。 |
| `MSG_EVENT` | 事务 outbox,承载整态投影、变更通知和删除 tombstone。 |
@@ -33,13 +33,13 @@
### 2.2 字段与集合
标量异常对象前缀字段 `SRVT`/`VIPF`/`MAFL` 文本字段存于主表。变长资源完整保存到明细表,保留输入顺序和协议源序号:
标量异常对象前缀字段存于主表。协议中的 `SRVT``VIPF` 是无界集合,目标形态必须按集合完整保存到专用明细表示;当前 wire mapper 与持久化尚未实现 `[G-SRVT-VIPF]``MAFL` 不是 SIS/XML 入站字段,而是由共享航班的 `MAID``FLID``FLNO` 生成的主航班派生投影;当前尚未实现 `[G-MAFL]`
- `ORDINAL` 是持久化顺序,从 1 开始;`SOURCE_SEQ` 是上游序号,允许为空或重复。
- 相同资源号不代表同一条分配,禁止按资源号去重。
- 每次持久化完整航班状态时,明细表按该 `FLID` 先删后插,以完整合并结果为准。
- ROUT 与 ERUT 是两类独立集合,不能因相同序号覆盖彼此。
- 主/共享关系主表字段承载`MAID` 是共享航班指向主航班 `FLID` 的引用(非共享航班为 `NULL``MAFL` 是主航班上的共享航班列表
- 主/共享关系主表`MAID` 为事实来源`MAID` 是共享航班指向主航班 `FLID` 的引用(非共享航班为 `NULL``MAFL` 只在读取和事件投影时从子航班事实派生,不按入站标量解析或保存
## 3. 合并与写入语义
+8 -5
View File
@@ -45,7 +45,7 @@
- **INV-15** 缺席于某个日计划不构成删除理由;删除只由 FDEL 或受控历史清理触发。
- **INV-16** 外部副作用(回填、Kafka 投递、出站信箱)失败可重试,但不回滚已提交的本地业务结果。
- **INV-17** 状态变更、待发事件、处理终态与回填意图在同一 PG 事务内原子提交。
- **INV-18** 航班表的写者集合是「主泵处理器」与「历史清理」;两者必须互斥(同一 `PIPELINE_LOCK`,或清理在同一事务内复查判据后再删除),不得出现清理删除与处理器更新同一 `FLID` 的竞态。`[实现核对待确认,关联 ACM2-30]`
- **INV-18** 航班表的写者集合是「主泵处理器」与「历史清理」;两者必须互斥(同一 `PIPELINE_LOCK`,或清理在同一事务内复查判据后再删除),不得出现清理删除与处理器更新同一 `FLID` 的竞态。
- **INV-19** 整包校验失败或运营日冲突时整包不落地,既有状态与版本保持不变。
- **INV-20** 处理器幂等:同一消息重复执行只产生一次业务效果。身份唯一只防「重复记录」,不防「重新执行」;29 类 FLOP 幂等矩阵补全前,本条**不可声明**。`[G-FLOP-IDEMPOTENT]`
@@ -88,14 +88,14 @@
| INV-11 | 权威唯一 | 缺口(展示视图与缓存不得成为写入或对账来源) |
| INV-12 / INV-13 | PG 事务失败、快照重复或迟到 | 整体回滚重试、不重复推进版本、不回退状态、不误删增量航班 |
| INV-12 | 运营日冲突 | 整包 `DEAD(PROTOCOL)`,既有状态与版本不变 |
| INV-14 / INV-19 | 整包协议拒绝(声明数不符、缺载荷) | 整包不落地、整体回滚、既有状态不变 |
| INV-19 | 整包协议拒绝(声明数不符、运营日冲突) | `DEAD(PROTOCOL)`整包不落地、整体回滚、既有状态不变 |
| INV-15 | 缺席不删除 | 缺口(F-del 与清理路径分别断言) |
| INV-16 | 外部副作用失败后本地结果不变 | 待核对 |
| INV-17 | 业务型终态四件套同事务 | 待核对;真实 PG 用例待补(ACM2-39 |
| INV-18 | 清理与处理并发 | **缺口**:需断言删除与处理同一 `FLID` 时互斥(关联 ACM2-30 |
| INV-18 | 清理与处理并发 | `HistorySweepJobTest`(归档后被主泵更新的航班不删除、不发 tombstone)+ `HistorySweepPurgePgTest`(删除阶段失败时 tombstone 与删除整体回滚 |
| INV-20 / CLM-3 | 重放同一条消息 | 缺口:29 类 FLOP 幂等矩阵未补全 |
| CLM-4 | 放弃行与清除前提 | 断言放弃行不写标记、不被当作已打标(关联 ACM2-36) |
| CLM-9 | 回填/积压完成时限 | 缺口:需要「最老待回填年龄」「扫描积压」「作业心跳」指标(关联 ACM2-38) |
| CLM-9 | 回填/积压完成时限 | 指标已就位:`msgx.pipeline.job.heartbeat_age_seconds` / `ticks.total` / `failures.total` / `last_sweep_selected``msgx.pipeline.backfill.oldest_unmarked_seconds`(关联 ACM2-38);实际延迟仍需现场数据,CLM-9 不可声明 |
| — | 请求超时、无匹配 RESP、时间单位不一致 | 不误用迟到应答、不提前完成请求 |
| — | stub 误配置、重复实例、停机中断 | 生产拒绝不安全启动,工作线程能正确退出 |
@@ -114,7 +114,10 @@
| `G-EVENT-RETENTION` | `MSG_EVENT` 已发送行的保留期与清理作业未实现 | outbox 有界性 |
| `G-BACKFILL-BACKOFF` | 回填独立退避键(`backfill-backoff-ms` / `-cap-ms`)未实现,当前为代码内硬编码(取值见 reference) | 回填重试节奏 |
| `G-KAFKA-D3` | `kafka.producers.default.max-in-flight` 与 D3 要求的 1 不一致(取值见 reference) | 投递幂等前提 |
| `G-JOB-HEARTBEAT` | 作业心跳、扫描积压、实际回填延迟指标未实现 | CLM-9;回填可观测性 |
| `G-REPLAY-CHANNEL` | 「打标即清除」语义下的独立原文保留通道未设计 | CLM-5 |
| `G-MAFL` | 主航班 `MAFL` 派生投影及主/共享原子级联未实现;`MAFL` 不是 SIS/XML 入站字段 | 航班完整态;删除与重建 |
| `G-SRVT-VIPF` | SIS/XML 的 `SRVT``VIPF` 无界集合尚未映射到 wire/domain/持久化明细 | 航班完整态;无损字段保存 |
| `G-COMPAT-HTTP` | compat 入口仍未实现 Q3 定案后的 ResponseDto、媒体类型、字符集、失败响应与请求体上限 | `C-28`US-02 |
| `G-REQ-OPEN-UNIQUE` | `REQ_TRACK` 尚无约束开放态 `(REQ_TYPE, OPERATION_DAY, SENDER)` 唯一性的部分索引 | US-08`G-REQ-TRACK` |
`G1` 沿用 Plane 既有编号(ACM2-41);其余为文档内稳定标记,与 Plane 工作项的对应关系在 Plane 侧维护。
+9 -4
View File
@@ -41,7 +41,7 @@
| `msgx.stubs` | `true`dev profile | 安全默认 | 内存适配器;**生产禁止**,生产 profile 不设置该键 |
| `msgx.schd.flush-period` | `3s` | 现役 | `schd` 聚合周期 |
| `msgx.schd.flush-limit` | `500` | 假定 | 聚合批上限 |
| `msgx.operation-day.zone` | `Asia/Shanghai` | 现役 | 运营日推导时区 |
| `msgx.operation-day.zone` | `Asia/Shanghai` | 现役 | 运营日推导时区;配置值非法时启动失败,不使用代码字面量回退 |
| `msgx.operation-day.cutoff-hour` | `0` | **假定(占位)** | 切日边界;待业务确认 |
| `msgx.identity.include-day-boundary` | `false` | 安全默认 | 身份是否加日期边界;影响去重语义,不能当调优项切换(`Q11` |
| `msgx.health.backlog-cache-ttl-ms` | `30000` | 假定 | 积压快照缓存窗口;`0` = 不缓存;`/health``/metrics` 共用同一快照 |
@@ -82,10 +82,15 @@
| `msgx.pipeline.watermark.lag` | 水位落后信箱最新 ID 的距离 | 增长 → 收报停滞 |
| `msgx.pipeline.hole.aged_out.total` | 永久空洞放行次数 | 突增 → ID 序列大量空位 |
| `msgx.pipeline.late_arrival.detected.total` | 迟到到达命中数 | **> 0 表示上游提交确实晚于水位推进,需要与库方对契约** |
| `msgx.pipeline.job.heartbeat_age_seconds` | 距上一次作业 tick 完成的秒数(未跑过为 -1) | 持续增长 → 作业线程卡死 |
| `msgx.pipeline.job.last_failure_age_seconds` | 距最近一次作业 tick 失败的秒数(从未失败为 -1) | 配合 `failures.total` 增长判断扫描/历史作业异常 |
| `msgx.pipeline.job.ticks.total` | 作业 tick 完成次数 | 不增长 → 作业停摆 |
| `msgx.pipeline.job.failures.total` | 作业 tick 抛错次数 | 增长 → 扫描/历史作业异常 |
| `msgx.pipeline.job.last_sweep_selected` | 上一轮回填扫描选中的待办条数(扫描积压) | 持续顶到批次上限 → 扫描吃不消 |
取数规则:统一走 `BacklogSnapshotProvider``PARAM:msgx.health.backlog-cache-ttl-ms`),`/health``/metrics` 共用同一快照——`backlog()``PROC_STATE` 的全表聚合,不能被高频抓取打穿;**无法取数上报 `NaN`,无可比记录的年龄/滞后类仪表上报 `-1`,都不伪造 0**。日志出口故障不得阻塞业务线程。
作业健康:回填扫描的存活性与延迟需要独立可观测(作业心跳、扫描积压、实际回填延迟),因为回填的唯一驱动是扫描作业 `[G-JOB-HEARTBEAT]`
作业健康:回填的唯一驱动是扫描作业,因此作业存活必须独立可观测——`msgx.pipeline.job.heartbeat_age_seconds` / `ticks.total` / `failures.total` 是作业心跳,`msgx.pipeline.job.last_sweep_selected` 是扫描积压,`msgx.pipeline.backfill.oldest_unmarked_seconds` 是实际回填延迟;作业线程停摆由 `/health` 的作业指示器判 `DOWN`(心跳超过 `3 × 扫描周期`,周期是代码常量)
## 3. 模块入口
@@ -105,8 +110,8 @@
| 错误类别 | 触发 | 处置 | 可重放 |
|---|---|---|---|
| `MALFORMED` | 报文非法原文缺失 | 立即 `DEAD` | 否 |
| `PROTOCOL` | 整包协议拒绝(运营日冲突、声明数不符、缺载荷 | 立即 `DEAD`,整包不落地 | 否 |
| `MALFORMED` | 报文非法原文缺失或缺少该类型必需的业务载荷 | 立即 `DEAD` | 否 |
| `PROTOCOL` | 载荷存在但整包违反业务协议(运营日冲突、声明数不符) | 立即 `DEAD`,整包不落地 | 否 |
| `CODEC_ERROR` | 解码能力问题 | `FAILED` 退避 | 是 |
| `UNSUPPORTED` | 处理器或快照能力未实现 | `FAILED` 退避,受尝试上限约束 | 是 |
| `INFRA` | 基础设施或执行异常 | `FAILED` 退避 | 是 |