Files
msgexchange-v2/docs/review-flight-state-2026-09-08.md
T

35 KiB
Raw Blame History

运营航班状态设计评审与重构建议

评审日期:2026-09-08。代码基线:cd5de56。评审对象:decision-flight-state.md、相关 SQL、仓储、快照流程、消息处理和测试。

本文件是有证据的设计评审和可实施提案,不代表下述重构已经实装,也不自动取代 ACM2-28/29 的历史决策。本次只新增评审文档及原决策文档的评审入口,没有修改生产代码、执行数据库迁移或更新 Plane 状态。

1. 结论与部署边界

保留“自有关系数据库内原子提交航班状态、处理终态和 outbox”的架构;建议撤销“全宽表、零子表、超界丢弃”的硬性限制,改为航班标量主表、无损重复明细、按需展示视图。

两项决策必须分开:选择一个事务内的权威存储,解决的是一致性;如何存放重复资源,解决的是信息保真和查询模型。前者正确,不足以证明后者正确。

本轮用户已明确:现场提供 Oracle 时一定是 Oracle 11g;否则自行安装 PostgreSQL。 因此实际产品边界是两种互斥部署配置,不能继续把 Oracle 当作遥远的假设,也不应让一个部署同时把 PG 和 Oracle 都作为动态状态权威。共享 MySQL 信箱边界保持原样。

评审建议分为三个层次:

  1. 立即修正会丢数据或破坏事务不变量的实现与验收口径。
  2. 用最小的领域结构承载 SIS 已有数据,完成一个 DNLD→资源增量→查询/通知的纵向闭环。
  3. 分别验收 Oracle 11g 和 PostgreSQL 适配;完整 AODB、资源优化调度、票务和历史分析不纳入本次重构。

2. 当前决策的合理部分与证据不足部分

原设计 评审结论 理由
航班状态迁回自有库,与 PROC_STATEMSG_EVENT 同事务 保留 消除原 Redis 状态与 PG 终态之间的跨存储提交窗口。
Redis 退出状态权威 保留 本项目已依赖自有关系数据库,增加另一个状态权威不带来必要收益。
Handler 纯函数,基础设施通过仓储隔离 保留 可以独立验证 KEEP/FIX 业务规则;物理表不应决定 Handler 的业务表达能力。
增量按字段合并,DNLD 全量替换 保留总体方向 需进一步区别字段未出现、显式清空、集合全量、操作记录增量。
FDAY 域化差删、ADFT 不随旧代误删 保留 FDAY 是快照所有权,不应冒充航班运营日期。
PIPELINE_LOCK 行锁 有条件保留 是写事务互斥工具,还不是完整的双实例 FIFO 或主备协议。
全字段字符串保持 legacy 兼容 保留原始值,补类型化查询面 时间字符串不适合排序/范围查询;Oracle 空串语义也会影响保真。
固定 2/3/2/2/2 槽位,超界丢弃 否决作为权威表示 项目自带三登机门样例已能触发丢失,不只是 XSD 理论边界。
多次操作只留一次、按资源号去重 否决作为通用规则 分配记录与物理资源不是同一实体;操作序号和资源身份有业务含义。
零 CLOB、零子表、零方言成本 撤销硬性约束 Oracle 11g 支持关系明细和 CLOB;串行事务与多表原子写入并不冲突。
“重启即恢复,RPO=0” 限定故障模型 本地事务关闭双写窗口,不等于数据库介质损坏、异步副本切换也零丢失。需单独定义备份、恢复与提交持久性要求。
“单行 upsert 亚毫秒,无新增往返” 改为待测目标 本轮未见支撑数据。当前增量路径实际包含父行 upsert 和字段 UPDATE,快照还涉及批量、差删、版本、事件。

文档也有演进失配:decision-flight-state.md §6/8/9 分别保留 JSONB、集合文本、固定槽位三个阶段,design.md §2 仍描述集合序列化文本;architecture.md 同时有行锁互斥与运行期排他尚未完成的说明。应为“当前有效规则”和“历史被替代规则”设置明确索引。

3. 互联网方案:哪些可以借鉴,哪些不能照搬

本次使用 TinyFish 搜索并读取一手资料。连接器未提供 batch_create,因此资料逐个 URL 读取,未使用异步浏览自动化。没有获得商业 AODB 的生产物理 DDL;不能把产品介绍或信息交换标准描述为厂商数据库表结构。

