docs(acm2-75): 清理空转待决项并对齐全仓旧编号引用
- 删 Q2/Q15/Q19/Q20:时钟偏斜不进判据、实现自定已是结论、原文留存已定、 运营日冲突处置已由 DEAD(PROTOCOL) 定 - Q6 的 abdg 定案本版不提供;Q22 改为 13 类报文到 admin-api 实体表组的映射 (REF_MASTER 单表口径退位);Q23 收窄为 SODT+FLID 幂等重试 - C-1 自清口径四方同步(requirements 非目标、US-03 AC2、architecture §6) - INV-2 吸收回填只写空值;§7 恢复 G-RESP-GUARD - architecture/implementation/README/V1/oracle/Kotlin/seed 中 40+ 处旧编号 按映射重指,check-docs 全绿
This commit is contained in:
@@ -68,7 +68,7 @@ data class ProcState(
|
||||
val receivedAt: Instant? = null,
|
||||
/**
|
||||
* 本地入队时间(本系统写入,非空)。超期补写期限 `R` **只比较它**:与判据用的本地
|
||||
* `NOW` 同源,不受库方时钟偏斜影响(`PRE-4`)。
|
||||
* `NOW` 同源,不受库方时钟偏斜影响。
|
||||
*/
|
||||
val enqueuedAt: Instant? = null,
|
||||
/** 非空表示已确认信箱行带上了处理标记。 */
|
||||
|
||||
@@ -119,7 +119,7 @@ interface ProcStateRepository {
|
||||
*
|
||||
* [overdueBefore] 是兜底:入队时间早于它的(已经等了超过 `R`)无视退避直接补写。
|
||||
* 没有这条兜底,退避一直失败的话这些行就永远打不上标记,库方也没法清理信箱。
|
||||
* 判据比较的是**本地** `ENQUEUED_AT`,不是库方时钟的 `RECEIVED_AT`(`PRE-4`)。
|
||||
* 判据比较的是**本地** `ENQUEUED_AT`,不是库方时钟的 `RECEIVED_AT`。
|
||||
*/
|
||||
fun findBackfillDue(now: Instant, overdueBefore: Instant, limit: Int): List<BackfillDue>
|
||||
|
||||
@@ -244,7 +244,7 @@ interface FlightStateRepository {
|
||||
* 物理删除主行与明细,按 `(FLID, STATE_VERSION)` 守卫:只删归档确认过的那个版本,
|
||||
* 期间被主泵写过的行影响 0 行。
|
||||
*
|
||||
* **只能在历史存储确认归档成功、且在 `PIPELINE_LOCK` 事务内按同一判据复查之后调用**(`INV-18`);
|
||||
* **只能在历史存储确认归档成功、且在 `PIPELINE_LOCK` 事务内按同一判据复查之后调用**(`US-14` AC4);
|
||||
* 历史存储没接通时,调用方必须传空列表,也就是一条都不删。
|
||||
*/
|
||||
fun purgeArchived(candidates: List<HistoryCandidate>): Int
|
||||
|
||||
+2
-2
@@ -37,10 +37,10 @@ class JdbcCminmsgInboxRepository(
|
||||
}
|
||||
} catch (e: IllegalStateException) {
|
||||
// 只调整适配代码、不碰共享 Schema:把"取不到 generated key"变成可诊断错误,
|
||||
// 指出这是共享信箱 ID 生成方式的前提(C-14、Q8)。
|
||||
// 指出这是共享信箱编号生成方式的前提(Q7、Q8)。
|
||||
throw IllegalStateException(
|
||||
"shared mailbox insert returned no generated CMINMSGS_ID; " +
|
||||
"the CMINMSGS_ID column must be DB-generated (C-14, Q8)",
|
||||
"the CMINMSGS_ID column must be DB-generated (Q7, Q8)",
|
||||
e,
|
||||
)
|
||||
}
|
||||
|
||||
+2
-2
@@ -86,7 +86,7 @@ class JdbcProcStateRepository(
|
||||
{ ps ->
|
||||
ps.setLong(1, msgId)
|
||||
ps.setTimestamp(2, receivedAt?.toSqlTimestamp())
|
||||
// 入队时间必须由本系统时钟给出:它同时是超期判据 R 的比较对象(PRE-4)。
|
||||
// 入队时间必须由本系统时钟给出:它同时是超期判据 R 的比较对象。
|
||||
ps.setTimestamp(3, (enqueuedAt ?: now).toSqlTimestamp())
|
||||
ps.setTimestamp(4, now.toSqlTimestamp())
|
||||
},
|
||||
@@ -252,7 +252,7 @@ class JdbcProcStateRepository(
|
||||
*/
|
||||
/**
|
||||
* 超期判据用 `enqueued_at`(本地时钟、非空),不用 `received_at`:
|
||||
* 后者来自库方时钟,前偏会让 `NOW − R` 提前成立(`PRE-4`)。
|
||||
* 后者来自库方时钟,前偏会让 `NOW − R` 提前成立。
|
||||
*/
|
||||
override fun findBackfillDue(now: Instant, overdueBefore: Instant, limit: Int): List<BackfillDue> =
|
||||
ds.query(
|
||||
|
||||
@@ -72,7 +72,7 @@ class InboxPoller(
|
||||
val enqueued = txManager.inTransaction {
|
||||
var n = 0
|
||||
rows.takeWhile { it.msgId <= committedTo }.forEach { row ->
|
||||
// 入队时间用本轮注入的本地时钟,与超期判据 R 同源(PRE-4)。
|
||||
// 入队时间用本轮注入的本地时钟,与超期判据 R 同源。
|
||||
if (procState.insertIfAbsent(row.msgId, row.receivedAt, enqueuedAt = now)) n++
|
||||
}
|
||||
cursor.save(InboxCursorRepository.Cursor(committedTo, holeSince))
|
||||
|
||||
@@ -46,10 +46,10 @@ class InboxService(
|
||||
}
|
||||
val receivedAt = mailboxReceivedAt ?: now
|
||||
try {
|
||||
// 入队时间单独传本地时钟:它是超期判据 R 的比较对象,不能借用库方时间(PRE-4)。
|
||||
// 入队时间单独传本地时钟:它是超期判据 R 的比较对象,不能借用库方时间。
|
||||
procState.insertIfAbsent(id, receivedAt, enqueuedAt = now)
|
||||
} catch (e: Exception) {
|
||||
// PG 入队失败不回退接收结果:原文已在信箱,轮询会按 ID 补建,且只建一条(INV-2b、US-01)。
|
||||
// PG 入队失败不回退接收结果:原文已在信箱,轮询会按 ID 补建,且只建一条(INV-1、US-01)。
|
||||
pgEnqueueFailures.incrementAndGet()
|
||||
log.error("compat accepted but PG enqueue failed msgId={}; poller will re-create", id, e)
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ class HistorySweepJob(
|
||||
|
||||
val toPurge = candidates.filter { it.flid in archivedFlids }
|
||||
// 归档与删除之间主泵可能已写入同一 FLID:进锁事务后按 (FLID, STATE_VERSION) 复查,
|
||||
// 仍合格才登记 tombstone 并删除;事件与删除同事务提交或回滚(INV-17、INV-18)。
|
||||
// 仍合格才登记 tombstone 并删除;事件与删除同事务提交或回滚(D1、US-14 AC4)。
|
||||
var purged = 0
|
||||
txManager.inTransaction {
|
||||
lock.lock()
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
-- 自有 PostgreSQL 全量基线(单迁移链条目)
|
||||
-- ---------------------------------------------------------------------
|
||||
-- 范围:本迁移只作用于自有 PostgreSQL。共享 MySQL 信箱(CMINMSGS/COUTMSGS)是他人
|
||||
-- 系统库,只做契约内 DML,不建表、不改结构(C-14)。
|
||||
-- 系统库,只做契约内 DML,不建表、不改结构(C-2)。
|
||||
--
|
||||
-- 本文件原为 V1 基线;原 V2–V10 的净结构已全部合并进来,版本链收敛为一条:
|
||||
-- · INBOX_CURSOR 及 SEEDED_AT(原 V2/V5);
|
||||
@@ -28,7 +28,7 @@
|
||||
-- =====================================================================
|
||||
|
||||
-- ① 单行锁:事务内串行化状态写事务(只串行化本地 DB 事务,不代替消息认领、选主或
|
||||
-- 故障切换,PRE-5)。历史清理与主泵处理器共用它互斥(INV-18)。
|
||||
-- 故障切换,OPS-1)。历史清理与主泵处理器共用它互斥(US-14 AC4)。
|
||||
CREATE TABLE PIPELINE_LOCK (
|
||||
LOCK_ID INT NOT NULL PRIMARY KEY, -- 恒为 1
|
||||
UPDATED_AT TIMESTAMP(6) WITH TIME ZONE NOT NULL
|
||||
@@ -36,8 +36,8 @@ CREATE TABLE PIPELINE_LOCK (
|
||||
INSERT INTO PIPELINE_LOCK (LOCK_ID, UPDATED_AT) VALUES (1, now());
|
||||
|
||||
-- ② 处理伴生状态:与共享信箱 CMINMSGS 一一对应(MSG_ID = CMINMSGS_ID)。
|
||||
-- MSG_ID 主键(INV-2b);IDENTITY_KEY 唯一(implementation「消息、身份与决策」);MSG_ID 已有成功终态即判重放,直接记幂等成功。
|
||||
-- 处理终态与回填意图在本表同表同行:不存在第二处落账,也不存在独立的回填待办表(INV-8)。
|
||||
-- MSG_ID 主键(INV-1);IDENTITY_KEY 唯一(implementation「消息、身份与决策」);MSG_ID 已有成功终态即判重放,直接记幂等成功。
|
||||
-- 处理终态与回填意图在本表同表同行:不存在第二处落账,也不存在独立的回填待办表(INV-2)。
|
||||
-- 已提交的 SUCCEEDED 不因写信箱处理时间或发 Kafka 失败回改(US-03 AC3)。
|
||||
CREATE TABLE PROC_STATE (
|
||||
MSG_ID BIGINT NOT NULL PRIMARY KEY,
|
||||
@@ -49,14 +49,14 @@ CREATE TABLE PROC_STATE (
|
||||
LAST_ERROR VARCHAR(1000),
|
||||
UPDATED_AT TIMESTAMP(6) WITH TIME ZONE NOT NULL,
|
||||
-- ==== 时间基准 ====
|
||||
RECEIVED_AT TIMESTAMP(6) WITH TIME ZONE, -- 复制自信箱 DATE_RECEIVED:库方时钟、可为 NULL,仅用于对账与展示(PRE-4)
|
||||
ENQUEUED_AT TIMESTAMP(6) WITH TIME ZONE NOT NULL DEFAULT now(), -- 本地入队时间:非空、与判据 NOW 同源,是超期期限 R 的唯一比较对象(PRE-4、CLM-4)
|
||||
-- ==== 回填事实(INV-8;写规则见 US-10)====
|
||||
RECEIVED_AT TIMESTAMP(6) WITH TIME ZONE, -- 复制自信箱 DATE_RECEIVED:库方时钟、可为 NULL,仅用于对账与展示
|
||||
ENQUEUED_AT TIMESTAMP(6) WITH TIME ZONE NOT NULL DEFAULT now(), -- 本地入队时间:非空、与判据 NOW 同源,是超期期限 R 的唯一比较对象
|
||||
-- ==== 回填事实(INV-2;写规则见 US-10)====
|
||||
BACKFILL_AT TIMESTAMP(6) WITH TIME ZONE, -- 非空 = 处理标记已确认写入
|
||||
BACKFILL_NEXT_AT TIMESTAMP(6) WITH TIME ZONE, -- 非空 = 还欠一次回填;与终态同语句写下
|
||||
BACKFILL_ATTEMPTS INT NOT NULL DEFAULT 0,
|
||||
BACKFILL_ERROR VARCHAR(512),
|
||||
BACKFILL_ABANDONED_AT TIMESTAMP(6) WITH TIME ZONE, -- 非空 = 已停止自动重试;**不等于**标记已确认(C-8、C-16)
|
||||
BACKFILL_ABANDONED_AT TIMESTAMP(6) WITH TIME ZONE, -- 非空 = 已停止自动重试;**不等于**标记已确认(US-10 AC2)
|
||||
BACKFILL_ABANDONED_REASON VARCHAR(64), -- MISSING_ROW(确定性)/ TRANSIENT_DEADLINE(暂时性耗尽 R)
|
||||
CONSTRAINT uk_proc_identity UNIQUE (IDENTITY_KEY)
|
||||
);
|
||||
@@ -80,14 +80,14 @@ CREATE TABLE INBOX_CURSOR (
|
||||
-- 初值 W=0:首轮把信箱现存行全部重新读一遍,重复登记不会建出第二行。
|
||||
INSERT INTO INBOX_CURSOR (CURSOR_ID, COMMITTED_UP_TO, HOLE_SINCE, UPDATED_AT) VALUES (1, 0, NULL, now());
|
||||
|
||||
-- ④ 航班当前态主行:一行一 FLID(决策层权威;展示视图是投影,不是权威,INV-11)
|
||||
-- ④ 航班当前态主行:一行一 FLID(决策层权威;展示视图是投影,不是权威,INV-5)
|
||||
CREATE TABLE FLIGHT_SCHD (
|
||||
FLID VARCHAR(32) NOT NULL PRIMARY KEY, -- AODB 实例 ID(Number(1-12));不得由航班号或资源号推断
|
||||
OPERATION_DAY DATE NULL, -- 运营保障日:未由日计划收录时为 NULL;非空后不可改变(INV-12)
|
||||
STATE VARCHAR(8) NOT NULL, -- ACTIVE/DELETED,无第三态;删除只由 FDEL 或受控历史清理触发(INV-15)
|
||||
OPERATION_DAY DATE NULL, -- 运营保障日:未由日计划收录时为 NULL;非空后不可改变(implementation「航班身份与运营日」)
|
||||
STATE VARCHAR(8) NOT NULL, -- ACTIVE/DELETED,无第三态;删除只由 FDEL 或受控历史清理触发(US-06、US-14)
|
||||
STATE_VERSION BIGINT NOT NULL DEFAULT 0, -- 航班写代次;成功写入时推进
|
||||
LAST_MSG_ID BIGINT NULL, -- 最近一次成功写入的消息 ID
|
||||
-- ==== SCHD.FLTR 标量字段(保持 AODB 字符串原样;未携带不隐式清空,INV-14)====
|
||||
-- ==== SCHD.FLTR 标量字段(保持 AODB 字符串原样;未携带不隐式清空,US-04 AC2)====
|
||||
ALCD VARCHAR(64) NULL, -- 航空公司代码
|
||||
ALSC VARCHAR(64) NULL, -- 航空公司简称
|
||||
FLNO VARCHAR(64) NULL, -- 航班号(展示用,不参与身份判定)
|
||||
@@ -104,12 +104,12 @@ CREATE TABLE FLIGHT_SCHD (
|
||||
MAXP VARCHAR(64) NULL, -- 最大旅客数
|
||||
CSOP VARCHAR(64) NULL, -- 共享承运人代码
|
||||
CSFT VARCHAR(64) NULL, -- 共享航班号
|
||||
MAID VARCHAR(32) NULL, -- 共享主航班 FLID:主/共享关系的事实来源;MAFL 由它派生(INV-21、INV-22)
|
||||
MAID VARCHAR(32) NULL, -- 共享主航班 FLID:主/共享关系的事实来源;MAFL 由它派生(US-06 AC2)
|
||||
ESTT VARCHAR(64) NULL, -- 预计时间
|
||||
ACTT VARCHAR(64) NULL, -- 实际时间
|
||||
STND VARCHAR(64) NULL, -- 备降站/机位
|
||||
PHAG VARCHAR(64) NULL, -- 地服代理(值机)
|
||||
CNCL VARCHAR(64) NULL, -- 取消时间;受控历史清理的判定依据之一(INV-15、INV-18)
|
||||
CNCL VARCHAR(64) NULL, -- 取消时间;受控历史清理的判定依据之一(US-14 AC2)
|
||||
REMC VARCHAR(256) NULL, -- 备注
|
||||
BOTM VARCHAR(64) NULL, -- 摆渡车时间
|
||||
LACL VARCHAR(64) NULL, -- 行李确认时间
|
||||
@@ -136,14 +136,14 @@ CREATE TABLE FLIGHT_SCHD (
|
||||
);
|
||||
CREATE INDEX idx_flight_schd_opday ON FLIGHT_SCHD (OPERATION_DAY);
|
||||
CREATE INDEX idx_flight_schd_state ON FLIGHT_SCHD (STATE);
|
||||
-- OPERATION_DAY 不可变(INV-12)的库层强化方式:快照 upsert 一律带
|
||||
-- OPERATION_DAY 不可变(implementation「航班身份与运营日」)的库层强化方式:快照 upsert 一律带
|
||||
-- WHERE OPERATION_DAY IS NULL OR OPERATION_DAY = :day,行级条件更新即满足;
|
||||
-- 不使用触发器(Oracle/PG 双方言成本)。
|
||||
|
||||
-- ⑤ 资源明细表 ×8 + 路线点表:每 FLID 多行;每次完整状态写入按该 FLID 先删后插,
|
||||
-- 以完整合并结果为准(INV-14)。ORDINAL 是持久化顺序(从 1 起),SOURCE_SEQ 是上游
|
||||
-- 以完整合并结果为准(implementation「字段与集合」)。ORDINAL 是持久化顺序(从 1 起),SOURCE_SEQ 是上游
|
||||
-- 序号(允许为空或重复);相同资源号不代表同一条分配,禁止按资源号去重。删除一律
|
||||
-- 标记在主行 STATE,明细物理清除只发生在受控历史归档(INV-15)。
|
||||
-- 标记在主行 STATE,明细物理清除只发生在受控历史归档(US-14)。
|
||||
-- ⑤-1 登机门 GTDT
|
||||
CREATE TABLE FLIGHT_GATE (
|
||||
FLID VARCHAR(32) NOT NULL,
|
||||
@@ -278,13 +278,13 @@ CREATE INDEX idx_flight_delay_flid ON FLIGHT_DELAY (FLID);
|
||||
CREATE INDEX idx_flight_bridge_flid ON FLIGHT_BRIDGE_OP (FLID);
|
||||
CREATE INDEX idx_flight_chock_flid ON FLIGHT_CHOCK_OP (FLID);
|
||||
|
||||
-- ⑥ 统一投递事件 outbox(C-9、INV-17b):KAFKA:schd 发整态、KAFKA:msg 只通知变化;
|
||||
-- ⑥ 统一投递事件 outbox(C-9、INV-3):KAFKA:schd 发整态、KAFKA:msg 只通知变化;
|
||||
-- tombstone 仅在 ACTIVE→DELETED 时与删除同事务登记,投递失败按退避持续重试。
|
||||
-- 目标级全序投递是当前实现,按 FLID 保序见 CLM-7。
|
||||
-- 目标级全序投递是当前实现,按 FLID 保序见 CLM-3。
|
||||
CREATE TABLE MSG_EVENT (
|
||||
EVENT_ID BIGSERIAL PRIMARY KEY, -- 对 KAFKA:msg 是稳定事件身份并决定投递顺序;对 KAFKA:schd 是每次接受 upsert 时替换的写代次
|
||||
TARGET VARCHAR(30) NOT NULL, -- KAFKA:msg / KAFKA:schd
|
||||
PARTITION_KEY VARCHAR(32) NOT NULL, -- 当前恒为 FLID(Q1 定案前为假定,C-29)
|
||||
PARTITION_KEY VARCHAR(32) NOT NULL, -- 当前恒为 FLID;msg 单分区下不参与路由(CLM-3)
|
||||
EVENT_TYPE VARCHAR(16) NOT NULL DEFAULT 'UPSERT', -- UPSERT / TOMBSTONE
|
||||
STATE_VERSION BIGINT NOT NULL, -- 发布时航班版本;schd 聚合按 FLID 只进不退
|
||||
PAYLOAD_JSON TEXT NOT NULL, -- TOMBSTONE 时至少含 FLID/STATE_VERSION/DELETED
|
||||
@@ -304,7 +304,7 @@ CREATE UNIQUE INDEX uq_schd_event
|
||||
|
||||
-- ⑦ 请求状态机:只有 RESP 完成 RQFD 请求,按(运营日、发送方、请求类型)匹配最新一条
|
||||
-- 开放请求。同类请求只留一条有效,新请求置旧请求为 EXPIRED。登记、超时与应答匹配
|
||||
-- 尚未实现(G-REQ-TRACK、C-24)。
|
||||
-- 尚未实现(G-REQ-TRACK、G-REQ-OPEN-UNIQUE)。
|
||||
CREATE TABLE REQ_TRACK (
|
||||
REQ_ID BIGSERIAL PRIMARY KEY,
|
||||
REQ_TYPE VARCHAR(20) NOT NULL,
|
||||
|
||||
@@ -18,9 +18,9 @@ Flyway 配置**;PG 路径使用 `classpath:db/migration`,两者互不混用
|
||||
- 单基线:`V1__flight_state_baseline.sql` 的 11g 等价 DDL——
|
||||
PIPELINE_LOCK/INBOX_CURSOR/PROC_STATE/MSG_EVENT/REQ_TRACK/FLIGHT_SCHD + 9 张明细表/SCHD_SNAP_LOG;
|
||||
`NUMBER`/序列替代 `BIGSERIAL`、`TIMESTAMP WITH TIME ZONE`、`VARCHAR2` BYTE/CHAR 语义钉死。
|
||||
- `INSERT ... ON CONFLICT` 改 11g MERGE(OPERATION_DAY 不可变条件,`INV-12`)。
|
||||
- `INSERT ... ON CONFLICT` 改 11g MERGE(OPERATION_DAY 不可变条件,implementation「航班身份与运营日」)。
|
||||
- 空串按 NULL 的语义回归:显式清空的 presence 信息不得被 11g 空串语义吞掉
|
||||
(字段清空语义见 `Q11`;定案前按「未携带不清空」实现,`INV-14`)。
|
||||
(增量报文未携带不清空见 `US-04` AC2,日计划清除语义见 `C-6`)。
|
||||
- 11g 无部分索引:`uq_req_open` / `uq_schd_event` / `idx_proc_backfill_due` 三个带 `WHERE`
|
||||
的索引必须换成等价的函数索引或冗余列方案,不能照搬 PG 定义。
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import java.util.UUID
|
||||
|
||||
/**
|
||||
* 在真实 PostgreSQL 上验证历史清退的原子性:归档确认后若在删当前态之前失败,
|
||||
* 同事务登记的 tombstone 必须一起回滚(`INV-17`、`INV-18`)。
|
||||
* 同事务登记的 tombstone 必须一起回滚(`D1`)。
|
||||
*/
|
||||
class HistorySweepPurgePgTest {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user