docs: 航班状态设计重写为当前基线,按请求/下载与动态两条线讲清状态变更 (ACM2-30)

This commit is contained in:
windyboy
2026-09-08 18:50:34 +08:00
parent 1fc2b2c895
commit bb81deae0a
11 changed files with 530 additions and 369 deletions
+4 -2
View File
@@ -161,6 +161,9 @@ MICRONAUT_ENVIRONMENTS=dev ./gradlew run # dev stub 冒烟:内存 stub,无
## 文档
从 [设计文档入口](docs/README.md) 开始阅读;最新完成度见
[ACM2-29 核查与简化计划](docs/acm2-29-audit-and-simplification.md)。
- [docs/architecture.md](docs/architecture.md):架构速览——**中间件定位**、总体拓扑(JDBC 轮询主路径)、
上下游边界、模块职责、关键决策、数据边界、部署与安全姿态、可观测性、就绪度。
- [docs/design.md](docs/design.md):设计细节——系统边界、状态机与错误分类、数据模型、
@@ -168,8 +171,7 @@ MICRONAUT_ENVIRONMENTS=dev ./gradlew run # dev stub 冒烟:内存 stub,无
- [docs/user-stories.md](docs/user-stories.md):阶段 A US-01US-14、延后清场 US-15、上线 Epic、
legacy HTTP 去留及逐项文档 TODO;包含验收标准、依赖、实现差距与待确认问题。
(原 ACM2-1522 文档整改清单 user-stories-todo.md 已收敛完成并移除,跟踪记录见 Plane。)
- [docs/decision-flight-state.md](docs/decision-flight-state.md)决策提案——运营航班表
FLIGHT_STATE 是否提前落自有 PG(阶段 A 权威化);讨论 issue ACM2-28,定案前不实施。
- [docs/decision-flight-state.md](docs/decision-flight-state.md)有效决策摘要——单库权威已采纳,固定槽位方案已撤销;历史全文另存。
- [docs/legacy/](docs/legacy/):外部参考/基线材料(自 legacy 仓库拷贝,非本系统文档)——
`msgexchange-api-legacy-user-stories.md`legacy 行为对拍基线,ACMA-4)、`unisysaodbsis.xsd`、
`SIS_AODB_RMS-V0.1.md`(消息结构唯一事实源;CIIMS 中间件交换模型)。
+18
View File
@@ -0,0 +1,18 @@
# 设计文档入口
当前实现核对日期:2026-09-08。文档中的目标能力不等于已实现;测试通过不等于现场已发布。
| 文档 | 唯一职责 |
|---|---|
| [architecture.md](architecture.md) | 系统边界、模块职责、FIFO、单写者、数据库归属。 |
| [design.md](design.md) | 管道流程、处理/投递状态、恢复与运行配置。 |
| [flight-state-design-v2.md](flight-state-design-v2.md) | 当前航班表、命令与事务实现,及明确标记的缺口。路径保留以兼容历史引用。 |
| [flight-state-semantics.md](flight-state-semantics.md) | 字段缺失、清除、集合替换及 wire 语义;区分实现和验收目标。 |
| [user-stories.md](user-stories.md) | US/OPS 验收目标,不能用“当前基础”替代完成证据。 |
| [decision-flight-state.md](decision-flight-state.md) | 有效决策与理由,历史全文转入 legacy。 |
| [ACM2-29 核查与简化计划](acm2-29-audit-and-simplification.md) | 本次完成度、问题证据、简化边界和后续验收计划。 |
| [旧评审](review-flight-state-2026-09-08.md) | cd5de56 基线的历史评审,不能当作当前缺陷或完成清单。 |
| [SIS 规范](legacy/SIS_AODB_RMS-V0.1.md) / [XSD](legacy/unisysaodbsis.xsd) | 外部协议事实;即使在 legacy 目录仍是兼容依据。 |
维护规则:架构只写约束,设计写当前机制,故事写目标,核查报告写证据,Plane 跟踪工作。
不在多份文档复制阶段完成清单;修订状态必须同步受影响的规则,但不改写历史迁移。
+123
View File
@@ -0,0 +1,123 @@
# ACM2-29 完成度核查与简化计划
日期:2026-09-08。核查基线:`7c2d22e`,开始时工作区干净。
范围:Plane ACM2-29 正文及评论、当前生产代码、迁移、测试与全部顶层设计文档。
本文包含本次本地修正,不代表代码已提交、发布或现场验收。
后续工作:[ACM2-30](https://plane.chans.xyz/space/projects/0b5086dd-dd33-4893-86d6-9e716a21b21f/issues/eb647af8-b9a5-4417-aeb6-f9da4e1a3661),已创建在 Backlog,正文已读回核对。与 ACM2-29 的 relates_to 创建返回成功;关系查询接口报参数错误,未能独立读回复核关联。
## 1. 结论
**ACM2-29 没有全部完成。** PostgreSQL 存储重构内核大体落地,但真实快照链路、查询接口、
部分保真与恢复不变量、数据迁移验收及 Oracle 完整适配尚未完成。
正文仍要求“全宽表零子表”,2026-09-08 09:49 评论已撤销该方案;17:44 评论又称 P2–P4 全部完成。
应按修订后的 v2 目标核查,不再按已撤销的正文补做有损槽位,也不将收口评论当作证明。
**存在局部过度工程化,主要是边界重复和文档失真,不是表数量本身。**
一套关系库、显式清除、明细保真、事务、FIFO、outbox 和回填重试都有具体失败场景支撑。
再次推倒已落地的无损存储会增加迁移风险;优先减少重复表示、未使用分支和无效承诺。
## 2. 原计划完成度
| 工作 | 核查结论 | 代码或证据 |
|---|---|---|
| P0:CAS 消息身份、成功后回填隔离 | 部分完成 | SnapshotFlow 锁内 LAST_MESSAGE_ID/CAS 已实现;回填待办写入再次失败仍可冒泡到处理失败边界。 |
| P1:无损表、命令和唯一写入口 | 内核完成,边界未全验收 | V1.3.0、FlightStateEngine、persistNextStates9 张表/10 类集合。ROUT/ERUT、未知属性和空数组仍有缺口。 |
| P1DNLD + 一个 FLOP 纵向链 | GTDT 已有;真实 DNLD 未完成 | JacksonXmlCodec 的 SCHD body 为 nullStageResult.parser 默认 null,仅测试赋值;主泵先要求 Handler,生产只注册 GTDT。 |
| P2-1:双读对拍 | 历史工具工作存在,未证明现场对拍 | 2af71e8 曾有双读比较,7c2d22e 移除旧读路径;当前保留 FlightStoreDiffTool,未见真实输入逐字段等值报告。 |
| P2-2:权威读、展示视图、查询接口 | 仓储和视图完成,接口未完成 | V1.3.1、FlightSchdReadAssemblerInboxController 的 /all/flights 仍是 TODO。 |
| P2-3:回填持久补偿 | 有待办和扫描,但崩溃窗口未闭合 | BACKFILL_TODO 在外部回填失败之后才 record;提交后立即崩溃时没有待办;轮询入队不会重置终态。 |
| P2-4:带数据升级 | 脚本可执行已验证,无损升级未完成 | FlywayMigrationTest 显式确认 V1.2 删除集合数据;没有旧集合回填到明细的实现。 |
| P2-5:锁内读取及语义矩阵 | 快照内核完成;文档与代码仍有偏差 | 增量事件在锁外预览产生;矩阵原称文本 Clear、DELY 历史保留及明细 FK,代码并不支持这些陈述。 |
| P3-APostgreSQL 验收 | 现有套件全绿,不是完整协议矩阵 | 本次基线 100 测试、0 失败、0 跳过,含 16 JDBC + 2 迁移测试;“16 类”样例未覆盖 ERUT 与 ROUT 共存。 |
| P3-BOracle 11g | 未完成,不只是等待现场 | 仅两个 SQL 模板;无 DDL、驱动接入和完整绑定,其他仓储仍含 PG SQL。 |
| P4:旧列退场、索引与回退窗口 | 代码退场完成,运营验收证据不足 | V1.4.0 删除旧列及冗余索引;没有真实查询 EXPLAIN、备份恢复与现场回退窗口关闭证明。 |
| 32 个 Handler | 未完成,后续业务范围 | 当前只有 GtdtHandler 生产实现,其余不应因存储收口而视为已交付。 |
ACM2-29 当前为 In Progress,本次没有修改其状态、正文或发表评论。
## 3. 具体问题与优先级
### A. 本次已修:日期差删破坏保留航班明细
JdbcFlightSchdRepository.deleteDiffByDay 原先先按传入 FLID 无条件删除明细,再按 FDAY 删除主行。
输入包含跨日迁移或 FDAY=NULL 的航班时,主行保留、集合丢失,违反快照域化差删不变量。
本次先按 FDAY 选择实际删除成员,主表与明细使用同一范围;继续由主泵事务行锁保护。
没有增加表或修改已发布迁移。
新增真实 PG 测试同时覆盖目标日期删除、跨日成员保留、ADFT 无归属成员保留;
原实现测试失败,修正后通过。它揭示的是此前测试矩阵遗漏,而非原测试结果造假。
### B. P0:尚未闭合的正确性边界
1. **路线键冲突**V1.3.0 的 flight_route_point 主键只有 (FLID, ORDINAL)
replaceRoutes 对 ROUT/ERUT 各从 ordinal=1 写入,同航班共存时冲突。
通过新迁移改为 (FLID, ROUTE_KIND, ORDINAL),补真实 PG 往返与整事务回滚测试。
2. **补偿落账窗口**Pump/SnapshotFlow 的回填在业务提交后执行,失败才写待办;
record 再失败会逃逸到 MessageProcessor.processOne 的 INFRA 落账,可能降级已提交成功态。
将回填意图与终态同事务写入;成功回填后确认待办。覆盖提交后立即崩溃、待办写失败、重复及 DEAD 终态。
3. **落库和 wire 来源不同**GtdtHandler 锁外计算 preview/SchdPush,主泵锁内重新读态计算 nextState。
改为事务内一份 nextState 同时服务持久化与状态事件;保留独立 msg 通知意图的业务语义。
4. **“无损”不是全字段保证**parseCollection 可接受非对象数组元素;未知属性被存储列映射忽略;
Replace([]) 的 wire 与读回不同;异常对象局部替换可能残留旧列;文本 null 清除与文档曾不一致。
固定合法字段/形状及空值黄金样例,未知输入显式拒绝,避免成功后静默丢字段。
以上 B 项本次记录为后续工作,没有声称已修复。
### C. P1:真实能力和验收
- 接通真实 XML DNLD/RESP 的解析、整包验证与路由,补无匹配/迟到 RESP;不依赖测试全局 parser。
- 实现 /all/flights 兼容接口;按真实 wire 契约验证。
- findAll/findByFlids 当前每航班追加 10 次集合查询;批量加载并明确同一数据库快照,
在实际规模下记录查询数和执行计划,不添加无消费方的索引或缓存。
- V1.1→V1.4 带数据升级必须提供备份/可信输入恢复步骤和逐字段核对;
不能把“确认旧集合已经丢失”的迁移测试作为无损验收。
### D. Oracle 是完整实施项
Oracle11gDialect 没有生产调用方,snapshot MERGE 的插入列包括 4 个追踪/时间字段,
模板尾部却只生成 3 个占位符;现有 PG 绑定也无法复用。
还需完整 DDL、其他仓储 SQL、生成 ID、CLOB、空值、驱动/Flyway/JDK 组合及目标 11.2 集成测试。
现场环境只影响目标库实测,不应把尚未编写的适配代码归为外部阻塞。
## 4. 简化方案
| 保留 | 简化或延后 | 原因 |
|---|---|---|
| 单库事务、行锁、身份、outbox | 不新增双库镜像/分布式协调层 | 当前单写者范围足够,不解决假设的扩容问题。 |
| 主表 + 明细 | 不重回固定槽位,不重新设计整套存储 | 三门、重复资源、多次操作是已有契约数据。 |
| FlightNextState | 逐步消除 Handler 预览与主泵重复计算、字段集 JSON 往返 | 一条状态生成路径更容易证明 DB/wire 等值。 |
| 必要 Clear/Replace | 删除未使用的 snapshotReplace 命令解析参数;Apply 待真实协议调用方再决定 | 两种解析行为实际相同,Apply 又假设了未确认的定位唯一性。 |
| 数据库列映射 | 复用已有单一列清单;其余资源元数据按需要集中,避免再造注册框架 | 减少漂移而不新增架构层。 |
| 回填待办 | 一条持久队列覆盖中断恢复 | 用事务登记意图替代失败后记录和额外重启推断。 |
| 当前 PG 实现和既定 Oracle 目标 | 不继续堆未运行的通用方言模板 | SQL、绑定、DDL 必须作为可执行整体设计。 |
| 回归与对拍 | 保留可发现失败的矩阵;不为删空壳编写镜像测试 | 验收关注故障行为而非类数量。 |
本次已实施的小范围简化:读写复用 FlightSchdReadAssembler 列清单,删除
FlightDetailTables 未使用的集合清单和 hasAnyDetailRows 旧回退探测;移除 commandsFromFields
未使用的 snapshotReplace 参数及全部调用实参;更新误导的双写注释。
大范围模型、API、数据迁移和 Oracle 实施进入新 issue,避免将未验证的业务变更混入文档整理。
## 5. 下一批实施顺序与验收
1. **可靠性收口**:路线键、回填意图、DB/wire 单一状态、严格字段验证。
验收:真实 PG 的正常/失败/回滚/重放/空值矩阵全绿,成功终态不降级。
2. **最小真实链路**DNLD → GTDT → 完整查询 → outbox,并验证 RESP 守卫。
验收:实际 XML 输入,不手动赋值 StageResult.parser;真实存储和查询、事件契约一致。
3. **读模型与升级**:批量一致读取、实际查询计划、带数据恢复和对拍证据。
验收:查询次数按批次有界,集合/源序号/顺序保真,备份恢复有演练记录。
4. **Oracle 完整适配**:按既定部署边界实现并单独验收,不借 PG 绿灯替代。
验收:目标 11.2 上新建/升级、DML、并发锁、事务恢复及字符语义通过。
每一步交付时同步对应设计及完成证据。32 Handler 扩展、阶段 B 和生产运维仍按原业务工作项推进,
本 issue 不承诺一次完成所有产品功能。不以类已存在、迁移成功或合并提交代替功能/发布验收。
## 6. 本次验证与文档整理
- 基线 ./gradlew test100 测试,0 失败,0 跳过。
- 新差删测试在原实现失败;修正后 ./gradlew test101 测试,0 失败,0 跳过,
包括 17 JDBC 与 2 Flyway 测试,Testcontainers 提供真实 PostgreSQL。
- 未连接 Oracle,未验证现场部署、生产负载或历史数据恢复。
- 重写航班设计为当前实现基线;历史决策归档,有效决策缩为摘要;
同步 architecture/design/user-stories/semantics/README,旧评审明确标为历史。
- 新 [文档入口](README.md) 规定各文件唯一职责,后续不再维护多份阶段完成清单。
+6 -2
View File
@@ -14,6 +14,10 @@ msgexchange-v2 是机场 OMMS 的上游报文处理中间件,用于替换旧
本文描述架构约束,不代表所有能力已实现;实现缺口见第 9 节。模块交互、状态机和参数详见 [design.md](design.md),需求见 [user-stories.md](user-stories.md)。历史报文契约仍以 [SIS 接口规范](legacy/SIS_AODB_RMS-V0.1.md) 和 [XSD](legacy/unisysaodbsis.xsd) 为兼容依据,其他 legacy 资料仅作参考。
现场供库时目标为 Oracle 11g,否则自建 PostgreSQL;当前只有 PG 实现可运行,Oracle 不是已支持的平台。
航班表结构统一见 [航班状态设计](flight-state-design-v2.md),当前验收结论见
[ACM2-29 核查](acm2-29-audit-and-simplification.md)。
## 2. 总体架构
```text
@@ -121,7 +125,7 @@ CIIMS / AODB 等上游
**部署与安全**
- 生产维持单活动实例,停机时停止接收新任务并等待工作线程退出。运行期排他保护尚未完成,当前不能依靠程序自动阻止双实例写入
- 生产维持单活动实例,停机时停止接收新任务并等待工作线程退出。已有事务级行锁,但消息认领和整个实例的排他保护尚未完成,不能依靠行锁宣称支持双实例 FIFO
- 配置、口令和环境端点通过环境变量提供。兼容写接口沿用内网信任模式,缺少鉴权,必须限制网络访问;管理端点不得直接暴露到生产外网。
- Eureka 用于服务发现,Logstash 接收结构化日志;日志出口故障不应阻塞业务处理。
@@ -145,4 +149,4 @@ CIIMS / AODB 等上游
- 生产启动校验、单实例排他保护、影子隔离和 Kafka 配置约束;当前配置仍允许 Kafka 参数覆盖,且默认 in-flight 值与 D11 要求不同。
- 死信告警、人工重放、端到端追踪、积压指标及安全边界。
具体进度由配套设计(design.md §9 已知缺口表)与 Plane ACM2-10 实施计划维护,本文不记录测试数量、临时补丁版本或逐项工单进展。架构基线沿用 ACM2-3,存储与事务边界以 ACM2-12 的修订为准
具体实现差异见 design.md §10 与 ACM2-29 核查报告,工作由 Plane 跟踪。有效存储决策见 decision-flight-state.md;历史阶段口径不覆盖当前规则
+23 -175
View File
@@ -1,182 +1,30 @@
# 决策:运营航班表 FLIGHT_SCHD 落自有 PostgreSQL 作阶段 A 权威(已定案 · 采纳选项 C)
# 决策摘要:单库航班状态与无损明细
> **状态**:已定案(讨论 issue**ACM2-28**,采纳选项 C;关联修订 ACM2-12)
> 本文正式确立选项 C 为最终方案:运营航班权威 `FLIGHT_SCHD` 与代版本 `SCHD_GEN` 提前至阶段 A 落自有 PostgreSQL;Redis 彻底退出动态权威与写路径
> 交叉引用:[architecture.md](architecture.md)、[design.md](design.md)、[user-stories.md](user-stories.md)、ACM2-10FS1FS8 实施批次)。
状态:ACM2-28 单库权威方向已采纳;ACM2-29 固定槽位方案已撤销
2026-09-08 按当前代码整理。本文只保留有效决策和理由,不作为完成或发布证明
> **2026-09-08 复核入口**:详细证据与重构提案见 [运营航班状态设计评审](review-flight-state-2026-09-08.md)。
> 本轮确认的部署边界:现场提供 Oracle 时使用 Oracle 11g,否则自行部署 PostgreSQL。
> 评审保留单库原子提交方向,但发现 §9 固定资源槽位与本仓库三登机门样例冲突,
> 并指出集合往返、迁移与快照 CAS 的风险。**§9 全宽表·零子表方案已按 v2 设计撤销**,
> 现行权威口径见 [§10 v2 落地](#10-acm2-29-v2-无损明细表方案落地2026-09-08)、
> [flight-state-design-v2.md](flight-state-design-v2.md) 与
> [flight-state-semantics.md](flight-state-semantics.md);§1–§9 保留为历史记录。
## 有效决策
## 1. 问题
| 决策 | 理由与边界 |
|---|---|
| 航班状态、快照代、处理终态和 outbox 同库提交 | 消除 Redis 状态与数据库终态跨库双写窗口。Redis 不参与动态状态权威。 |
| 当前实现为 PostgreSQL;现场供库目标为 Oracle 11g | 一个部署选择一个内部权威库。Oracle 尚未实现完整适配,不是可用配置开关。 |
| 航班标量主表 + 9 张重复明细表 | 保存 10 类集合的顺序、源序号和多条资源/操作;不按展示槽位截断。 |
| 异常对象保留主表前缀列;SRVT/VIPF/MAFL 保留文本列 | 已有载体足够时不新建抽象或关系表;具体读写语义仍需回归证明。 |
| 单活动主泵,事务内 PIPELINE_LOCK 行锁 | 保护局部状态提交;不能代替消息认领、FIFO 和整个实例的协调。 |
| 外部回填、投递按幂等重试处理 | 不使用跨库事务;回填意图同事务持久化是待补齐的目标。 |
| 展示视图只是投影 | 前两个资源用于展示,不能作为全量权威或对拍输入。 |
阶段 A 航班动态权威 = Redis(`flightInfo` hash + `SCHD_GEN`),永续驻留、不落任何关系表
ACM2-12)。FLIGHT_STATE 表原属阶段 B`Decision.kt` 注释即「阶段 B 落 FLIGHT_STATE
同事务」)。要回答的问题:**是否把 FLIGHT_STATE 提前到阶段 A 落自有 PG,作为运营航班的
权威存储。**
不再采用:固定 GATE1/2 等槽位作为权威、超界静默丢弃、“零子表”为架构约束、
“存储模型决定零方言成本”、没有备份前提的 RPO=0 承诺。
## 2. 触发重估的事实
## 历史与当前设计
| # | 事实 | 出处 |
|---|---|---|
| F1 | Redis 全损后,FLOP 报文按 KEEP 语义「航班不存在 → SUCCEEDED 不重试」持续终结——空态被当正常态,**状态损坏持续到下一次完整 DNLD**;且这些报文已回填 `DATE_PROCESSED``ReplayService` 白名单(FAILED/DEAD)无法找回 | US-05 AC3、design.md §1.1 |
| F2 | 当前唯一恢复手段 = 等下一次 DNLD 或人工触发 RQFD(依赖 AODB 外部响应,超时 60s);无本地 durable 副本 | US-08、design.md §3.4 |
| F3 | dev Valkey 虽配 AOF`compose.yaml --appendonly yes`),**生产 Redis 拓扑(持久化策略/副本)未定**;Redis 持久化本就是尽力而为,不构成状态安全边界 | compose.yaml、§8 部署姿态 |
| F4 | PG 已是处理管道硬依赖(PROC_STATE/MSG_EVENT),主泵本就停摆于 PG 不可用——航班状态放 PG **不新增** 系统级 SPOF | design.md §2 |
| F5 | U05(PG 本地事务边界)正在实装;此刻调整事务模型成本最低,切流后迁移要重开 I2/I5 与影子对拍口径 | ACM2-10 U05 |
| F6 | U09gen Redis 内 CAS 协议重设计)存在的根因就是「权威在 Redis、终态在 PG」的跨存储窗口——ACMA-8 v4 原设计 gen 本在 DBACM2-12 迁 Redis 仅因 FLIGHT_STATE 缓做 | design.md §3.4 已知缺口 |
| F7 | Handler 输入视图 `hgetAllFlightInfo()` 每报文全量读 hash,已是 U22–U24 已知缺口;PG 按 FLID 索引读可顺带收敛 | design.md §9 |
- ACM2-28:Redis 权威改为本地关系库原子提交。
- ACM2-29 早期:JSONB → 宽表文本 → 固定槽位;后者被三登机门等样例否定。
- ACM2-29 v2:明细表及唯一写路径落地,但不能据此认定全部端到端和平台验收完成。
- 历史原文保存在 [决策历史](legacy/decision-flight-state-history.md),不作为当前实施指令。
## 3. 选项
### A — 维持缓做 + 运维缓解(ACM2-12 现状)
冷启动空态自动触发 RQFD、生产 Redis 强制 AOF+副本部署要求、Runbook 记录重建流程。
**局限**:F1 的永久损坏窗口依旧存在,缓解只是缩短;恢复依赖外部系统可用性。
### B — PG 镜像(write-behind 灾备副本,非权威)
主泵在 Redis 写成功后异步把变更镜像到 PG,仅供灾难重建。
**局限**:镜像滞后窗口内的 FLOP 效果同样丢失,恢复后仍需 DNLD 修正(与 A 等价的损伤面);
却要付出接近 C 的复杂度(表 + 双表示一致性哨兵 + 重建任务)。性价比最差,仅列出备选。
### C — FLIGHT_STATE 落自有 PG,作为阶段 A 权威(推荐)
航班状态变更并入既有 **PG 事务 2**(与 `MSG_EVENT` 插入、`PROC_STATE→SUCCEEDED` 同事务
原子提交);Redis 退出动态权威写路径。
- 跨存储双写窗口(I2 的 Redis 先写)整体消失;U09 从「Redis Lua 协议重设计」变为
「SQL 版本 CAS + 集成测试」,gen/SCHD_GEN 随 FLIGHT_STATE 回 PG(回到 ACMA-8 v4 形态)。
- Redis 全损场景不再存在:PG 即状态,重启即恢复;F1 的损坏路径被根除。
- Handler 输入改为按 FLID 索引读(F7);US-12 `GET /all/flights` 读 PG。
- 影子对拍沿用 ACM2-12 既有口径(自有 PG 独立 schema 比对),不新增负担。
- 阶段 B 不受影响:清场/判史改为 SQL 驱动,ES 仍是历史投影。
- 性能:机场报文量级(秒级峰值)下单行 JSONB upsert 亚毫秒,且在既有事务内,无新增往返。
**C 的代价**(须如实计入):
- 推翻 ACM2-12 阶段 A 存储口径,I2/I5 不变量重述,U29 不变量测试清单同步。
- `FlightStateRepository` 从 day 粒度(`replaceDay`)重设计为 FLID 粒度 upsert + day 快照
replaceSnapshotFlow 的 Lua 差删改 SQL`redis-flight-store` 健康指示器调整。
- Redis 在阶段 A 角色大幅缩小(仅剩 orms_stand 热点缓存可选),部署面与文档需收口。
- 与 U05/U09/U15 排序重排:先定此决策,再收 U05 事务边界。
## 4. 对比
| 维度 | A 缓做+缓解 | B 镜像 | C PG 权威 |
|---|---|---|---|
| Redis 全损后果 | 状态永久损坏至下次 DNLD | 损坏窗口缩短,仍需 DNLD 修正 | 不存在该场景 |
| 事务模型 | 跨存储双写(I2/U09 复杂度保留) | 双写 + 镜像一致性 | 单库原子(U09 消解为 SQL) |
| 新增范围 | 无 | 表+哨兵+重建 | 表+接口+事务改造(并入 U05/U09 批次) |
| 影子对拍 | 不变 | 需比对三方 | 不变(口径已是 PG schema 比对) |
| 恢复 RTO/RPO | 依赖 AODB 响应 | 本地副本+DNLD 修正 | 重启即恢复,RPO=0 |
## 5. 建议
**推荐 C**。核心理由:F1 的损坏是**永久且不可自动找回**的(报文已回填、不可重放),
选项 A/B 只能缩短窗口不能根除;而 C 的增量成本大部分落在 U05/U09 本就要动的事务边界上,
时机(F5)与代价重合。若评审认为生产 Redis 必然配强持久化+副本且接受 DNLD 重建窗口,
A 是最小代价回退位;B 不建议。
## 6. 定案结论(ACM2-28 决策记录)
| # | 问题 | 定案结论 |
|---|---|---|
| Q1 | 生产 Redis 拓扑 / RPO | **不再阻塞**。Redis 退出权威与写路径,不构成状态安全边界。 |
| Q2 | 表形态 | 表名定案 `FLIGHT_SCHD`。FLID 主键 + FLTR JSONB 全量 + FDAY 所属代列(可空)。派生字段后置。 |
| Q3 | U09 重定义 | 「Redis Lua 协议重设计」**取消**,改为「`SCHD_GEN` SQL 版本 CAS + Testcontainers PG 集成测试」。 |
| Q4 | Redis 去留 | 阶段 A 动态写路径归零;orms_stand 缓存不保留;US-12 读 PG`redis-flight-store` 健康指示器移除。 |
| Q5 | 不变量重述 | I2(变更+事件+SUCCEEDED 单事务提交);I4(覆盖+差删+版本推进单事务);I5(单写者主泵线程,PG advisory lock 保护)。 |
| Q6 | 影子对拍口径 | nextgen PG `FLIGHT_SCHD` vs legacy Redis `flightInfo` 跨存储 AST 递归比对(FS7 工具)。 |
## 7. 落地完成清单(FS1–FS8 全部交付)
- **FS1** 迁移 `V1.1.0__flight_schd.sql`:创建 `FLIGHT_SCHD``SCHD_GEN`TIMESTAMPTZ 规范)。
- **FS2** `FlightSchdRepository` 接口与实装:快照批量写入、增量更新(ON CONFLICT 保留 FDAY)、域化差删、点查、`SCHD_GEN` SQL CAS、历史清场 `deleteByFlids`
- **FS3** `MessageProcessor` 事务 2 扩展:变更写入 + 事件写入 + `SUCCEEDED` 在单 PG 事务原子提交;Handler 视图按 FLID 点查。
- **FS4** `SnapshotFlow` SQL 化:单事务内批处理 upsert + 域内差删 + SQL CAS 推进 + 事件入队 + 终态标记;`JobExecutor` 清场删除闭环。
- **FS5** Redis 退役收口:删除 `FlightRedisClient`、Lua 脚本、`FlightRedisHealthIndicator``REDIS_FLIGHT_INFO` 投影与相关配置。
- **FS6** U29/U09 不变量测试门禁:崩溃幂等无自增、CAS 防并发、ADFT 存活保障、UTC 方言测试、ES 历史清场 5 场景全绿。
- **FS7** 影子对拍 Diff 工具:`FlightStoreDiffTool` 递归 AST 字段归一化比较与已知合法偏离识别。
- **FS8** 文档回改:架构、设计、用户故事与项目规范同步收敛。
## 8. 修订:11g 现场约束下废除 JSON 存储(本节追加,不改写上文定案历史)
> **触发**:现场环境只提供 Oracle 11g(无任何 JSON 能力——无 `JSON_VALUE`、无 `IS JSON` 约束、无 JSON 类型,
> JSON 支持自 12.1.0.2 才引入)。Q2 定案的「FLTR JSONB 全量」存储形态在该约束下不成立:
> 整文档存储丧失库端校验、字段级索引与直接 SQL 可查性,违背「运营航班可被 DBA/运营直接使用」的初衷。
**修订结论**
| # | 问题 | 修订结论 |
|---|---|---|
| R1 | 表形态 | 废除 `FLTR_JSON` JSONB 整文档,改为**一行一航班的宽表**:FLID 主键 + FDAY 代列 + SCHD.FLTR 标量字段列(锚定 `unisysaodbsis.xsd` 契约与 legacy 派生字段 ABDG/LPSDT/ABN)+ 1:N 明细集合序列化文本列。字段即列,天然可索引、可直查。 |
| R2 | 集合字段 | 登机口/柜台/转盘/桥/延误等 1:N 明细(单航班可达 99 条)暂存序列化文本列(`*_TXT`11g 移植为 CLOB);阶段 2/3 Handler 钉死语义后按需升独立子表。 |
| R3 | 值语义 | 全部字段保持 legacy 字符串原样(不做库端类型转换),保证影子对拍逐字段保真;范围查询需要的类型化列(如 SODT→DATE)按查询需求逐列后置提升。 |
| R4 | SCHD_GEN | `FLIDS_JSON` 同步废除,展开为 `SCHD_GEN_FLID(FDAY, FLID)` 行;版本 CAS 语义不变。 |
| R5 | 值机视图 | Handler 在线视图契约由 `flid → FLTR_JSON` 改为 `flid → 字段集`(与 legacy `hgetAllFlightInfo` hash 同构);增量写语义=字段级合并(与 legacy hmset 一致),快照=整体替换。 |
| R6 | 对拍口径 | FS7 Diff 工具改为 PG 宽表列值 vs legacy FLTR JSON 的逐字段归一化比对(数值精度/空值等价规则保留),比原 JSONB AST 比对更精确到字段。 |
**未尽事项(11g 方言移植不在本修订范围,另行决策)**:存储模型已 11g 兼容,但仓储层仍存 PG 方言
`ON CONFLICT` upsert 需改 `MERGE`)、I5 advisory lock 的 11g 替代(`DBMS_LOCK`)、Flyway/驱动
对 11.2 的支持矩阵。若「自有 PG → 现场 11g」成为确定部署形态,需按 ACM2-28 体例开独立决策记录
重开「自有库」平台定案,评估点在方言与并发原语,不在本修订已解决的存储形态。
## 9. ACM2-29 修正落盘:全宽表·零子表(Zero-Subtable Wide Table
R2 的「`*_TXT` 过渡 + 按需升独立子表」经 `docs/legacy/SIS_AODB_RMS-V0.1.md` 报文样例核验与
成都现场(双流/天府)地服实际规律复核,确认为对 XSD 理论上限(maxOccurs="99")的过度设计,
正式废除。16 个明细集合全部收敛为 `FLIGHT_SCHD` 宽表的标准 VARCHAR 标量列或紧凑格式化字串,
零子表、零 CLOB(V1.2.0 迁移落地):
- **平铺标量列**(槽位号即报文序号属性,可按需直接追加普通 B-tree):登机门 `GATE1/2+PGOT/PGCT/GOTM/GCTM/GTYP`
×2 组;值机柜台 `CHKC1..3` 全属性(同柜台分舱复用条目写侧抹平去重);转盘 `BELT1/2` 全属性
(计划时刻 `BPCOT/BPCCT` 专属前缀避免与值机同名);计划机位 `PSST1/2`;离港通道 `CHUT1/2`
(等级/类型 `CHCLS/CHTYP` 专属前缀)。槽位数按现场规律定界(2/3/2/2/2),超界条目按定案丢弃。
- **单值/里程碑标量列**:延误 `DELY_CODE/DELY_STRT/DELY_DURA/DELY_REMC`(业务上任意时刻仅 1 个
有效延误,覆盖语义);靠撤桥 `ABTM_A/ABTM_D`(桥号沿用派生列 ABDG);轮挡 `CHOT_ON/CHOT_OFF`
(机位沿用派生列 STND);异常 `FDIV/FRET/FLAB` 前缀标量列(规范 1:0..1 单值异常)。
- **紧凑 VARCHAR 字串列**(无界/航路型集合,11g VARCHAR2 内联存储,非 CLOB):
`ROUT_PATH/ERUT_PATH`(≤7 站紧凑航路 `"APCD/SCAT/SCDT,..."`);`SRVT_TEXT/VIPF_TEXT/MAFL_TEXT`
(服务/VIP/共享列表条数无 XSD 上限,存 JSON 数组;超长由列约束 fail fast,不静默截断)。
- **消息语义**:运营资源 = 单资源集合级全量快照替换(集合键出现即整集合覆盖,缺槽位置 NULL,
序号属性 "0" 为显式删除标记);外层航班字段仍为字段级增量合并(仅新增/覆盖)。
- **读侧视图**:由平铺列重建 16 个集合键,与 legacy `flightInfo` hash 同构——KAFKA_SCHD 线格式
FLTR JSON 数组)与 FS7 Diff 逐字段比对均依赖该同构视图,存储形态变化不出仓储边界。
已知保真边界:`ABTM/CHOT` 只保留里程碑时刻(资源号沿用 ABDG/STND 派生列);航路空串属性
与缺失属性在紧凑字串中合并为空槽位;`FDIV/FRET/FLAB` 自由文本键名待阶段 2 Handler 钉死。
- **出站序列化**`FlightFieldsJson` 对契约结构字段解析 JSON tree 输出原生数组/对象,
杜绝集合被再次编码成字符串的双重转义。
- **I5 单写者锁修正**:不再依赖 PG advisory lock/11g `DBMS_LOCK`。V1.2.0 创建 `PIPELINE_LOCK`
单行锁表,处理事务对 `FLIGHT_SCHD_WRITER` 行执行 `SELECT ... FOR UPDATE`;连接断开、提交或
回滚时由数据库释放,PG 与 11g 同构,无 DBA 特权依赖。
16 个 `*_TXT` 文本列由 V1.2.0 全部删除;运营查询索引(如 `GATE1/CHKC1/DELY_CODE`)按需以普通
B-tree 逐列追加,11g/PG 零方言成本。
Oracle 11g 的 `MERGE INTO` 方言、Flyway 11.2 支持版本和 ojdbc 认证组合仍必须在目标数据库环境
中完成平台决策与集成验收;不能用 PostgreSQL 测试结果替代该外部环境证据。
## 10. ACM2-29 v2 无损明细表方案落地(2026-09-08)
按 [评审报告](review-flight-state-2026-09-08.md)F1F9)与
[flight-state-design-v2.md](flight-state-design-v2.md) 评审定案,撤销 §9「全宽表·零子表」
作为权威存储形态,落地无损模型;本节为当前有效规则索引:
- **存储**`FLIGHT_SCHD` 保留标量/异常前缀/无界文本列(V1.4.0 已 DROP 槽位、里程碑与紧凑
航路列及冗余索引);10 类重复集合由 9 张明细表 + `flight_route_point(ROUTE_KIND)` 承载,
PK `(FLID, ORDINAL)``SOURCE_SEQ` 不去重不设唯一(v2 §3.2)。
- **写路径**`persistNextStates` 是唯一写路径;引擎产生显式命令
Unchanged/Set/Clear/Replace/Apply),序号 0 条目仅在整集合清除时翻译为 Clear,
混排 fail fast;异常对象 `"null"`/`{}`/空串 = 显式清除置 NULL。
- **事务**PIPELINE_LOCK 行锁内复核快照身份(`schd_gen.last_message_id`)与当前态,
CAS 失败一律回滚 `FAILED(INFRA)`;提交后共享信箱回填失败落 `backfill_todo` 补偿重试,
成功终态不降级(v2 §5)。
- **读模型**:明细表为权威读;`flight_schd_display` 视图提供首/次资源与总数投影。
- **双库**`SqlDialect` 接缝(PG 实装;`Oracle11gDialect` 为编译级交付),
11g 激活门控见 `db/migration/oracle11g/README.md`——现场 11.2 实测通过前不切换。
- **字段语义唯一索引**[flight-state-semantics.md](flight-state-semantics.md)。
当前规则分别由 [航班状态设计](flight-state-design-v2.md)、
[字段语义](flight-state-semantics.md) 和 [管道设计](design.md) 维护。
完成度、发现的问题和下一批实施计划见 [ACM2-29 核查](acm2-29-audit-and-simplification.md)。
+12 -6
View File
@@ -6,6 +6,10 @@
阶段 A 采纳 ACM2-28 选项 C 定案:运营航班权威状态落自有 PostgreSQL(表 `FLIGHT_SCHD``SCHD_GEN`),Redis 彻底退出动态权威与全部写路径。阶段 B 的历史投影和清场暂不启用。
本文的流程是验收目标,当前差异见 §10。航班存储细节统一由
[航班状态设计](flight-state-design-v2.md) 和 [字段语义](flight-state-semantics.md) 维护;
2026-09-08 的代码证据与剩余计划见 [核查报告](acm2-29-audit-and-simplification.md)。
## 2. 数据与领域模型
### 2.1 持久化记录
@@ -19,7 +23,8 @@
| `PUMP_JOB` | 持久化维护作业 | 状态为 `QUEUED / RUNNING / DONE / FAILED`;不与业务消息共用排序序号。 |
| `REQ_TRACK` | 上游请求及应答关联 | 保存请求类型、参数、出站信箱 ID、发送和完成时间;同类只允许一个开放请求。 |
| `REF_MASTER` | 静态参考数据 | `(RTYPE, RKEY)` 唯一,`SOURCE` 记录数据来源。 |
| `FLIGHT_SCHD` | 运营航班当前权威状态(SCHD 快照 + FLOP/ADFT 增量合并),一行一航班宽表 | `FLID` 主键,`FDAY` 所属日代(可空),SCHD.FLTR 标量字段列 + 1:N 明细集合序列化文本列(11g 修订,废除 `FLTR_JSON` JSONB 整文档),`TIMESTAMPTZ` 时间戳。 |
| `FLIGHT_SCHD` | 航班标量及单值异常、3 类文本载荷 | `FLID` 主键,`FDAY` 可空;版本和消息身份用于追踪。10 类重复集合存于 9 张明细表,见航班状态设计。 |
| `BACKFILL_TODO` | 共享信箱回填重试 | 当前失败后落账,终态与待办同事务持久化尚未完成。 |
| `SCHD_GEN` | 日计划代版本(差删依据与版本 CAS) | `FDAY` 主键,`VERSION` 版本号,`TIMESTAMPTZ`。 |
| `SCHD_GEN_FLID` | 当前代有效航班 FLID 集合(原 `FLIDS_JSON` 展开为行) | `(FDAY, FLID)` 主键,`FDAY` 外键级联删除。 |
| `PROC_STATE_HST` | 终态处理记录的归档目标 | 属于目标设计,当前迁移尚未建表;不得改写为共享库历史表。 |
@@ -120,7 +125,7 @@ Handler 不写 Kafka 或数据库。主泵负责应用决策;各类变更在
3. **SQL 原子替换**:在自有 PG 单事务内批处理写入新代 `FLIGHT_SCHD` 全量、按 `FDAY` 域内差删旧代航班,并通过 SQL CAS 推进 `SCHD_GEN` 版本。删除集为“旧代航班集合 − 新代航班集合”,仅作用于属于该代的记录,ADFT(FDAY=NULL)与已迁移航班天然存活。
4. **提交结果**:在同一 PG 事务中保存 `MSG_EVENT`、将消息置为 `SUCCEEDED`,并将匹配 `RESP` 的请求置为 `DONE`;事务提交后执行信箱回填。
单事务保证崩溃后整体回滚,无跨存储中间态;重放时识别版本,绝不发生二次自增
单事务保证未提交变更整体回滚。最近一次快照重放按 `LAST_MESSAGE_ID` 识别版本号不能单独证明消息身份;任意历史快照的重放保护仍需验收
### 4.2 上游请求与静态数据
@@ -212,17 +217,18 @@ REGISTERED → SENT → WAITING → DONE
| 调度与处理 | `processing/Pump.kt`(含 `MessageProcessor`)、`Handler.kt``Identity.kt` |
| 快照与请求 | `processing/SnapshotFlow.kt``reference/RequestCoordinator.kt` |
| 投递与作业 | `delivery/Dispatcher.kt``SchdAggregation.kt``jobs/JobExecutor.kt` |
| 持久化与恢复 | `infra/persistence/``infra/redis/``infra/retry/` |
| 持久化与恢复 | `infra/persistence/``infra/retry/``jobs/BackfillSweepJob.kt` |
| 启停与配置 | `PipelineLifecycle.kt``config/PipelineProps.kt` |
## 10. 当前实现差异
以下缺口直接影响上述设计是否成立,不能以类或接口已存在作为完成依据:
- **事务与外部副作用**ACM2-28 定案后,已完成 `FLIGHT_SCHD` 增量更新与快照全量写入、待发事件与 `PROC_STATE` 在自有 PG 事务原子提交,提交后异步回填信箱
- **事务与外部副作用**状态、outbox 与终态同 PG 事务已实现;提交后尝试回填,失败落 BACKFILL_TODO。提交后崩溃及待办落账再次失败仍有恢复缺口,不能认定补偿闭环
- **收报与调度**:轮询仍从 `afterId=0` 扫描,持久水位与补扫策略未完成;作业以“队头为 FAILED”近似窗口,未区分是否已到期。主泵直取系统时间,滞留判据使用更新时刻,不能保证设计要求的超时升级。
- **快照与业务能力**:快照 SQL 事务化已落地,支持 JDBC 批处理、域内差删与 SQL CAS 推进,超限 10000 熔断保护;忽略规则及所需 Handler 仍需在后续阶段铺开
- **快照与业务能力**:快照事务内核已有测试,但生产 staging parser 未接通;Handler 注册检查在 DNLD 路由之前,生产只有 GTDT Handler。RESP 匹配和其他 Handler 待完成;10000 条保护不等于整包协议校验完成
- **航班读写**:唯一写入口和明细权威读已落地;ROUT/ERUT 主键冲突、空值/未知属性保真、事件重复计算和逐航班多次查询仍需修正。`/all/flights` 尚未实现。
- **请求、静态数据与归档**:请求在真实出站前就标记发送,时间匹配与审计仍需修正;静态数据存在进程内过渡实现,归档表与关联保留策略尚未落地。
- **生产与运维**缺少完整启动校验、运行期单写者保护、影子隔离Kafka 强制配置校验;投递滞留升级、死信告警、端到端追踪与指标未闭环。影子对拍 cross-store diff 工具已就绪
- **生产与运维**已有事务行锁,但没有整个实例的排他/FIFO 协调;启动校验、影子隔离Kafka 强制配置、告警与指标未闭环。对拍比较工具存在不等于现场对拍已完成。Oracle 尚需完整适配
进度与验收项见 Plane ACM2-10 实施计划(U01–U30),业务契约与待确认事项见 [user-stories.md](user-stories.md)。本文件不维护工单流水账、测试数量或历史方案全文。
+132 -163
View File
@@ -1,215 +1,184 @@
# 运营航班状态数据库设计 v2
# 运营航班状态设计
状态:提案,待 ACM2-29 评审确认
日期:2026-09-08
适用部署:现场提供数据库时使用 Oracle 11g;现场不提供时使用自建 PostgreSQL。
状态:PostgreSQL 实现基线,2026-09-08 核对。未完成项在各节明确标注;文件名沿用 v2,兼容历史引用。
## 1. 目标和边界
配套文档:完成度证据见 [ACM2-29 核查与简化计划](acm2-29-audit-and-simplification.md),字段级行为见 [语义矩阵](flight-state-semantics.md),决策理由见 [决策摘要](decision-flight-state.md)。
本设计为 msgexchange-v2 提供一个可恢复、可查询、可审计的运营航班当前状态库。系统接收共享 MySQL 信箱中的 SIS 报文,解析后在自有数据库中形成当前航班状态,并通过 outbox 投递 Kafka 和出站信箱。
## 1. 系统做什么
系统不是 AODB 主数据编辑系统,不负责票务、旅客、机队维护或资源排班优化。上游 AODB/CIIMS 仍是业务报文来源;本系统的“权威”仅表示本系统已成功处理的当前状态和待发送事件
系统从共享 MySQL 信箱接收 SIS 报文,把报文合并成本系统的"航班当前状态",再把变化以事件发出
设计目标
三条底线
- 航班主表、资源明细、快照版本、处理终态和 outbox 在一个本地事务中提交
- 保留 SIS 重复元素的顺序、源序号、资源号和全部属性,不因页面列数或现场样例缩减数据
- 明确区分“字段未出现”“设置值”“显式清除”“集合替换”
- 同一业务模型支持 PostgreSQL 和 Oracle 11g,数据库方言只存在于基础设施适配层。
- 在迁移、重放、回填失败和进程崩溃后,可以判断哪些业务已经提交,避免重复应用。
- **一套权威状态。** 读写都指向本地数据库;共享 MySQL 和 Kafka 是提交之后的外部边界,失败各自重试,不会让已提交的状态回滚或重做
- **一个活动主泵。** 生产同时只有一个实例在处理;事务行锁只保护本地提交,不是多实例协调机制
- **一条提交事务。** 状态、outbox、处理终态在同一个本地事务里落库,要么全成功要么全回滚
## 2. 核心不变量
处理顺序严格按信箱 FIFO,按业务身份去重,失败按退避重试。
1. `FLIGHT_SCHD` 是当前航班状态的唯一写入目标;Redis 不参与动态状态权威
2. 只有主泵写入航班状态和快照成员。跨实例保护使用 `PIPELINE_LOCK` 行锁,但生产仍只允许一个活动实例。
3. 一条消息的航班变更、明细变更、快照版本、outbox 事件和 `PROC_STATE=SUCCEEDED` 同事务提交;任一写入失败全部回滚。
4. 快照差删只删除上一代在同一 `FDAY` 域内的成员;`ADFT` 和已经迁移到其他日期的航班不能被误删。
5. 消息重放不得重复推进版本、重复生成业务事件或重复改变当前态。
6. 合法的重复输入不得静默丢弃。容量不足、未知属性、非法结构必须显式失败并记录原因。
7. 共享 MySQL 的成功回填属于提交后的补偿任务;回填失败不能把已成功的业务事务重新标记为失败。
数据库:现场提供库时目标是 Oracle 11g;当前实际接通的只有 PostgreSQL,本文描述 PG 实现
## 3. 逻辑模型
## 2. 存储:一张主表加九张明细表
```mermaid
erDiagram
FLIGHT_SCHD ||--o{ FLIGHT_GATE : has
FLIGHT_SCHD ||--o{ FLIGHT_CHECKIN : has
FLIGHT_SCHD ||--o{ FLIGHT_BELT : has
FLIGHT_SCHD ||--o{ FLIGHT_STAND_PLAN : has
FLIGHT_SCHD ||--o{ FLIGHT_CHUTE : has
FLIGHT_SCHD ||--o{ FLIGHT_DELAY : has
FLIGHT_SCHD ||--o{ FLIGHT_BRIDGE_OP : records
FLIGHT_SCHD ||--o{ FLIGHT_CHOCK_OP : records
FLIGHT_SCHD ||--o{ FLIGHT_ROUTE_POINT : has
SCHD_GEN ||--o{ SCHD_GEN_FLID : contains
```
### 为什么不用固定槽位
### 3.1 航班主表:`FLIGHT_SCHD`
航班资源是变长集合:登机门通常一两个、值机柜台一到三个、转盘一个,报文规范理论上限 99。ACM2-29 曾定过"全标量宽表、零子表"方案(GATE1/GATE2 式固定槽位),当天撤销——槽位会截断数据,而设计要求保留重复资源的全部属性、输入顺序和源序号。结论:标量进主表,集合进明细表。
一行代表一个 `FLID` 的当前状态。保留现有 SIS 标量字段和派生字段,但不再把重复资源展开为 `GATE1/GATE2` 这类固定槽位。
### 表清单
建议补充:
| 字段 | 含义 |
| 表 | 作用 |
|---|---|
| `FLID` | 本地航班主键;单机场单 AODB 部署下可继续使用。 |
| `FDAY` | DNLD 快照归属日,可为空;不等同于航班业务日期。 |
| `LAST_MESSAGE_ID` | 最后影响该航班的消息 ID,用于审计和恢复判断。 |
| `STATE_VERSION` | 有效状态变更版本;无副作用重放不得推进。 |
| `SODT_RAW` 等 | 上游原始字符串,保证兼容和对拍。 |
| `SCHEDULED_AT` 等 | 按机场业务时区解析后的查询字段。 |
| `CREATED_AT``UPDATED_AT` | 本系统写入时间。 |
| `FLIGHT_SCHD` | 主表,一行一个 FLID:标量字段、异常前缀列、SRVT/VIPF/MAFL_TEXT、FDAY、STATE_VERSION、LAST_MESSAGE_ID、时间戳。 |
| 8 张明细表 | gate、checkin、belt、stand_plan、chute、delay、bridge_op、chock_op;主键 (FLID, ORDINAL)。 |
| `FLIGHT_ROUTE_POINT` | 第 9 张明细表,ROUT/ERUT 两类路线共用,ROUTE_KIND 区分。 |
| `SCHD_GEN` / `SCHD_GEN_FLID` | 日代版本、最后消息身份、当日成员集合;成员表带外键级联删除。 |
| `PIPELINE_LOCK` | 单写者行锁,每个处理事务先锁 FLIGHT_SCHD_WRITER 行。 |
| `BACKFILL_TODO` | 回填待办,见第 4 节。 |
| `FLIGHT_SCHD_DISPLAY` | PG 展示视图(前两个登机门/柜台、首个转盘/延误、资源数),只是投影,不是状态来源。 |
时间解析必须保留原值。SIS 使用本地机场时间格式时,不能直接当作 UTC;机场时区来自配置,解析失败不能静默转换
9 张明细表承载 10 类集合——路线表一张顶两类
### 3.2 重复资源表
三个要知道的事实:
每个明细表至少保留以下通用字段:
1. **明细表没有声明外键**,清理由仓储显式执行,不靠数据库级联;SOURCE_SEQ 可空且不唯一,只是记录——逐条定位更新的 Apply 协议因此不铺开(无生产调用方,定位键不可靠)。
2. **差删按日期圈定范围**:先选出目标 FDAY 的实际成员,再删这些成员的明细和主行;跨日迁移和 FDAY 为空的航班不动。(这里曾有 bug:先删明细再圈主行,导致保留航班的集合被误删;已修复并有真实 PG 回归。)
3. **演进纪律**:不为尚无消费者的查询加类型化时间列、摘要、额外载荷表或索引;时间先保留原始字符串,字段与长度以已应用迁移为准。
### 已知待修
路线表主键仍是 (FLID, ORDINAL):同一航班 ROUT 和 ERUT 各自从序号 1 写起,共存即冲突。计划用新迁移改成 (FLID, ROUTE_KIND, ORDINAL),见 ACM2-30。
## 3. 航班状态的两条来路:请求/下载与动态数据
状态只有两个来源,各成一条线:
- **请求/下载线(基准)**:建立某天航班的基准全量。本系统向 AODB 发日计划请求,AODB 回 SCHD 报文(一整天全部航班),快照整体落库;当天没再出现的航班被差删。
- **动态线(运行事件)**:跟踪运行中的持续变化——实际时间、登机门开关、值机柜台、行李转盘、延误、靠撤桥、轮挡等。FLOP 类事件规范有 29 类,当前已接 GTDT 一类;一条报文只改一个航班的一类资源,其余字段不动。
两条线共用同一个入口和同一套事务/失败规则:进事务后都是"锁内读态 → 合并 → 写库 → 事件入 outbox → 终态 → 提交 → 回填"。
```text
FLID 主表外键
ORDINAL 本次集合中的输入顺序
SOURCE_SEQ 源报文序号,如 GTNO/CKNO/ASNO/CSNO
RECORD_VERSION 产生该记录的状态版本
CREATED_AT
UPDATED_AT
信箱队头 → 解码 → 绑定身份(去重)→ 按 Handler 路由
├─ SCHD-DNLD(已接通)────────→ 请求/下载线
├─ SCHD-RESP / ADFT(未接通)─→ 请求/下载线
└─ FLOP 事件(已接 GTDT)────→ 动态线
```
业务字段按 SIS 原样保存。例如:
### 公共入口(所有消息都走)
| 表 | 主要字段 |
|---|---|
| `FLIGHT_GATE` | `GATE``PGOT``PGCT``GOTM``GCTM``GTYP` |
| `FLIGHT_CHECKIN` | `CHKC``CCLS``PCOT``PCCT``COTM``CCTM``CTYP` |
| `FLIGHT_BELT` | `BELT``BCLS``PCOT``PCCT``FBAG``LBAG``BTYP` |
| `FLIGHT_STAND_PLAN` | `PSST``STST``STET` |
| `FLIGHT_CHUTE` | `CHUT`、等级、计划/实际时刻、类型字段 |
| `FLIGHT_DELAY` | `CODE``STRT``DURA`、原文说明 |
| `FLIGHT_BRIDGE_OP` | `ASNO``ABDG``ABOP``AOTM` |
| `FLIGHT_CHOCK_OP` | `CSNO``CHID``CHST``CHTM` |
| `FLIGHT_ROUTE_POINT` | `ROUTE_KIND``RTNO``APCD``SCAT``SCDT` |
1. **排队**:信箱消息和定时任务共用一个 FIFO 队列,主泵只处理队头;队头失败按退避等待,重试超限或队头滞留超过时限 → DEAD。
2. **解码**:报文非法 → DEAD 不重试;解码器缺陷类错误 → FAILED 退避重试。
3. **绑定身份**:幂等键 = 发送方|类型|子类型|序号,仅首次处理时绑定;已被其他消息占用 → SKIPPED(重复),自身照常回填共享信箱。
4. **路由**:按报文类型找 Handler;没有注册的 Handler → FAILED(可重放,不写终态)。
主键推荐为 `(FLID, ORDINAL)``SOURCE_SEQ` 是源数据,不默认唯一;相同资源号不代表同一条分配记录,因此不能按 `GATE``CHKC``ABDG` 全局去重。
### 请求/下载线:基准全量怎么来
`FDIV``FRET``FLAB` 当前是 XSD 中的单值结构,可以保留在主表的明确前缀列中;必须定义显式清除语义。`SRVT``VIPF``MAFL` 如暂时不需要 SQL 查询,可使用专用集合载荷表保存原始文本;Oracle 11g 使用 `CLOB`PostgreSQL 使用 `TEXT`,应用层负责结构校验。
SIS 循环:本系统发日计划请求 → AODB 回 SCHD。SCHD 三个子类型载荷结构相同:
### 3.3 快照版本
- **DNLD**:AODB 定时下发的时刻表下载(当前唯一接通的)。
- **RESP**:应答本系统的日计划请求。
- **ADFT**:临时/加班航班,单条航班记录。
```text
SCHD_GEN
FDAY 主键
VERSION 单调递增版本
LAST_MESSAGE_ID 生成该版本的消息
CONTENT_DIGEST 可选,内容摘要
UPDATED_AT
请求登记在 REQ_TRACK(同类并发=1,新请求把旧请求强制置 EXPIRED),出站经 COUTMSGS outbox——发送与超时重发尚未接线。
SCHD_GEN_FLID
FDAY
FLID
PRIMARY KEY (FDAY, FLID)
```
收到下载后的落库步骤:
`VERSION` 只是顺序令牌;重放判断必须结合 `LAST_MESSAGE_ID` 或明确的快照身份,不能只比较 `VERSION == expected + 1`
1. 解析整包并校验,单包上限 10000 个航班(超限 DEAD);真实解析未接通,测试靠注入 staging 数据
2. 进事务(先锁 PIPELINE_LOCK 行):
- **重放短路**:该消息已提交过(日代 LAST_MESSAGE_ID 相同)→ 直接 SUCCEEDED,不加版本、不发事件。
- **算成员差**:新快照里消失的航班进差删名单。
- **合并写入**:锁内逐航班读当前态 → 合并 → 整集合替换,FDAY 置为快照日期。
- **差删**:删差删名单航班的明细和主行(规则见第 2 节)。
- **CAS 推进日代版本**:版本不符回滚 → FAILED(INFRA) 退避。
- **应答匹配**:有待答的 SCHD 请求则标记完成。
- 每个航班一条 KAFKA_SCHD 事件入 outboxPROC_STATE → SUCCEEDED;提交。
3. 提交后回填共享信箱(失败落 BACKFILL_TODO,见第 4 节);重放短路同样要回填。
### 3.4 处理和投递表
现状:只有 DNLD 被路由进这条流程;RESP 和 ADFT 还没有 Handler,进来会记 FAILED(UNSUPPORTED)——"请求 → 应答 → 标记完成"的闭环待接通。参考数据请求(静态主数据 21 类)用同一张 REQ_TRACK,但应答落参考库、由 RequestCoordinator 处理,与航班状态无关。
保留 `PROC_STATE``MSG_EVENT``PUMP_JOB``REQ_TRACK` 等现有管道表。建议在 `MSG_EVENT` 中记录 `CMINMSGS_ID``STATE_VERSION``IDEMPOTENCY_KEY`,便于确认同一业务事件是否已经入队。
### 动态线:运行事件怎么改状态(以 GTDT 为例)
## 4. 消息写入语义
1. **Handler 纯函数决策**:输入 = 受影响航班的当前态 + 报文,输出 = 字段变更和待发事件;这一步不算账、不落库。每类资源的语义在 Handler 里固定,如 GTDT 是登机门集合整体替换(GTNO=0 表示清除)。
2. 进事务(先锁 PIPELINE_LOCK 行):
- 每个受影响航班:锁内重读最新态 → 合并字段变更 → 整集合替换写入,保留已有 FDAY。
- 事件(KAFKA_MSG 通知 + KAFKA_SCHD 状态推送)入 outboxPROC_STATE → SUCCEEDED;提交。
3. 提交后回填共享信箱(同上)。
解码层把 XML 转换为显式命令,仓储层不再猜测字符串含义:
### 状态变更的实现(两条线共用)
```text
Unchanged 报文没有该字段,不修改数据库
Set(value) 写入或覆盖值
Clear 按协议删除该值
Replace(list) 用完整集合替换旧集合
Apply(item, key) 按协议中的源序号更新单条记录
```
所有状态变更都走同一套加工链:**报文 → 命令 → 合并出新状态 → 写库**。
通用规则:
#### 数据的统一形态
- 外层标量字段默认是字段级增量合并:未出现不修改,出现则覆盖
- DNLD 对航班集合使用完整快照替换;集合缺失表示“按 DNLD 规则处理”,不能自动推广为清空所有旧资源。
- 集合出现且被协议定义为快照时,在同一事务中删除该航班该集合的旧明细,再批量写入新明细。
- `GTNO=0``CKNO=0` 等清除标记只对协议明确规定的集合生效。
- 非法 JSON、未知字段、超长文本和超过协议容量的输入不得截断后成功。
内存里一个航班的状态就是一个模型(FlightNextState):一组标量键值 + 10 类集合(每类是一组条目,每条目一组键值)+ 版本/消息追踪字段。主表存标量,明细表存集合,列映射固定(如 GTDT 的 GATE/GOTM/GCTM → flight_gate 对应列);读回时按 ORDINAL 组装集合,DB 权威态 = 主行 + 明细行
成功提交后的出站载荷必须从同一份 `nextState` 生成,不能让仓储先丢字段、事件再使用原始输入。
#### 第一步:报文变成命令
## 5. 事务流程
`commandsFromFields` 把 Handler 解出的字段集翻成命令,规则:
```text
解析并校验报文(锁外)
取得 PIPELINE_LOCK 行锁
复核消息状态和快照身份
读取当前航班与 SCHD_GEN
计算 nextState
写主表、明细表、快照成员和版本
从 nextState 生成 MSG_EVENT
PROC_STATE = SUCCEEDED
提交
补偿回填共享 MySQL / 投递下游
```
- **字段没出现 = 不动**。增量报文只带变化的部分,其余不碰。
- **标量出现 = Set**(覆盖)。异常键(FDIV/FRET/FLAB)载荷为空串、null、空对象时 = Clear。
- **集合出现 = Replace**(整组替换为报文给的条目);整组条目序号属性为 0(如 GTNO=0)= Clear(显式清除);清除标记和正常条目混在一组直接拒绝,不猜。
- DELY 没有协议序号属性,不支持逐条 Apply;清除走空数组替换。
CAS 失败直接回滚并进入可重试的 `FAILED(INFRA)`;不能用相同版本号推断“已经是我的提交”。
#### 第二步:命令合并进当前态(纯函数)
提交之后的回填失败只创建或更新补偿记录。重启时先检查 `PROC_STATE` 和 outbox,已经成功的消息不能重新执行航班业务变更
`FlightStateEngine.apply` 从当前态拷贝一份,逐条应用命令:Set 覆盖键值,Clear 删键并记入 clearedKeysReplace 整组换,Clear 删组。然后版本 +1、记 LAST_MESSAGE_ID,产出 FlightNextState。纯函数:不碰库、不发事件,同样输入永远同样输出
## 6. PostgreSQL 与 Oracle 11g
#### 第三步:新状态写进库(唯一写入口 persistNextStates
两种数据库使用相同的仓储接口、领域模型和测试样例,分开提供迁移和 SQL 实现
- **主表标量**:快照线整行替换(FDAY=快照日期);动态线只 UPDATE 本次出现的列,clearedKeys 对应的物理列显式置 NULL,新航班先插一行(FDAY 为空)
- **明细集合**:每次写航班,9 张明细表对该 FLID 先 DELETE 再按合并后的集合整组 INSERT——即使某个集合本次没变也重写,保证库里与内存状态严格一致。这就是"整集合替换"的落地:ORDINAL 按输入顺序 1..NSOURCE_SEQ 存协议序号,RECORD_VERSION 记写入时的版本。
- last_message_id / state_version / updated_at 一并更新。
| 能力 | PostgreSQL | Oracle 11g |
|---|---|---|
| Upsert | `INSERT ... ON CONFLICT` | `MERGE` 或受控的 UPDATE/INSERT |
| 长文本 | `TEXT` | `CLOB` |
| 时间 | `TIMESTAMPTZ` | `TIMESTAMP` 配合明确时区转换策略 |
| 自增 ID | identity/sequence | sequence |
| 锁 | `SELECT ... FOR UPDATE` | `SELECT ... FOR UPDATE` |
| 迁移 | PG 专用 Flyway location | Oracle 11g 专用 Flyway location,经目标版本实测 |
#### 事件从状态序列化而来
Oracle 11g 的 `VARCHAR2` 受 4000 字节限制,空字符串按 `NULL` 处理。因此应用命令必须保留 presence/operation 信息,不能把“缺失、空串、NULL、清除”当作同一个状态。JDK 25、Oracle 11.2 补丁、JDBC 驱动、Flyway 和连接池的组合必须在现场环境实测,不能用 PostgreSQL 通过代替 Oracle 验收
KAFKA_SCHD 载荷 = FlightNextState 序列化(FlightFieldsJson):键按字典序输出保证同状态字节级稳定;集合键输出为真 JSON 数组/对象,不做双重字符串编码。快照线的事件与落库用同一份事务内状态;动态线的事件目前来自 Handler 锁外预览(见已知偏差)
## 7. 迁移和发布
### 失败规则(两条线共用)
1. 先增加明细表和主表追踪字段,不删除现有集合列
2. 从旧列、完整 DNLD 或可信历史输入回填明细表。
3. 逐航班比较数量、顺序、源序号、属性、空值和出站载荷。
4. 切换读路径和写路径到无损模型,保留旧结构一个回退窗口。
5. 确认对拍、回放、备份恢复和查询性能通过后,再删除旧的损失性列和转换代码。
事务内任何一步失败 → 整体回滚,消息记 FAILED 退避重试。已提交的成功不受后续失败影响:回填和投递失败只落补偿待办,不降级 SUCCEEDED,不重放业务
已经执行过直接删除旧集合列的实例无法从新槽位列推回第三个登机门、原始序号或多次操作;需要旧库备份、完整 DNLD 或原始报文恢复。
### 不变量(两条线共用)
## 8. 验收标准
- **状态、事件、终态同事务**:库里能看到的必是完整提交结果。
- **计算是合并不是覆盖**:报文没提的字段一律不动(加工链见上节)。快照更新 FDAY,动态线保留 FDAY,新行为空。
- **快照重放只有一道防线**LAST_MESSAGE_ID 识别最近一次快照的重放;更早的历史快照重入是否安全,靠人工重放的版本/业务日期策略。
- 字段缺失、清除、空数组、异常对象的精确行为见语义矩阵。
- 三个及以上登机门、非连续源序号、相同资源号不同属性都能完整读回。
- 多次靠桥/撤桥、上/下轮挡和多条延误记录不丢失顺序或资源身份。
- 未出现、显式清空、集合替换、序号删除四种语义有独立测试。
- 主表、明细、`SCHD_GEN`、outbox 和成功终态原子提交;任意一步失败全部回滚。
- 同一快照重放不增加版本和事件;不同快照争用同一版本时只能一个成功。
- 共享信箱回填失败不会重新执行业务状态变更。
- PostgreSQL 与 Oracle 11g 各自通过新建库、升级库、回滚和并发锁测试。
- 查询视图可以提供首个/第二个资源和总数,但完整明细仍可查询。
- 对拍工具按字段、顺序、空值和集合内容比较,不通过忽略差异来“洗绿”。
### 已知偏差(待收敛)
## 9. 实施顺序
GTDT Handler 在事务外算一遍预览并构造事件,主泵在事务里再算一遍。目标是 Handler 只表达业务变化,事务内算出唯一一份 nextState,落库和 KAFKA_SCHD 共用——尚未实现。
| 阶段 | 内容 |
|---|---|
| P0 | 修复快照 CAS、提交后回填状态边界,建立 16 类结构的语义矩阵。 |
| P1 | 建立无损明细表、领域命令和 nextState 生成;完成 DNLD 与一个 FLOP 纵向链路。 |
| P2 | 完成旧数据回填、双读对拍和兼容查询视图。 |
| P3 | 分别完成 PostgreSQL 和 Oracle 11g 的 DDL/DML、迁移和集成测试。 |
| P4 | 根据真实查询计划增加索引,确认回退窗口关闭后删除旧损失性结构。 |
## 4. 提交之后:回填与事件
本设计对应 ACM2-29 的后续实现;在评审确认前,不应将当前 V1.2.0 固定槽位结构作为生产保真方案
提交成功后有两件外部事:回填共享 MySQL 信箱、异步投递 Kafka,各自独立重试
回填恢复靠一条持久待办:
- 现状:只在回填失败后写 `BACKFILL_TODO`;提交后立即崩溃的窗口没有待办,恢复不了。
- 目标:在业务终态同一事务里登记回填意图,提交后回填成功再删待办。崩溃自然有账可查,不需要额外的"重启对账框架"。
- 红线:登记待办再失败,不能把已提交的 SUCCEEDED 降级为 FAILED。
## 5. 怎么读
现在读一个航班 = 主行 1 次 + 10 类集合各 1 次,全量约 1+10N 次查询;多次读不在同一数据库快照,主子状态不保证一致。
后续在仓储边界按 FLID 批次加载明细,并明确一致性读事务;不引入缓存权威或通用查询框架。`GET /all/flights` 仍是 Controller TODO——仓储方法和展示视图存在不等于接口已交付。
KAFKA_SCHD 输出结构化 JSON,集合禁止双重编码成字符串。清除的 wire 表达、空数组、未知属性、异常局部替换仍有验收缺口,见核查报告;一个全字段样例通过不等于整个 SIS 协议无损。
## 6. Oracle:一整个工作项,不是"等环境"
PG 是唯一实现:SqlDialect 只覆盖两条主行 upsertOracle11gDialect 是没接线的模板(绑定顺序不兼容、MERGE 列数和占位符对不上);迁移目录只有 README,驱动、其余 SQL、视图、目标库测试都没写。
口径因此是"完整适配并验收":以一条跑通的仓储纵向链定接口,处理 DDL、绑定、空字符串、长文本,再上目标 11.2 实测。入口见 [Oracle 迁移说明](../src/main/resources/db/migration/oracle11g/README.md)。
## 7. 老数据升级:脚本能跑不等于数据无损
V1.1→V1.4V1.2 删 *_TXTV1.3 建空明细表,V1.4 删旧槽位列——旧集合数据没有任何自动恢复链路。带数据的实例升级前必须:导出/备份 → 选完整 DNLD 或可信原文恢复 → 逐字段核对后再切换。已发布迁移不改(不靠改历史隐藏损失),也别指望从已删槽位找回数据。
## 8. 现状
已验证:PG 上的事务、部分集合往返、CAS、行锁、脚本升级,101 个测试全绿。
未完成:真实 DNLD/RESP 解析与生产路由、`/all/flights`、全字段与空值无损往返、提交后崩溃恢复、带数据无损迁移、查询规模证据、Oracle 实测、现场对拍。分项证据在核查报告与 Plane,本文不复述工单流水账。
+15 -14
View File
@@ -1,12 +1,12 @@
# 运营航班字段语义矩阵(v2 权威口径)
状态:定稿(ACM2-29 P2-5 收口,原 P0-A 交付物)。
依据:[flight-state-design-v2.md](flight-state-design-v2.md) §3/§4/§9、[SIS_AODB_RMS-V0.1.md](legacy/SIS_AODB_RMS-V0.1.md)、`FlightStateEngine` 实现。
本文是「当前有效规则」的唯一索引;decision-flight-state.md 中历史口径与此冲突时以本文为准
状态:2026-09-08 实现核对;以下明确区分当前行为与待验收语义。
依据:[航班状态设计](flight-state-design-v2.md)、[SIS 规范](legacy/SIS_AODB_RMS-V0.1.md)、`FlightStateEngine` 实现。
本文是字段语义入口;历史决策不覆盖本文。已发现的不一致和修正计划见 [核查报告](acm2-29-audit-and-simplification.md)
## 1. 命令模型
解码/Handler 层产生显式命令,仓储不做字符串猜测
当前 Handler 返回字段集,FlightStateEngine 再解析为命令;仓储仍会解析异常/文本 JSON。显式命令尚未贯通所有边界
| 命令 | 标量 | 集合 |
|---|---|---|
@@ -14,11 +14,11 @@
| Set(value) | 覆盖 | — |
| Clear | 删除该键,物理列置 NULL | 删除全部明细行 |
| Replace(list) | — | 同事务 DELETE + 批量 INSERT 整集合替换 |
| Apply(item, sourceSeq) | — | SOURCE_SEQ 定位更新,缺失则追加 |
| Apply(item, sourceSeq) | — | 引擎内预留,按首个 SOURCE_SEQ 匹配更新,则追加;无生产调用方,尚非已确认业务协议。 |
- 序号属性为 `"0"` 的条目是协议显式清除标记:**仅当集合内全部条目均为 0** 时翻译为 Clear;与常规条目混排属非法结构,fail fast 拒绝。
- 空数组 `[]` = Replace(空集):明细行清空(区别于 Unchanged 的键缺失)。
- 非法 JSON、未知形状、超容输入不得截断后成功。
- 验收目标:非法 JSON、未知形状、超容输入不得截断后成功。当前 parseCollection 未校验数组元素必须是对象,仓储会忽略未知属性;该目标尚未完成。
## 2. 16 类结构映射
@@ -29,7 +29,7 @@
| CLDT 行李转盘 | `flight_belt` | CLNO | Replace | CLNO=0 全清 | 支持 |
| PSDT 计划机位 | `flight_stand_plan` | PSNO | Replace | PSNO=0 全清 | 支持 |
| CHDT 行李滑槽 | `flight_chute` | CHNO | Replace | CHNO=0 全清 | 支持 |
| DELY 延误 | `flight_delay` | 无(DLNO 非协议属性) | Replace(当前单有效延误 + 历史保留) | `[]` 清空 | **不支持**(协议无定位键;Apply 对 DELY 拒绝 |
| DELY 延误 | `flight_delay` | 无(仓储残留 DLNO 映射待清理) | Replace(仅保存本次集合,不追加历史) | `[]` 清空 | 不支持(协议无定位键 |
| ABTM 靠撤桥 | `flight_bridge_op` | ASNO | Replace(操作全集) | ASNO=0 全清 | 支持(ASNO 定位;多次靠/撤桥各占一行) |
| CHOT 轮挡 | `flight_chock_op` | CSNO | Replace | CSNO=0 全清 | 支持(CHID=ON/OFF 为业务属性,非序号) |
| ROUT 计划航路 | `flight_route_point(route_kind=ROUT)` | RTNO | Replace | RTNO=0 全清 | 支持 |
@@ -37,24 +37,25 @@
| FDIV 备降 | 主表前缀列 `FDIV_*` | — | Set(对象) | `"null"`/`{}`/空串 → Clear | — |
| FRET 返航 | 主表前缀列 `FRET_*` | — | Set | 同上 | — |
| FLAB 中止 | 主表前缀列 `FLAB_*` | — | Set | 同上 | — |
| SRVT 服务 | 主表 `SRVT_TEXT`(无损 JSON 文本) | — | Set | 同异常(Clear 置 NULL | — |
| VIPF 贵宾 | 主表 `VIPF_TEXT` | — | Set | 同 | — |
| MAFL 共享航班 | 主表 `MAFL_TEXT` | — | Set | 同上 | 一旦出现关联查询需求,升级为显式关系表(v2 §8.2) |
| SRVT 服务 | 主表 `SRVT_TEXT` | — | Set | 当前字段转换器不将 null/{}/空串转 Clear;显式 ScalarCommand.Clear 可清列 | — |
| VIPF 贵宾 | 主表 `VIPF_TEXT` | — | Set | 同 SRVT | — |
| MAFL 共享航班 | 主表 `MAFL_TEXT` | — | Set | 同 SRVT | 无查询需求时维持现有载体 |
公共明细列:`FLID` FK、`ORDINAL`本集合内输入顺序,1 起)、`SOURCE_SEQ`源序号字符串,不强转整数、不唯一)、`RECORD_VERSION``CREATED_AT/UPDATED_AT`PK `(FLID, ORDINAL)`。相同资源号不代表同一条分配记录,**禁止按资源号去重**
公共明细列:`FLID`(逻辑归属,当前没有数据库 FK`ORDINAL`(输入顺序,1 起)、`SOURCE_SEQ`字符串、不唯一)、`RECORD_VERSION``CREATED_AT/UPDATED_AT`当前 PK `(FLID, ORDINAL)`路线表还需将 ROUTE_KIND 纳入主键,避免 ROUT/ERUT 冲突。相同资源号不代表同一条分配记录,禁止按资源号去重。表中“支持 Apply”仅指引擎分支,生产只生成 Replace/Clear
## 3. 清除的库内与线上表达
- 库内:标量/异常/文本键 Clear → 对应列 `NULL`;集合键 Clear/Replace(空) → 明细行删除。
- 线上(KAFKA_SCHD):被清除的键**缺失**(不输出 `"[]"`/`"null"`)。下游契约如需显式清除表达(如 `"GTDT":[]`),由阶段 2 Handler 按下游契约决定,不在存储层擅自改变线格式(v2 §9.1)
- 线上(KAFKA_SCHD):Clear 会移除键;Replace(空集) 当前仍生成 `[]`,数据库读回却缺键,尚未统一。下游清除契约必须固定黄金样例,再同时修正 nextState、落库读回与 wire;不得宣称已等值
- 异常对象 Set 在增量 SQL 中只写出现的属性;旧对象被省略的属性可能残留。这与对象替换目标不一致,待回归修正。
## 4. 快照(DNLD)与增量(FLOP/ADFT
- DNLD:外层标量字段级合并;集合为整包快照 Replace;集合缺失 = Unchanged(不自动清空旧资源)。删除航班`SCHD_GEN` 差集 + `deleteDiffByDay`FDAY 域化ADFT/跨代迁移行受保护)
- DNLD 事务内核:外层标量字段级合并;出现的集合 Replace;集合缺失 = Unchanged。删除走 `SCHD_GEN` 差集 + `deleteDiffByDay`FDAY 域化)。真实 XML parser/生产路由仍未接通,不能视为端到端协议已验收
- FLOP:字段级合并;Handler 产生的集合键出现即全量替换。
- 重放判定凭 `schd_gen.last_message_id`(快照)与 `identity_key`(消息去重);版本号只是顺序令牌。锁内 CAS 失败一律回滚 `FAILED(INFRA)`
## 5. 时区与空值
- SIS 时间串(`DDMONYYHHMM` 机场当地时)原值保存;类型化提升按查询需要另行定案(ACM2-29 遗留 3)。
- PG 保存空字符串;Oracle 11g 空串按 NULL——命令层保留 presence 信息,清除一律走显式 Clear 命令,不把「缺失、空串、NULL」混同(v2 §6)
- PG 当前保留字符串空值,集合 JSON null 属性会被解析为空串。Oracle 的空字符串/NULL 等价性还未在目标库验证;显式命令本身不能保证持久化往返保真
@@ -0,0 +1,186 @@
# 历史存档说明
以下是整理前的决策原文,包含已撤销方案和当时的交付声明;现行结论见 [决策摘要](../decision-flight-state.md)。
# 决策:运营航班表 FLIGHT_SCHD 落自有 PostgreSQL 作阶段 A 权威(已定案 · 采纳选项 C)
> **状态**:已定案(讨论 issue**ACM2-28**,采纳选项 C;关联修订 ACM2-12)。
> 本文正式确立选项 C 为最终方案:运营航班权威 `FLIGHT_SCHD` 与代版本 `SCHD_GEN` 提前至阶段 A 落自有 PostgreSQL;Redis 彻底退出动态权威与写路径。
> 交叉引用:[architecture.md](../architecture.md)、[design.md](../design.md)、[user-stories.md](../user-stories.md)、ACM2-10FS1FS8 实施批次)。
> **2026-09-08 复核入口**:详细证据与重构提案见 [运营航班状态设计评审](../review-flight-state-2026-09-08.md)。
> 本轮确认的部署边界:现场提供 Oracle 时使用 Oracle 11g,否则自行部署 PostgreSQL。
> 评审保留单库原子提交方向,但发现 §9 固定资源槽位与本仓库三登机门样例冲突,
> 并指出集合往返、迁移与快照 CAS 的风险。**§9 全宽表·零子表方案已按 v2 设计撤销**,
> 现行权威口径见 [§10 v2 落地](#10-acm2-29-v2-无损明细表方案落地2026-09-08)、
> [flight-state-design-v2.md](../flight-state-design-v2.md) 与
> [flight-state-semantics.md](../flight-state-semantics.md);§1–§9 保留为历史记录。
## 1. 问题
阶段 A 航班动态权威 = Redis(`flightInfo` hash + `SCHD_GEN`),永续驻留、不落任何关系表
ACM2-12)。FLIGHT_STATE 表原属阶段 B`Decision.kt` 注释即「阶段 B 落 FLIGHT_STATE
同事务」)。要回答的问题:**是否把 FLIGHT_STATE 提前到阶段 A 落自有 PG,作为运营航班的
权威存储。**
## 2. 触发重估的事实
| # | 事实 | 出处 |
|---|---|---|
| F1 | Redis 全损后,FLOP 报文按 KEEP 语义「航班不存在 → SUCCEEDED 不重试」持续终结——空态被当正常态,**状态损坏持续到下一次完整 DNLD**;且这些报文已回填 `DATE_PROCESSED``ReplayService` 白名单(FAILED/DEAD)无法找回 | US-05 AC3、design.md §1.1 |
| F2 | 当前唯一恢复手段 = 等下一次 DNLD 或人工触发 RQFD(依赖 AODB 外部响应,超时 60s);无本地 durable 副本 | US-08、design.md §3.4 |
| F3 | dev Valkey 虽配 AOF`compose.yaml --appendonly yes`),**生产 Redis 拓扑(持久化策略/副本)未定**;Redis 持久化本就是尽力而为,不构成状态安全边界 | compose.yaml、§8 部署姿态 |
| F4 | PG 已是处理管道硬依赖(PROC_STATE/MSG_EVENT),主泵本就停摆于 PG 不可用——航班状态放 PG **不新增** 系统级 SPOF | design.md §2 |
| F5 | U05(PG 本地事务边界)正在实装;此刻调整事务模型成本最低,切流后迁移要重开 I2/I5 与影子对拍口径 | ACM2-10 U05 |
| F6 | U09gen Redis 内 CAS 协议重设计)存在的根因就是「权威在 Redis、终态在 PG」的跨存储窗口——ACMA-8 v4 原设计 gen 本在 DBACM2-12 迁 Redis 仅因 FLIGHT_STATE 缓做 | design.md §3.4 已知缺口 |
| F7 | Handler 输入视图 `hgetAllFlightInfo()` 每报文全量读 hash,已是 U22–U24 已知缺口;PG 按 FLID 索引读可顺带收敛 | design.md §9 |
## 3. 选项
### A — 维持缓做 + 运维缓解(ACM2-12 现状)
冷启动空态自动触发 RQFD、生产 Redis 强制 AOF+副本部署要求、Runbook 记录重建流程。
**局限**:F1 的永久损坏窗口依旧存在,缓解只是缩短;恢复依赖外部系统可用性。
### B — PG 镜像(write-behind 灾备副本,非权威)
主泵在 Redis 写成功后异步把变更镜像到 PG,仅供灾难重建。
**局限**:镜像滞后窗口内的 FLOP 效果同样丢失,恢复后仍需 DNLD 修正(与 A 等价的损伤面);
却要付出接近 C 的复杂度(表 + 双表示一致性哨兵 + 重建任务)。性价比最差,仅列出备选。
### C — FLIGHT_STATE 落自有 PG,作为阶段 A 权威(推荐)
航班状态变更并入既有 **PG 事务 2**(与 `MSG_EVENT` 插入、`PROC_STATE→SUCCEEDED` 同事务
原子提交);Redis 退出动态权威写路径。
- 跨存储双写窗口(I2 的 Redis 先写)整体消失;U09 从「Redis Lua 协议重设计」变为
「SQL 版本 CAS + 集成测试」,gen/SCHD_GEN 随 FLIGHT_STATE 回 PG(回到 ACMA-8 v4 形态)。
- Redis 全损场景不再存在:PG 即状态,重启即恢复;F1 的损坏路径被根除。
- Handler 输入改为按 FLID 索引读(F7);US-12 `GET /all/flights` 读 PG。
- 影子对拍沿用 ACM2-12 既有口径(自有 PG 独立 schema 比对),不新增负担。
- 阶段 B 不受影响:清场/判史改为 SQL 驱动,ES 仍是历史投影。
- 性能:机场报文量级(秒级峰值)下单行 JSONB upsert 亚毫秒,且在既有事务内,无新增往返。
**C 的代价**(须如实计入):
- 推翻 ACM2-12 阶段 A 存储口径,I2/I5 不变量重述,U29 不变量测试清单同步。
- `FlightStateRepository` 从 day 粒度(`replaceDay`)重设计为 FLID 粒度 upsert + day 快照
replaceSnapshotFlow 的 Lua 差删改 SQL`redis-flight-store` 健康指示器调整。
- Redis 在阶段 A 角色大幅缩小(仅剩 orms_stand 热点缓存可选),部署面与文档需收口。
- 与 U05/U09/U15 排序重排:先定此决策,再收 U05 事务边界。
## 4. 对比
| 维度 | A 缓做+缓解 | B 镜像 | C PG 权威 |
|---|---|---|---|
| Redis 全损后果 | 状态永久损坏至下次 DNLD | 损坏窗口缩短,仍需 DNLD 修正 | 不存在该场景 |
| 事务模型 | 跨存储双写(I2/U09 复杂度保留) | 双写 + 镜像一致性 | 单库原子(U09 消解为 SQL) |
| 新增范围 | 无 | 表+哨兵+重建 | 表+接口+事务改造(并入 U05/U09 批次) |
| 影子对拍 | 不变 | 需比对三方 | 不变(口径已是 PG schema 比对) |
| 恢复 RTO/RPO | 依赖 AODB 响应 | 本地副本+DNLD 修正 | 重启即恢复,RPO=0 |
## 5. 建议
**推荐 C**。核心理由:F1 的损坏是**永久且不可自动找回**的(报文已回填、不可重放),
选项 A/B 只能缩短窗口不能根除;而 C 的增量成本大部分落在 U05/U09 本就要动的事务边界上,
时机(F5)与代价重合。若评审认为生产 Redis 必然配强持久化+副本且接受 DNLD 重建窗口,
A 是最小代价回退位;B 不建议。
## 6. 定案结论(ACM2-28 决策记录)
| # | 问题 | 定案结论 |
|---|---|---|
| Q1 | 生产 Redis 拓扑 / RPO | **不再阻塞**。Redis 退出权威与写路径,不构成状态安全边界。 |
| Q2 | 表形态 | 表名定案 `FLIGHT_SCHD`。FLID 主键 + FLTR JSONB 全量 + FDAY 所属代列(可空)。派生字段后置。 |
| Q3 | U09 重定义 | 「Redis Lua 协议重设计」**取消**,改为「`SCHD_GEN` SQL 版本 CAS + Testcontainers PG 集成测试」。 |
| Q4 | Redis 去留 | 阶段 A 动态写路径归零;orms_stand 缓存不保留;US-12 读 PG`redis-flight-store` 健康指示器移除。 |
| Q5 | 不变量重述 | I2(变更+事件+SUCCEEDED 单事务提交);I4(覆盖+差删+版本推进单事务);I5(单写者主泵线程,PG advisory lock 保护)。 |
| Q6 | 影子对拍口径 | nextgen PG `FLIGHT_SCHD` vs legacy Redis `flightInfo` 跨存储 AST 递归比对(FS7 工具)。 |
## 7. 落地完成清单(FS1–FS8 全部交付)
- **FS1** 迁移 `V1.1.0__flight_schd.sql`:创建 `FLIGHT_SCHD``SCHD_GEN`TIMESTAMPTZ 规范)。
- **FS2** `FlightSchdRepository` 接口与实装:快照批量写入、增量更新(ON CONFLICT 保留 FDAY)、域化差删、点查、`SCHD_GEN` SQL CAS、历史清场 `deleteByFlids`
- **FS3** `MessageProcessor` 事务 2 扩展:变更写入 + 事件写入 + `SUCCEEDED` 在单 PG 事务原子提交;Handler 视图按 FLID 点查。
- **FS4** `SnapshotFlow` SQL 化:单事务内批处理 upsert + 域内差删 + SQL CAS 推进 + 事件入队 + 终态标记;`JobExecutor` 清场删除闭环。
- **FS5** Redis 退役收口:删除 `FlightRedisClient`、Lua 脚本、`FlightRedisHealthIndicator``REDIS_FLIGHT_INFO` 投影与相关配置。
- **FS6** U29/U09 不变量测试门禁:崩溃幂等无自增、CAS 防并发、ADFT 存活保障、UTC 方言测试、ES 历史清场 5 场景全绿。
- **FS7** 影子对拍 Diff 工具:`FlightStoreDiffTool` 递归 AST 字段归一化比较与已知合法偏离识别。
- **FS8** 文档回改:架构、设计、用户故事与项目规范同步收敛。
## 8. 修订:11g 现场约束下废除 JSON 存储(本节追加,不改写上文定案历史)
> **触发**:现场环境只提供 Oracle 11g(无任何 JSON 能力——无 `JSON_VALUE`、无 `IS JSON` 约束、无 JSON 类型,
> JSON 支持自 12.1.0.2 才引入)。Q2 定案的「FLTR JSONB 全量」存储形态在该约束下不成立:
> 整文档存储丧失库端校验、字段级索引与直接 SQL 可查性,违背「运营航班可被 DBA/运营直接使用」的初衷。
**修订结论**
| # | 问题 | 修订结论 |
|---|---|---|
| R1 | 表形态 | 废除 `FLTR_JSON` JSONB 整文档,改为**一行一航班的宽表**:FLID 主键 + FDAY 代列 + SCHD.FLTR 标量字段列(锚定 `unisysaodbsis.xsd` 契约与 legacy 派生字段 ABDG/LPSDT/ABN)+ 1:N 明细集合序列化文本列。字段即列,天然可索引、可直查。 |
| R2 | 集合字段 | 登机口/柜台/转盘/桥/延误等 1:N 明细(单航班可达 99 条)暂存序列化文本列(`*_TXT`11g 移植为 CLOB);阶段 2/3 Handler 钉死语义后按需升独立子表。 |
| R3 | 值语义 | 全部字段保持 legacy 字符串原样(不做库端类型转换),保证影子对拍逐字段保真;范围查询需要的类型化列(如 SODT→DATE)按查询需求逐列后置提升。 |
| R4 | SCHD_GEN | `FLIDS_JSON` 同步废除,展开为 `SCHD_GEN_FLID(FDAY, FLID)` 行;版本 CAS 语义不变。 |
| R5 | 值机视图 | Handler 在线视图契约由 `flid → FLTR_JSON` 改为 `flid → 字段集`(与 legacy `hgetAllFlightInfo` hash 同构);增量写语义=字段级合并(与 legacy hmset 一致),快照=整体替换。 |
| R6 | 对拍口径 | FS7 Diff 工具改为 PG 宽表列值 vs legacy FLTR JSON 的逐字段归一化比对(数值精度/空值等价规则保留),比原 JSONB AST 比对更精确到字段。 |
**未尽事项(11g 方言移植不在本修订范围,另行决策)**:存储模型已 11g 兼容,但仓储层仍存 PG 方言
`ON CONFLICT` upsert 需改 `MERGE`)、I5 advisory lock 的 11g 替代(`DBMS_LOCK`)、Flyway/驱动
对 11.2 的支持矩阵。若「自有 PG → 现场 11g」成为确定部署形态,需按 ACM2-28 体例开独立决策记录
重开「自有库」平台定案,评估点在方言与并发原语,不在本修订已解决的存储形态。
## 9. ACM2-29 修正落盘:全宽表·零子表(Zero-Subtable Wide Table
R2 的「`*_TXT` 过渡 + 按需升独立子表」经 `docs/legacy/SIS_AODB_RMS-V0.1.md` 报文样例核验与
成都现场(双流/天府)地服实际规律复核,确认为对 XSD 理论上限(maxOccurs="99")的过度设计,
正式废除。16 个明细集合全部收敛为 `FLIGHT_SCHD` 宽表的标准 VARCHAR 标量列或紧凑格式化字串,
零子表、零 CLOB(V1.2.0 迁移落地):
- **平铺标量列**(槽位号即报文序号属性,可按需直接追加普通 B-tree):登机门 `GATE1/2+PGOT/PGCT/GOTM/GCTM/GTYP`
×2 组;值机柜台 `CHKC1..3` 全属性(同柜台分舱复用条目写侧抹平去重);转盘 `BELT1/2` 全属性
(计划时刻 `BPCOT/BPCCT` 专属前缀避免与值机同名);计划机位 `PSST1/2`;离港通道 `CHUT1/2`
(等级/类型 `CHCLS/CHTYP` 专属前缀)。槽位数按现场规律定界(2/3/2/2/2),超界条目按定案丢弃。
- **单值/里程碑标量列**:延误 `DELY_CODE/DELY_STRT/DELY_DURA/DELY_REMC`(业务上任意时刻仅 1 个
有效延误,覆盖语义);靠撤桥 `ABTM_A/ABTM_D`(桥号沿用派生列 ABDG);轮挡 `CHOT_ON/CHOT_OFF`
(机位沿用派生列 STND);异常 `FDIV/FRET/FLAB` 前缀标量列(规范 1:0..1 单值异常)。
- **紧凑 VARCHAR 字串列**(无界/航路型集合,11g VARCHAR2 内联存储,非 CLOB):
`ROUT_PATH/ERUT_PATH`(≤7 站紧凑航路 `"APCD/SCAT/SCDT,..."`);`SRVT_TEXT/VIPF_TEXT/MAFL_TEXT`
(服务/VIP/共享列表条数无 XSD 上限,存 JSON 数组;超长由列约束 fail fast,不静默截断)。
- **消息语义**:运营资源 = 单资源集合级全量快照替换(集合键出现即整集合覆盖,缺槽位置 NULL,
序号属性 "0" 为显式删除标记);外层航班字段仍为字段级增量合并(仅新增/覆盖)。
- **读侧视图**:由平铺列重建 16 个集合键,与 legacy `flightInfo` hash 同构——KAFKA_SCHD 线格式
FLTR JSON 数组)与 FS7 Diff 逐字段比对均依赖该同构视图,存储形态变化不出仓储边界。
已知保真边界:`ABTM/CHOT` 只保留里程碑时刻(资源号沿用 ABDG/STND 派生列);航路空串属性
与缺失属性在紧凑字串中合并为空槽位;`FDIV/FRET/FLAB` 自由文本键名待阶段 2 Handler 钉死。
- **出站序列化**`FlightFieldsJson` 对契约结构字段解析 JSON tree 输出原生数组/对象,
杜绝集合被再次编码成字符串的双重转义。
- **I5 单写者锁修正**:不再依赖 PG advisory lock/11g `DBMS_LOCK`。V1.2.0 创建 `PIPELINE_LOCK`
单行锁表,处理事务对 `FLIGHT_SCHD_WRITER` 行执行 `SELECT ... FOR UPDATE`;连接断开、提交或
回滚时由数据库释放,PG 与 11g 同构,无 DBA 特权依赖。
16 个 `*_TXT` 文本列由 V1.2.0 全部删除;运营查询索引(如 `GATE1/CHKC1/DELY_CODE`)按需以普通
B-tree 逐列追加,11g/PG 零方言成本。
Oracle 11g 的 `MERGE INTO` 方言、Flyway 11.2 支持版本和 ojdbc 认证组合仍必须在目标数据库环境
中完成平台决策与集成验收;不能用 PostgreSQL 测试结果替代该外部环境证据。
## 10. ACM2-29 v2 无损明细表方案落地(2026-09-08)
按 [评审报告](../review-flight-state-2026-09-08.md)F1F9)与
[flight-state-design-v2.md](../flight-state-design-v2.md) 评审定案,撤销 §9「全宽表·零子表」
作为权威存储形态,落地无损模型;本节为当前有效规则索引:
- **存储**`FLIGHT_SCHD` 保留标量/异常前缀/无界文本列(V1.4.0 已 DROP 槽位、里程碑与紧凑
航路列及冗余索引);10 类重复集合由 9 张明细表 + `flight_route_point(ROUTE_KIND)` 承载,
PK `(FLID, ORDINAL)``SOURCE_SEQ` 不去重不设唯一(v2 §3.2)。
- **写路径**`persistNextStates` 是唯一写路径;引擎产生显式命令
Unchanged/Set/Clear/Replace/Apply),序号 0 条目仅在整集合清除时翻译为 Clear,
混排 fail fast;异常对象 `"null"`/`{}`/空串 = 显式清除置 NULL。
- **事务**PIPELINE_LOCK 行锁内复核快照身份(`schd_gen.last_message_id`)与当前态,
CAS 失败一律回滚 `FAILED(INFRA)`;提交后共享信箱回填失败落 `backfill_todo` 补偿重试,
成功终态不降级(v2 §5)。
- **读模型**:明细表为权威读;`flight_schd_display` 视图提供首/次资源与总数投影。
- **双库**`SqlDialect` 接缝(PG 实装;`Oracle11gDialect` 为编译级交付),
11g 激活门控见 `db/migration/oracle11g/README.md`——现场 11.2 实测通过前不切换。
- **字段语义唯一索引**[flight-state-semantics.md](../flight-state-semantics.md)。
+4
View File
@@ -1,5 +1,9 @@
# 运营航班状态设计评审与重构建议
> 历史评审:以下结论针对 cd5de56,不是当前状态。最新核查见
> [ACM2-29 完成度与简化计划](acm2-29-audit-and-simplification.md),现行设计见
> [航班状态设计](flight-state-design-v2.md)。保留原文用于追溯,不重复维护其阶段清单。
评审日期:2026-09-08。代码基线:`cd5de56`。评审对象:[decision-flight-state.md](decision-flight-state.md)、相关 SQL、仓储、快照流程、消息处理和测试。
本文件是有证据的设计评审和可实施提案,**不代表下述重构已经实装,也不自动取代 ACM2-28/29 的历史决策**。本次只新增评审文档及原决策文档的评审入口,没有修改生产代码、执行数据库迁移或更新 Plane 状态。
+7 -7
View File
@@ -10,7 +10,7 @@
- 所有内部迁移只落自有 PG;共享 MySQL 不建表、不增列、不写历史表。本文用 `DATE_PROCESSED / STATUS` 表示逻辑字段,实际列名以库方契约为准。
- 验收条目可按 `US-xx/条目号` 引用。故事较大时按下文子范围拆成小 PR,不把一个故事等同于一个提交。
**存储基线提醒**[FLIGHT_STATE 决策提案](decision-flight-state.md) 尚未定案。本文仍按阶段 A Redis 权威描述;涉及权威状态、快照和事务顺序的最终实现,先处理 Q1,不能一边实现 Redis 协议一边擅自迁为 PG 权威
**存储基线**[单库权威决策](decision-flight-state.md) 已采纳:当前 PG 主表与明细表是状态权威,Redis 不参与动态写路径。Oracle 11g 是尚待完整适配的部署目标。本文保留验收目标,完成度以 [ACM2-29 核查](acm2-29-audit-and-simplification.md) 的证据为准
## 2. 建议实施顺序
@@ -81,13 +81,13 @@
3. 解码后首次绑定 `SNDR|TYPE|STYP|SEQN`;冲突转 `SKIPPED` 并记录原 ID;自身重试保留绑定。生产 `include-day-boundary=false`,更改算法须另行评审上游序号规则。
4. `MALFORMED` 直接 `DEAD``CODEC_ERROR / UNSUPPORTED / INFRA` 按次数和退避处理,耗尽转 `DEAD(EXHAUSTED)`。不能无限重试未实现类型,也不能立即当非法报文丢弃。
5. HOL deadline 使用稳定的 `PROC_STATE.CREATED_AT`,不使用每次重试刷新的 `updatedAt`;所有调度判断注入 `Clock`。默认 5 次重试、10 分钟滞留限制;积压与人工重放的 deadline 边界按 Q6 验证。
6. 当前 Redis 基线下,由主泵先幂等应用状态,再在同一 PG 事务提交事件与处理结果;终态回填意图通过 US-09 同事务保存。Redis 已成功而 PG 失败可安全重试,不重复生成业务事件。
6. 主泵在同一 PG 事务提交航班主表/明细、事件与处理结果;终态回填意图通过 US-09 同事务保存。任一步失败整体回滚;提交后只重试外部回填,不重复生成业务事件。
7. Handler 只返回 `Decision`,副作用由管道执行;失败只在持有消息上下文的边界落账,中断向上传递,不作为普通失败吞掉。
8. 权威存储不可用或未完成恢复时停止业务处理;不能把“整个状态丢失”误判为“单航班不存在”而批量成功结束增量报文。
**当前基础与落点**`processing/Pump.kt`(含 `MessageProcessor``Identity.kt``codec/XmlCodec.kt``infra/retry/``JdbcPgRepositories.kt`。已有身份/重试边界测试,但 codec 为接口,主泵直取系统时间,普通 Redis 更新占位,PG 操作未共享事务;补 `CREATED_AT` 迁移及主泵级回归测试
**当前基础与落点**`processing/Pump.kt``Identity.kt``codec/JacksonXmlCodec.kt``infra/retry/``JdbcPgRepositories.kt`。已有 PG 共享事务、身份/重试及 GTDT XML 链路;完整 SCHD 解码/路由、时钟与 deadline、同事务回填意图仍未完成
**前置**US-01Q1 决定状态提交实现Q6 决定 deadline 边界。现有 `FlightStateRepository` 空实现不是建表授权
**前置**US-01Q1 已定单库方向Q6 决定 deadline 边界。数据库迁移只落自有库
### US-04 明确忽略非业务报文(KEEP)
@@ -282,10 +282,10 @@
|---|---|---|
| OPS-1 单写者与启动安全 | 生产缺真实适配器、误用 stub、未启用必需管道时拒启;第二活动写者不能启动,失去写权后不得继续写;中断与停机能正确退出。 | 配置拒启、双实例/失去写权及停机测试。单靠副本数配置不算运行期保护。 |
| OPS-2 可观测与安全 | 真实依赖健康、队列/队头年龄、投递/回填滞后、DEAD 和一致性异常有指标、告警与处理入口;敏感管理操作有访问控制,日志不泄漏口令或完整敏感报文。 | 故障注入触发真实告警,消息到事件可关联;日志出口断开不阻塞业务。 |
| OPS-3 影子隔离 | PG、Redis key、topic、服务注册身份隔离;输入只读水位或回放,禁生产回填、真实出站和误注册。Q1 若改存储,隔离方案同步修改。 | 配置与集成测试证明生产信箱、状态、topic 未被影子修改。 |
| OPS-3 影子隔离 | 自有数据库/schema、topic、服务注册身份隔离;输入只读水位或回放,禁生产回填、真实出站和误注册。 | 配置与集成测试证明生产信箱、状态、topic 未被影子修改。 |
| OPS-4 切流与恢复 | 对拍不少于 7 天,未解释业务字段差异为 0,DLQ 积压为 0MSG_EVENT 最老滞留 < 5 秒;切流后 48 小时观察,24 小时内具备经演练的回滚能力。 | 明确负载与统计口径的对拍报告;Runbook 含停写、排空/水位、状态恢复、写权交接和失败回退,不能只回滚程序版本。 |
上述阈值沿用既有需求基线,需在真实环境提供证据,不代表当前已满足。Redis 权威方案还必须验证空态/全损恢复期间停止增量处理,以及备份报文保留能支持的恢复范围;不承诺未经演练的“一键无损回滚”。
上述阈值沿用既有需求基线,需在真实环境提供证据,不代表当前已满足。自有库备份报文保留和完整状态重建需要恢复演练;本地事务不能承诺任意数据库灾难下 RPO=0,也不承诺未经演练的“一键无损回滚”。
### HTTP 工具边界
@@ -303,7 +303,7 @@
| 编号 | 问题与当前口径 | 解除阻塞的产物 |
|---|---|---|
| Q1 权威存储 | 当前基线是 RedisFLIGHT_STATE 提前到 PG 只是提案。影响 US-03/05/06/12、迁移和健康检查。 | 明确选择及批准记录,同步三份主文档后再固定事务、快照协议;未定案可先做无关接口和调度测试。 |
| Q1 权威存储(方向已定) | 当前 PG 单库权威,主表 + 无损明细;现场供库目标为 Oracle 11g。 | 单库决策已采纳,Oracle 完整适配与部署验收仍待交付;不得退回 Redis 双写。 |
| Q2 入队顺序 | 水位+补扫无法自动保证较小 ID 迟提交不越序;当前有限批扫描也可能被未回填记录挡住。 | 库方 ID/提交顺序约束,或明确的发现完整性与暂停/恢复协议;晚提交、空洞、兼容入口与重扫联合测试。不能凭空假定 ID 连续。 |
| Q3 HTTP 契约 | 目标 ResponseDto 与现有 text/plain ID 不同;请求媒体类型目标已列出,错误码、状态码、查询格式等仍需对拍。 | 每个保留接口的真实请求/响应样例、错误表和契约测试;10MB 的字节口径、字符集及兼容变更说明一起固定。 |
| Q4 Kafka wire | schd 当前多 FLID 聚为一个数组/一条 record,与“record 按 FLID 分区”冲突;msg 的 SNDR 尚未接线。 | 下游确认发送粒度、key、去重标识放置、分区内顺序及批次确认策略;同步 Dispatcher/设计,不擅自把现役数组改成逐航班消息。 |