来源与性质 已核实内容 对本项目的适用性与限制
IATA《AIDX Implementation Guide》v3.02014,基于 14.1 schema;行业信息模型 航段是核心结构;重复元素保留顺序;文档包括资源、共享航班、运行时刻和不正常运行身份。 用于检验模型是否丢失领域信息。它不是本项目 SIS 的替代协议,也不是最新版本认证。
EUROCONTROL A-CDM Specification2025-01-30;运行流程模型 将进港与出港关联,通过里程碑推进周转和离港估计。 支持把进出港记录、周转关系和操作时刻分清;不要求本项目实现完整 A-CDM。
SITA Airport Management;商业产品功能边界 区分航班管理、固定资源、移动资源、协同决策,并涉及多机场。 可借鉴职责分离;公开页不披露其物理表,不能推断“它采用某种子表”。
Postgres Professional Airlines 演示库,版本 10 文档;公开关系 schema flights 有独立 flight_id、计划/实际时刻和机场引用;通过 flights_v 提供附加展示信息。 是能阅读字段、键和约束的数据库示例,但侧重订票,不是机场生产 AODB。
Oracle Database 11g SQL Reference;平台约束 SQL VARCHAR2 有 4000 字节上限;字符长度为零按 NULL 处理。 必须影响长度、空值、测试和驱动适配,不能仅替换 upsert 语法。

来源:IATA AIDX 指南EUROCONTROL 2025 A-CDMSITA Airport ManagementPostgres Professional schemaOracle 11g 数据类型Oracle 11g NULL

具体可借鉴之处:

  • AIDX §3.1.9 要求重复数据保持可追踪的顺序,更新一个重复元素时携带其余元素;附录 H 中登机门可重复三次、值机可表达不同类型和不连续范围、延误原因可有多项。这足以反驳“两登机门、一延误是通用航空事实”,但本项目每个消息类型的最终行为仍应以 SIS 和现场协议为准。IATA 指南
  • 进港和出港关联有运营价值,但不应因为是同一架飞机就合成同一条记录;周转关系应显式表达,不能从航班号或机尾号临时猜测。EUROCONTROL A-CDM
  • 展示可以是视图,权威表不必复制屏幕布局。演示库的 flights_v 提供本地时刻及机场附加信息,展示了这种分离方式;其票务实体和简化自然键不应直接搬入 SIS 中间件。Postgres Professional schema

以下表设计是结合这些资料和项目代码得出的本次建议,不是宣称行业标准强制规定了这些表名或范式。

4. 关键发现:契约保真

F1 — 高:当前两个登机门槽位直接违反本仓库样例

证据:

  • SIS §3.34.3,源文件约 8321–8340 行,同时给出 GTNO=1/G28GTNO=2/G33GTNO=3/G23
  • JdbcPgRepositories.kt:367 定义 GTDT 两槽位;:466flattenSlots 使用 .take(c.slots.size)
  • decision-flight-state.md §9 却称已通过该规范样例核验,并允许超界丢弃。

当前转换实际把三条变成两条,G23 消失。对于合法输入,系统仍可能提交 SUCCEEDED,不会因丢失而自动重试。这是权威存储的损失性转换,不只是展示不全。

建议:完整保存记录,界面需要两列时仅在视图投影前两项,并显示总数/更多信息。仅把上限从 2 改成 3 不能解决其他集合及将来的合法输入。

F2 — 高:资源编号、分配身份和数组位置被混为一谈

flattenSlots 并不按 GTNO/CKNO 选槽,而是按去重后的数组顺序赋槽;slotView 再用下标合成序号。因此只输入 GTNO=3,读回就是 GTNO=1。这也与文档“槽位号即报文序号属性”不符。

同一函数对五类集合都按资源号去重。两个 CHKC=01CCLS=F/Y 的分配会丢掉后一条;机位不同时间段、相同资源不同属性也存在同类风险。没有证据证明这些是可无条件抹平的冗余。

建议:分别保存 ordinal(报文出现顺序)、source_seq(源序号字符串)和资源号;资源号本身不设“每航班唯一”的约束。是否允许重复源序号应由协议矩阵决定,不能由 SQL 唯一键替上游作决定。

F3 — 高:操作历史被不充分地压缩为单一里程碑

  • SIS 约 51275157 行明确 ABTM 按每次靠桥/离桥重复,ASNO 标识操作序号,ABDG 标识实际桥资源。
  • XSD BRIDGEDATA(约 1714 行)也包含 ABDG/ABOP/AOTM/ASNO
  • flattenAirbridge 只取首个 A 和首个 D,读回时合成 1/2,完全不输出原条目的 ABDG
  • flattenChocks 只保留首次 ON/OFF,丢掉 CSNO/CHSTchocksView 给两者都生成 CSNO=1
  • flattenDelay 总取首条;XSD 的 DELY 为 maxOccurs="unbounded"(约 2251 行),不能从“一次只显示一条原因”推出只存一条。

建议:原始操作列表和“当前/首次/最近一次里程碑”分开。后者可以派生,但必须命名清楚,并定义按源序号、发生时刻还是到达顺序选择;现在的 firstOrNull 不具备“当前有效”的证明。

F4 — 高:显式清空与缺失混用,异常对象处理又不一致

FlightFieldsMap<String,String>,通过键是否存在、字符串中嵌套 JSON 表达不同含义。当前存在:

  • GTDT 的 []/null 会清空槽位,读回没有 GTDT 键;如果下游以“键缺失=保持旧值”消费,就不能代表清空。
  • flattenExceptionsFRET="null"、空对象、非对象或非法 JSON 返回,不写清空列;旧异常可能保留。
  • 异常缺失属性不清空,与文档“集合键出现即整体替换”的统称不一致。
  • 自由文本在 REMC/RSN/value/#text 中猜一个键,读侧固定输出另一个键,无法承诺原格式往返。

FRET="null" 不产生清空写入已由转换诊断复现。是否在每一种真实 XML 清除消息中触发,还需 Codec/Handler 的实际映射;不能把诊断输入误称为已经上线的报文路径。

建议:消息解释层显式使用 Unchanged / Clear / SetUnchanged / Clear / Replace / 按协议定义的操作。编解码层负责将 XML 删除标记翻译成操作;仓储不要靠字符串内容猜业务命令。

F5 — 高:出站载荷与数据库当前态可能在同一次提交后不同

SnapshotFlow.kt:99 从未压缩的 normalized 构造事件;仓储在写入时截断/去重。三门快照由此可能产生“Kafka 有三门,数据库点查只有两门”。普通路径也分别接受 flightChanges 和已序列化 schdPush,一致性依赖 Handler 自觉。

事务原子性保证两者一起提交,不保证它们表达同一状态

建议:统一生成完整的 nextState,无损持久化和线格式序列化都使用它。增加 readAfterWrite == nextStatewirePayload == wire(nextState) 两道断言;不要只测试事件数量与事务提交。

5. 关键发现:迁移、并发与恢复

F6 — 高:V1.2.0 不是有数据的安全升级迁移

V1.2.0 SQL 第 97–104 行直接删除 16 个旧集合列,前面只有 ADD COLUMN,没有数据转换或回填。若 V1.1.0 数据库有集合值,升级后旧值丢失,新列仍为 NULL。

空库首次安装不会出现这个损失;本轮未访问现场,不能声称现场已发生丢失。但当前脚本不具备带数据升级能力。

建议:扩展表结构→回填→逐字段核验→切换读写→观察→最后删旧列。已应用的 Flyway 版本不要直接改写;尚未升级但会经过 V1.2.0 的实例,必须在它执行前做无损导出或另行制定受控升级路线。后续新增迁移无法凭空恢复已被删除的数据。

F7 — 高:CAS 失败后版本相同,不能证明是同一个快照重放

SnapshotFlow.kt:59 在锁外读取 generation:69 先覆盖数据,:8287 在 CAS 失败时只要 again.version == expected + 1 就继续成功。

可由代码推导的交错:

  1. 处理者 A、B 在锁外都读到版本 5,但携带不同快照。
  2. A 拿锁,写入集合 A,把 generation 推到 6 并提交。
  3. B 随后拿锁,按旧预期写入集合 B 和旧差集。
  4. B 的 CAS 失败,看到版本恰为 6,于是继续提交事件和 SUCCEEDED。
  5. generation 成员仍可能是 A,航班内容却被 B 改写;这不是 no-op。

生产严格只有一个活动处理者时,此交错不会自然出现;它揭示的是文档宣称的交叉实例保护不能成立,也不能以“已有行锁”作为开放多实例的依据。本轮未做真实双连接复现。

现有 FlightSchdInvariantTest:223 的 mock 在 CAS 失败后仍返回版本 5,没有覆盖恰好为 6 的分支。

建议:锁内读 generation 和有效处理身份;CAS 失败统一回滚。若需要重放短路,必须在任何状态写入前,使用已成功的消息身份/明确的快照身份识别,同内容摘要只能作为补充,不能仅凭版本号。为 generation 增加 last_snapshot_message_id(或同等可核验身份)与可选摘要,保留版本只作为顺序令牌。

F8 — 高(双实例场景):行锁没有覆盖“读状态→决策”

Pump.kttxManager.inTransaction 之前读航班视图并调用 Handler。两实例可以基于同一旧状态分别计算,然后串行覆盖。PIPELINE_LOCK 也不自动保证较小信箱 ID 的处理者先取得锁。

建议:继续单活动实例部署。要增强防错,应在锁内复核处理终态与队头、读取当前态、应用纯决策并提交;XML 解析等不依赖当前态的工作可在锁外。切勿使用 SKIP LOCKED 跳过较小业务消息破坏严格 FIFO。数据库隔离级别也不能替代处理身份与顺序协议。

F9 — 高(提交后异常):回填失败可能落回处理失败路径

Pump.kt:235backfillOnSuccess 位于状态事务提交之后;异常会被外层 processOne 归为 INFRA 并交给 procFailure。快照回填也处于提交后。若再次进入完整处理流程,有重复事件、重复快照推进或覆盖新状态的风险。

建议:成功终态不可被回填错误降回业务失败。单独持久化回填待办,失败只重试回填;业务重试入口复核数据库中已提交终态。恢复能力应围绕“事务提交前/后”和“外部投递前/后”分别测试。

6. Oracle 11g 兼容性:不是把 ON CONFLICT 换成 MERGE

维度 当前风险 设计要求
空串 PG 可保存空字符串,Oracle 字符零长度按 NULLMap 的键存在性也会变化。 对有业务意义的空值保留 presence 标记或无损文档;不要未经协议认可全局将空串、NULL、缺失视为同义。Oracle NULL
长度 现有 *_TEXT VARCHAR(512/2000) 是序列化后的整列表;XSD 不限条数的列表可合法超长。 明确 BYTE/CHAR 与数据库字符集;11g SQL VARCHAR2 仍受 4000 字节边界,使用子表或 CLOB 承接无界集合。Oracle 数据类型
时间 SODT/ESTT/ACTT 是源字符串;数据库审计时间使用另一套类型。 区分业务发生时刻、收到时刻、提交时刻;原值和标准化时间并存。
DDL PG 迁移含 BIGSERIALTEXTADD COLUMN 等,整个辅助管道也需适配。 分 PG/Oracle migration location;定义 NUMBER、序列、长文本、时间与约束的映射。
DML 不只有 upsert,还包括生成键、分页、锁、批量和查询日期处理。 方言留在适配层,业务层不按数据库类型分支。
事务 行锁概念可共用,连接绑定和失效恢复仍须实测。 明确同一事务同一数据源;JdbcOps 当前 ThreadLocal 未按数据源区分,未来多个自有连接源不得误复用。
运行组合 当前依赖有 PG 驱动与 PG Flyway 模块,没有可据此验收的 Oracle 组合。 在目标 11.2 补丁级别上验证 JDK 25、具体 JDBC 驱动、迁移工具和连接池组合。不能凭“ojdbc8”名称认定支持。

11g 缺少现代 SQL/JSON 能力,不等于不能存储 JSON 文本。当前 V1.2.0 自己还把 SRVT/VIPF/MAFL 保存为 JSON 数组字符串;“废除 JSON 存储”因此也不是准确描述。真正需要选择的是:哪些字段要求 SQL 查询/约束,哪些原始结构只需无损保存。

超长 fail fast 优于静默截断,但如果合法消息仅因本地任意长度限制进入反复重试,严格 FIFO 会被持续阻塞,最终 DEAD 后仍缺失有效更新。容量问题应在投产前按契约和观测量解决;永久验证错误与暂态基础设施错误也应分开分类。

7. 建议的领域边界

本项目是外部 AODB 报文的有状态接收与分发系统。这里的“权威”指本系统已处理消息所形成的当前态及下游输出,不表示取得上游 AODB 的业务编辑权。

概念 建议定义 不要混用
航班记录 当前源系统用 FLID 标识的进/离港运营记录 航线模板、每天重复的航班号、航空器、旅客行程
航段/周转关系 对现有源关系作明确映射,需要时关联两个记录 不能先假定本地 FLID 与 AIDX 航段完全同义,也不能仅按机尾号自动配对
资源分配记录 某航班某一序号的门/柜台/转盘/计划机位等分配 资源主数据;相同资源号不一定是同一分配
操作记录 一次靠桥、撤桥、上/下轮挡等源记录 当前资源、首次/最近里程碑
运营日期 按明确机场时区及业务口径定义的日期 FDAY 快照归属日;不要直接改现有主键
消息身份 源消息去重与处理恢复标识 快照版本、FLID、发生时刻
当前态、历史、展示 当前态由消息合并;历史单独留痕;展示可派生 outbox 的合并通知不是完整运行事件历史

现有单机场/单 AODB 数据域可继续以 FLID 作主键。双流/天府若以后汇入一个实例,先核实跨机场和跨源 FLID 唯一性;只有证实会冲突才引入 (source_system, airport_scope, external_flid) 唯一映射及内部 ID。当前没有证据证明已经发生碰撞,不把这个潜在需求扩大为立即全表换键。

8. 推荐关系模型

8.1 主表与明细的分工

erDiagram
    FLIGHT_SCHD ||--o{ FLIGHT_GATE : contains
    FLIGHT_SCHD ||--o{ FLIGHT_CHECKIN : contains
    FLIGHT_SCHD ||--o{ FLIGHT_BELT : contains
    FLIGHT_SCHD ||--o{ FLIGHT_BRIDGE_OP : records
    FLIGHT_SCHD ||--o{ FLIGHT_CHOCK_OP : records
    FLIGHT_SCHD ||--o{ FLIGHT_ROUTE_POINT : contains
    SCHD_GEN ||--o{ SCHD_GEN_FLID : owns

图只列主要关系;计划机位、通道、延误的明细见下表。SCHD_GEN_FLID 是代成员历史元数据,不强行对当前航班加级联外键,避免清场或跨代迁移改变既有差删语义。

FLIGHT_SCHD 保留 FLID、FDAY、标量业务字段、created/updated 时间。新增字段按实际查询和恢复需求选择:

  • LAST_MESSAGE_ID:追踪最后影响该航班的消息,关联审计,不代替业务去重。
  • STATE_VERSION:需要一致查询或恢复校验时使用;每次有效变更推进,不因无副作用重放推进。
  • SCHEDULED_AT 等类型化时间:从原始 SODT 等派生,保留原值。
  • presence/解析状态:只在需区分空/缺失或解析失败的字段上引入,不滥加全表状态。

8.2 16 类结构的建议映射

现有集合 建议存储 必须保留的关键含义
GTDT FLIGHT_GATE ordinal、GTNO、GATE、PGOT/PGCT/GOTM/GCTM、GTYP
CKDT FLIGHT_CHECKIN ordinal、CKNO、CHKC、CCLS、计划/实际开放关闭、CTYP;不按 CHKC 去重
CLDT FLIGHT_BELT ordinal、CLNO、BELT、BCLS、计划时刻、首末行李时刻、BTYP
PSDT FLIGHT_STAND_PLAN ordinal、PSNO、PSST、开始/结束时刻
CHDT FLIGHT_CHUTE ordinal、CHNO、CHUT、等级、计划/实际时刻、类型
DELY FLIGHT_DELAY ordinal、CODE、STRT、DURA、原文说明;不以“唯一当前原因”覆盖列表
ABTM FLIGHT_BRIDGE_OP ordinal、ASNO、ABDG、ABOP、AOTM
CHOT FLIGHT_CHOCK_OP ordinal、CSNO、CHST、CHID、CHTM
ROUT、ERUT 共用 FLIGHT_ROUTE_POINT,区分 route_kind ordinal、原 RTNO、APCD、SCAT、SCDT,避免自定义分隔符和重编号
FDIV、FRET、FLAB 主表前缀列可保留 单值符合 XSD;明确文本键、空值、清除规则即可,不必强拆子表
SRVT、VIPF、MAFL 第一阶段允许无损文本旁表;查询需求明确后再结构化 JSON 数组原有顺序、全部属性和长度;Oracle 使用 CLOB,PG 使用 TEXT;应用验证结构

低频集合旁表可定义为 FLIGHT_COLLECTION(FLID, COLLECTION_KIND, PAYLOAD, SCHEMA_VERSION),主键是 (FLID, COLLECTION_KIND)。这不是用于任意核心字段的 EAV 表,只承接暂未需要 SQL 过滤的完整集合。MAFL 的共享航班关系一旦用于关联查询,应提升为明确关系,而不是在 SQL 中解析文本。

明细表数量由独立属性和读写语义决定,既不追求“零子表”,也不要求所有 16 类各建一张。1:N 表只存实际 N 行;支持 XSD 上限并不等于每航班预建 99 行。

8.3 单个明细表的可评审形状

以下是逻辑字段设计,非可直接运行的双数据库 DDL:

FLIGHT_GATE
  FLID                 FK -> FLIGHT_SCHD
  ORDINAL              positive integer; input order
  SOURCE_SEQ           source GTNO string
  GATE_CODE            source GATE
  PLANNED_OPEN_RAW      source PGOT
  PLANNED_CLOSE_RAW     source PGCT
  ACTUAL_OPEN_RAW       source GOTM
  ACTUAL_CLOSE_RAW      source GCTM
  RESOURCE_TYPE        source GTYP
  [typed timestamps / presence data as required]

  PK (FLID, ORDINAL)
  INDEX (GATE_CODE, FLID) when resource lookup is required

SOURCE_SEQ 不强转整数,以免丢掉契约/legacy 需要保留的形式。ORDINAL 是当前集合内部位置,不承诺跨版本稳定身份。若某报文是按序号逐项变更,必须由其业务规则使用源序号定位,并单独验证重复序号。

子表 FK 可随主表删除级联,以保证删除完整;对资源主数据的引用建议先做软校验和未知代码可观测性,避免参考数据刷新滞后使整个 FIFO 卡住。引用完整性是否升级硬约束需结合上游保证。

8.4 标量类型与时区

SIS 的时间示例是 DDMONYYHHMM 当地时间(例如 GTDT 的定义),不能因为 AIDX 使用 UTC 就直接给 SIS 字符串加 Z。应使用明确的 Asia/Shanghai 和英文月份解析,定义两位年份的世纪窗口,保留原字符串;测试跨月、跨年、午夜和非法值。其他机场接入时必须重新绑定时区。

高频查询使用标准化时间列;原始 SODT 字符串的字典序不等于时间序。PG 使用相应时区时间类型,Oracle 使用经过验证的 TIMESTAMP 类型与 JDBC 绑定;保留机场时区配置,不能认为数据库保存了瞬间就保存了源业务时区。

FTSS、取消、异常、资源操作里程碑也不要合成一个强制单向状态机。来自外部的更正消息可以修正历史值,哪些允许更正由 KEEP/FIX 矩阵决定。

9. 写入与消息语义

建议将模块边界调整为:

SIS XML -> 契约解码与校验 -> 类型明确的命令
                               |
                  当前态 + 纯 Handler -> nextState / 通知意图
                               |
           同一自有库事务:主表 + 明细 + generation + outbox + SUCCEEDED
                               |
                    提交后回填补偿 / 至少一次投递

9.1 明确操作,不让仓储推测

输入情况 标量动作 集合动作
字段/集合未出现 不修改 不修改
合法值出现 Set(value) 对被确认是集合快照的类型,Replace(完整列表)
明确删除标记 Clear Clear,按线协议输出明确的清除表达
操作型消息 按该类型规则 按序号更新、追加或替换由逐类型契约决定
非法对象、未知属性、超容量 可诊断失败 禁止成功后悄悄丢弃;是否可扩展接收由版本策略决定

GTDT/CKDT 的“0=全部未分配”在本地 SIS 文档有依据;不能直接推广为所有类型的序号 0 都有同样含义。ABTM/CHOT 尤其需要确认报文携带操作全集还是本次变化。AIDX 的重复元素约定可帮助设计,但不能替代 SIS 实测。

Clear 后数据库与 wire 是否输出 []null 或协议专属标记,必须按下游契约固定;本轮不擅自改变 Kafka 线格式。

9.2 保持原子性与 FIFO 的提交顺序

  1. 锁外解析并验证完整消息,生成不依赖当前态的命令;快照整包校验重复 FLID、日域和集合边界。
  2. 开自有库事务,取得 writer 行锁。
  3. 锁内复核队头与消息终态;已成功的同一消息直接结束业务应用,不增加版本/事件。
  4. 读取当前航班与 generation,调用纯函数计算下一状态。
  5. 写主表和受影响的明细;快照进行 FDAY 域化差删、版本和成员推进。CAS 失败即回滚。
  6. 从同一个完整状态产生 outbox;记录必要身份和版本;置 SUCCEEDED。
  7. 提交后独立执行回填/投递;其失败不回退业务终态。

集合替换可在同一事务内 DELETE WHERE FLID=? 后 JDBC batch INSERT。小集合无需先实现复杂行级 diff;其他连接只能看到已提交状态,但多条 SELECT 的组装仍需一致读取策略。

DNLD 删除某航班后,下游如何得知删除也需要验收:显式删除通知、整批替换标记或重新拉取必须选定一种。当前 SnapshotFlow 只为新集合构造 schd 事件,不能只验收数据库少了一行。

10. 查询与展示:避免把屏幕列数写成业务上限

项目当前没有前端,现有输出是 /all/flights 与 Kafka。所谓运营“显示”应落实为它们的读模型,而非凭空增加 UI。

兼容 API 可以继续提供原来的 FLTR/字段结构,仓储聚合主表和明细。DBA 需要首个/第二个登机门时,提供 FLIGHT_SCHD_DISPLAY 查询视图,额外给出 GATE_COUNT,并保留完整明细查询。主表无需新增任意数量的 GATE3...99

实现时注意:

  • 不把多个 1:N 表直接 JOIN 成一个巨大的结果;三个门乘四个柜台再乘两个转盘会重复行。
  • 主表分页后,按 FLID 批量读取各类明细并组装,避免每航班 N+1 查询。
  • 父子表分多次 SELECT 时使用同一一致快照,或读取版本并重试;否则可能混合更新前的主表与更新后的明细。PG/Oracle 分别验收隔离行为。
  • MAID 过滤保留现有共享航班查询契约。共享记录、实际承运信息和营销航班关系不要合并成单一文本标签。
  • 资源反查索引通常从 (resource_code, flid) 出发;航班列表按实际日期/进离港筛选建立组合索引。用真实查询计划确定,不是把所有宽表列都加 B-tree。
  • /all/flights 的兼容全量接口先保留;需要新增分页接口时另定 API 契约,不能悄悄把原接口改成只返回第一页。

性能验收应测报文峰值、最大 DNLD、每类集合 p95/p99/最大值、事务 p95/p99、锁等待、提交日志量、查询组装成本和消息积压。只有生产样本分布,不能证明业务硬上限;同时要有协议边界测试。

11. 分阶段迁移与回退

阶段 具体工作 完成证据
P0:契约和阻断项 建立 16 类集合的缺失/清空/替换/操作矩阵;修 CAS、提交后回填边界;恢复完整集合承载能力。 三门、多舱柜台、多次桥/轮挡、异常清除、两快照冲突都通过。
P1:无损扩展 增加明细/过渡旁表;保留现有展示列;建立版本和来源追踪;增量写同库同事务。 新旧读取与源完整状态对比,所有合法输入无静默损失。
P2:数据回填与切换 按已部署版本选择回填来源,完整校验后切读;保留旧结构一个回退窗口。 数量、属性、顺序、空值、状态及 outbox 载荷核对;无未解释偏差。
P3:双数据库验收 分别提供 PG/Oracle DDL/DML、迁移与集成环境;业务契约用同一组测试。 两套隔离环境均通过;11g 驱动/JDK/迁移组合已实际验证。
P4:查询优化与收尾 按负载加索引/展示视图,移除确认不再使用的损失性列与转换函数。 查询计划和时延达标;回退窗口关闭、数据备份可恢复。

不同安装状态必须区别处理:

  • 空库:可新建正确结构,但仍需通过升级链的空库安装测试。
  • V1.1.0 有数据、尚未到 V1.2.0:必须在 DROP 之前保存完整集合;由原 *_TXT 回填并对拍。
  • V1.2.0 已运行:固定槽位里缺失的第三个门、源序号等不可从现表推算回来。需要旧库备份、完整历史输入或可信的完整快照。新的 DNLD 只能恢复它包含的当前态,不能保证恢复全部历史操作。
  • 回放恢复:进入隔离库、隔离投递目标;先重建并核对水位,再受控切换。不能把旧消息直接灌回实时 FIFO 覆盖新状态。

PG→Oracle 切换不做两个权威库之间的实时应用双写。以停写水位、待投递事件状态、完整导出、目标导入对拍和单活动实例切换为基本路线;具体停机窗口与回退点待部署资料确定。

12. 验证结果与还缺什么

12.1 本轮已执行

使用 JDK 25 执行:

GRADLE_USER_HOME=/home/windy/project/msgexchange-v2/.gradle-home \
TMPDIR=/home/windy/project/msgexchange-v2/.gradletmp \
./gradlew test \
  --tests '*FlightSchdInvariantTest' \
  --tests '*FlightStoreDiffToolTest' \
  --tests '*FlightFieldsJsonTest'

结果:13 tests0 failures0 errors0 skipped。分别为 6 个状态不变量测试、5 个对拍测试、2 个序列化测试。初次沙箱运行因 Gradle 本机网络绑定失败;离线重试缺依赖;授权的在线运行成功。编译存在既有弃用/注解警告,没有据此修改依赖。

另以临时 Java 诊断直接调用本次编译产物中的 flattenForStoragemapFlightRow。DataSource 代理禁止任何数据库调用,ResultSet 为内存字段映射;这是转换级诊断,不是 JDBC 集成测试。复现结果:

输入 实际观察
三条 GTDTG28/G33/G23 读回两条,G23 丢失
单条 GTNO=3 读回 GTNO=1
同 CHKC 的 F/Y 两条分配 读回一条
两次靠桥、不同 ASNO/ABDG 只余一次,原桥号和源序号消失
GTDT=[] 读回没有 GTDT 键
FRET=null 没有生成任何清空列
DELY 两条原因 读回首条

这解释了为什么既有测试全绿不足以支持“100% 保真”:相关测试未覆盖上述反例,stub 也没有模拟 JDBC 的损失性表示。

12.2 尚未执行及原因

  • 未执行全部测试及现有 FlightSchdJdbcPgTest。其 setUp 固定连接本机 msgxcleanup:70 删除 TEST_% 航班及 2026-09-01 以后的 generation;未确认隔离性,不应在设计评审中直接运行该清场逻辑。
  • 该 JDBC 测试在连接不可用时会 assumeTrue 跳过,当前构建没有以 Testcontainers 提供隔离 PG 的证据;因此文档中的集成验收措辞不能只看总任务绿色。
  • 未连接 Oracle 11g;没有驱动/JDK/迁移工具兼容验收,没有带真实数据迁移、跨连接并发和恢复性能结果。
  • 没有现场真实流量分布或双机场 FLID 唯一性材料。文档声称的现场规律与本轮可核验事实分开记录。

12.3 实施时必需的回归矩阵

类别 至少覆盖
契约往返 三门、99 条允许集合、重复资源不同舱位、非连续源序号、顺序、每个可选属性、中文长文本
空值 未出现、空串、NULL、空数组、0 删除标记、异常对象清除;PG 与 Oracle 同语义
操作 多次靠撤桥/轮挡、更正原操作、不同桥/机位、多个延误、路线空属性
快照 同 FLID 重复、跨日归属、ADFT 存活、空快照、非法快照整包拒绝、响应匹配
事务 任意明细/outbox 写失败全回滚;generation 内容与版本一致;不同消息争同一下一版本必须冲突
恢复 提交成功后回填失败不重做业务;重放不加版本和事件;下游已接收可去重
多连接 锁等待、断连释放、读后写冲突、不能跳过 FIFO;不能只用内存 mock 证明
迁移 有数据 V1.1→目标、V1.2 损失检查、故障恢复、回退窗口、两数据库新建/升级
查询/投递 父子状态一致读取、全量接口保兼容、无 N+1/笛卡尔积、删除可见、DB 与 wire 等价

对拍工具继续保留严格默认:不通过忽略 GTNO、忽略第三个资源、忽略 ABDG 等方式把有损转换“洗绿”。任何合法偏离必须有业务依据、明确范围和黄金样例。

13. Plane workflow 对齐与后续落点

已按 plane-workflow 读取 ACM2-28 与 ACM2-29,确认属于 ACM2 项目;并搜索已有航班主题工作项。没有新建任务、修改状态或发表评论。

  • ACM2-28 的单库权威决策可以保留;RPO、完成清单和当前平台措辞应按本评审限定。
  • ACM2-29 直接覆盖本次问题,但其中“全系统零子表”“100% 吻合”“零方言包袱”等验收标准需要修订为可测的不变量。
  • 建议将后续工作拆为:契约保真与安全存储、快照/回填事务修正、有数据迁移、Oracle 11g 适配、读模型与查询验收。工作项依赖应在获授权写入时建立 Plane relation,不靠标题排序替代依赖。
  • 本轮用户已明确的部署条件应写入后续平台方案,不再重复询问是否可能是 Oracle 12c/19c;仍需的是实际 11.2 补丁、字符集、权限和可测试环境。

评审意见:ACM2-28 的一致性方向通过;ACM2-29 的损失性宽表方案不建议作为生产权威形态验收。优先完成无损表示和事务恢复,再扩展批量 Handler。