Table of Contents

产品一致性架构(TierKv / TierTimeSeries / TierBlob)

本文回答:三个产品怎么保证一致性、不丢不重、原子性与崩溃恢复——数据真源与派生结构的 分层、分配-持久分离、2PC 提交点语义、恢复对账,以及每产品的一致性语义边界与崩溃窗口。 它是 raft-consistency.md(产品 × raft) 的本地产品面(无 raft 时的一致性),也是 persistence-architecture.md(跨层 持久化)的产品消费面。

与使用指南的关系:tierkv.md / tierseries.md / tierblob.md 是怎么用;本文是为什么一致。 代码引用为仓库相对路径(Kv/… = src/KernLab.Tier.Products/Kv/…)。


0. 三产品共同骨架

三个产品建立在同一条底层教义上:数据结构是"数据真源 + 可重建派生结构"的两层模型, 原子性由 2PC(ITransactionParticipant)在提交点收口,恢复靠"持久化真源 + 重放对账"。

产品 数据真源(持久化权威) 派生/查询结构 水位/元数据真源 原子性载体
TierKv Ring(append-only 记录流) Hash/BTree/SkipList 主索引 + 可选范围索引(可重建) Ring opaque(版本高水位 + 过期扫描水位) Ring 2PC Prepare/Confirm + 索引 CAS 换绑可见性
TierTimeSeries Ring(样本 record 流) BTree 时间索引(键 (ts,addr)) VersionedMetadata(TrimmedUntil/计数) 水位 VersionedMetadata 作 2PC 参与者
TierBlob 数据 = StreamSnapshot;对象表 = 表引擎版本链记录帧流 无索引(地址直达) 对象表(meta O(1) 水位)+ 数据引擎三水位 表参与者 Prepare/Confirm/Abort + undo 链

1. TierKv

1.1 写路径原子性:append-only 换绑(Kv/TierKv.cs:14-22)

  • Ring 是数据唯一真源,主索引是可重建派生结构("派生重放教义"——任何索引切换/重建都不丢数据)。
  • 写 = 「Ring 追加新记录 → 索引 CAS 换绑地址」——不覆盖、版本历史天然保留;Delete = 墓碑 写入 + 索引清槽(恢复重放跳过已删 key)。索引换绑唯一写入口经版本保护区包络(TierKv.cs:362-387)。

1.2 会话读一致性三档(Kv/KvSessionContracts.cs:6-25)

  • None:读恒走索引看最新。ReadMyWrites(缺省):写立即应用 + 本地写集,读优先命中写集 (本会话最后一次写胜出他人并发覆盖)。Serializable:同步写临界区在版本保护下执行。
  • 会话创建即取单调版本号(会话单线程契约,内部零锁)。

1.3 多 key 原子批(全或无,TierKv.cs:472-536)

  • 会话侧 BeginAtomicBatch → 批内写走暂存(本会话读可见、他人与索引不可见)→ CommitBatchAsync / AbortBatch。
  • 引擎侧编排:Ring 追加全部暂存 → 2PC Prepare(整体落盘悬空)→ ConfirmCommitted(提交点)→ 索引换绑(可见性)。崩溃任一窗口全或无:Prepare 前/悬空期崩溃 = 恢复丢弃(索引零污染); Confirm 后崩溃 = 记录已提交、索引由派生重放自建(TierKv.cs:464-468)。

1.4 完成语义三档与组提交(Kv/TierKvContracts.cs:12-24)

  • FireAndForget(内存档,持久化由 checkpoint 承担)/ Committed(写级不丢显式旋钮)/ WaitForPending(会话 pending 收口)。
  • 组提交摊薄 fsync(FlushGroupedAsync,TierKv.cs:756-808——floor 只推实刷值,防持久化契约破坏)。

1.5 Functions RMW(读-改-写,TierKv.cs:580-633)

三流统一「读折叠 → Format → 追加 → 索引 CAS 换绑」;miss→Initial、hit→InPlace 先行(false 回落 Copy)、折叠 false→Error 零写入;旧记录恒保留(版本历史是多出能力)。

1.6 TTL(惰性读删 + 时钟防线,TierKv.cs:93-115,1752-1767)

  • 值帧统一 [tag 0xC8][8B 过期 ticks][payload]("全量封装不变式")。
  • 惰性读删:读路径判定过期即不存在。
  • 时钟倒流防线(公开面易忽略的关键):NowUtcTicks 经单调水位钳制——墙钟后跳/倒流时已判 过期项不复活(NTP 校时跳变下的 TTL 语义防线)。

1.7 CAS(地址即版本 / fencing token,TierKv.cs:1816-1881)

  • 地址版/值版;_casGate 串行 compare→append→换绑窗口。
  • 关键语义边界:并发 CAS 恰一成功;但 CAS×普通 Put 仍是 last-writer-wins(CAS 不是全局 写锁——防丢失保证仅对其他 CAS 调用方成立)。过期绑定视为不存在。TTL+CAS = 「比较 + 写入 + 挂过期」单一原子单元,获取即租约(fencing token = NewAddress)。

1.8 版本号单调性(Kv/KvVersionAllocator.cs)

  • opaque 24B 布局 = HighWater(8B) + Magic "TKV1"(4B) + SweepSegId + SweepOffset——魔数代际守卫, 演进换值;NextVersion() 全局单调(Interlocked);Restore 只升不降守卫。
  • 持久化 = 版本高水位搭 Ring opaque 随 2PC Prepare 同块原子落盘(TierKv.cs:502-508); 恢复续接先于 Ready(分配版本恒大于历史)。

1.9 恢复 / 检查点(帧物化 + 增量重放,TierKv.cs:243-296,833-870)

  • 检查点三拍:① Ring 全量落盘 → ② 主索引帧落盘 → ③ 版本图入 opaque 随 2PC Prepare 原子落盘。
  • 崩溃后重开 = 索引帧物化(O(索引))+ 增量重放 (W, Tail](非全量);帧缺失/损坏 fail-safe 回退全量重放(数据等价)。恢复正确性不依赖索引(派生物可重建)。
  • PITR 回滚到指定历史版本排期 W6.1+。

1.10 回收与过期强删(TierKv.cs:1488-1610)

  • ReclaimAsync:低频全表扫判定存活 → 存活最小地址为安全下界 → TruncatePrefix 逻辑回收; 未落盘尾不回收(clamp 至 FlushedUntil)。
  • 过期强删:水位推进式(不做逐条墓碑化)——增量扫描找最老未过期绑定 → 截断前缀 → 扫描 水位随 2PC Prepare 原子落盘;"先截断后持久化"——崩溃窗口内水位落后于截断点 = 下轮重扫 (幂等安全)。过期与回收强删均不发事件。

1.11 Watch(无丢无重续传,TierKv.cs:1378-1429 + Kv/KvWatchHub.cs)

历史补扫 + 实时直通,地址开区间续传;锁内发布水位去重 + 有界通道慢订阅者断连(写路径零反压); 游标越回收线抛(etcd compacted 同义);恢复期重置发布水位。

2. TierTimeSeries

2.1 组合配方(TimeSeries/TierTimeSeries.cs:18-33)

Ring(数据真源,append-only)× BTree 时间索引(键 (ts,addr),乱序吸收/范围序/最新点查)× VersionedMetadata 水位(retention 边界原子持久)。单序列与 dense 多序列共用同一流程面。

2.2 写入 / 乱序吸收 / 守卫(TierTimeSeries.cs:137-162,211-220)

  • Append:Ring 写 record + 写后即知插索引(内存可见;落盘由显式 Flush/trim 前置 flush 承担)。
  • 乱序天然吸收(BTree 有序插入,无后台排序工序)。
  • fail-fast 两守卫:ts < TrimmedUntil(已回收区间,数据必丢)+ 超出 MaxOutOfOrderPast (防老样本钉住索引)。

2.3 查询一致性(TierTimeSeries.cs:311-320)

  • 索引在 = 索引序交付(正确时间序);Indexed=false 降档 = 地址序交付(显式降档契约—— 乱序写入下 Ring 地址序 ≠ 时间序,直接 Scan 会交付乱序流)。索引是正确性必需而非优化。

2.4 retention / 治理(截断先行、水位随后,TierTimeSeries.cs:480-535,671-708)

  • TruncateAsync:前置 flush → 索引前缀截断 → Ring TruncatePrefix → 水位持久(原子提交, 单调不回退)。
  • 崩溃窗口语义:截断先行、水位随后——窗口 = 已截断但水位未推进 → 重启对账以数据事实 为准(TrimmedUntil = min(水位, 首样本 ts))。后台 retention 循环(TTL/MaxBytes 取更晚锚); dense 逐序列推进。

2.5 dense 多序列(逐序列一致性边界,SeriesRegistry.cs:57-117)

每序列 O(1) 侧账(~64B),惰性注册 + 容量护栏;MinPinnedAddress(慢序列钉住——快序列回收 不越雷池);逐序列 trim → Ring 截断下限 = 全体序列钉住地址 min;恢复按 envelope SeriesId 路由。

2.6 恢复对账三步(spec §7,TierTimeSeries.cs:819-1018)

① 水位载入 → ② 索引启动(Ring Ready 后)→ ③ 对账:4a 悬空索引清理(地址 < Begin 或 > Tail)→ 4b 缺条重建(Ring 扫 [Begin, Tail) 不在索引的重插)→ 4c 水位 min 校正。崩溃窗口:record 已落、 索引未持久 → 恢复对账重插(at-least-durable)。

2.7 水位 2PC 参与者(TierTimeSeries.cs:813)

GetWatermarkParticipant() 返回水位标准 ITransactionParticipant——"业务效果与 trim 水位同域" (rollup 管道事务化形态,spec §10)——跨结构原子的现成入口。

3. TierBlob

3.1 组合配方与句柄模型(Blob/TierBlob.cs:13-21)

StreamSnapshot 数据引擎(对象 = CRC64 帧流,句柄 = 起始 LogicalAddress)× 对象表(版本链记录 帧流,meta 真相源)。帧扇区对齐不变式(物理 = 逻辑布局,重启恒等映射读回成立,定长读免 帧解析 +14 直达)——恢复正确性的地基。

3.2 对象登记持久化收口(句柄不丢,Blob/BlobObjectTable.cs:11-21,329-334)

  • 对象表 = 表引擎({name}.blob.meta)上每条登记/删除/标损 = 一帧 [Header 14B][payload 48B+补零][Footer 28B],CRC64 帧级自校验。
  • 每条登记 = 帧闭环 → 表引擎 Prepare(fsync + 水位 meta)→ ConfirmCommitted——表水位恒领先/ 对齐登记事实,任何崩溃窗口下已返句柄不丢。

3.3 崩溃对账(TierBlob.cs:420-484)

① 表重放(meta O(1) 水位 + 定步长逐帧重放,步长不齐 fail-fast)→ ② 数据尾由表推导(登记帧 几何权威,不采信 Backward 扫描——稀疏介质伪 footer 会把尾推上天)+ TruncateSuffix 纠偏 → ③ Active 对象逐个验帧(尾级 footer magic+TotalLength O(1) 或 DeepVerifyOnRecovery 整帧 CRC64) 损坏标墓碑显式可见。孤儿帧(Complete 后登记前崩溃)= 不可见,落在推导尾之上被后续写入 覆盖(at-least-once:崩溃 Put 由调用方重试产生新对象)。

3.4 Session 2PC(业务写 + 对象登记原子,TierBlob.cs:380-414)

GetParticipant() → BlobTableParticipant:Prepare = 表引擎 Prepare + 开启会话域(此后登记延迟 提交挂 undo 链);Confirm = 统一 fsync + 提交水位;Abort = 表帧尾截断 + 内存镜像逆序回退 (延迟删除回退 = 对象复活)。undo 链捕获 previous(BlobObjectTable.cs:155-170,246-263)。 会话期间 PutAsync/DeleteAsync 自动挂入事务。

3.5 写入单通道 / Complete / Abort(Blob/BlobSessions.cs)

  • 产品写闸 _writeGate 串行(结构层写尾单会话契约);不同对象并行写 = Runtime 迭代候选。
  • Complete/Abort 显形:Dispose 未 Complete = Abort(尾截断回滚);定长会话须恰好写满(不足/超过 Complete 拒绝并回滚);不定长流容量终判。
  • Delete = 表墓碑 + 空间保留;物理回收逐墓碑 extent 打洞 + 死亡前缀头截断(Active 区间零触碰)。

4. 底层依赖(统一契约)

依赖 提供
ITransactionParticipant(Contracts/Transactions) 2PC 统一契约:Prepare(落盘悬空)/ ConfirmCommitted(提交点)/ Abort(幂等回滚)
RingBase.Transaction Ring 2PC:Prepare 落盘数据+meta(Transport 回落宿主 = 1 次 fsync);Abort = TruncateSuffix 回退到上一提交边界
RingBase.Recovery 恢复四级回退:hints → meta(O(1)+KeySize 锚点+2PC 事务水位还原) → 引擎 CommittedTail → 扫盘找 torn write 边界
RingBase.Watermarks 7 个水位指针 CAS128 单调推进(无 TOCTOU)
VersionedMetadata 版本链存储(内存工作副本 + 磁盘版本链 + 多版本保留 Abort 零 IO)——TimeSeries 水位/Blob 对象表基座
SessionManager 组合域统一协调协议层(物化 → Prepare-all → Confirm-all、悬挂裁决)——Blob/TimeSeries 多域事务
EntryLog TierWal 的底层 WAL 结构(raft 复制日志面);三产品直接用 Ring,不直接用 EntryLog

5. 一致性语义核心主线(三产品统一)

  1. 数据真源与派生结构分层(Kv 索引 / TimeSeries 索引 / Blob 无索引)——"派生结构可重建、 正确性不依赖它"是全仓恢复语义的根。
  2. 分配-持久分离(Append/Put/Complete 返回即内存可见;持久化 = 显式 Flush / checkpoint / Committed / 2PC Prepare)——三产品同一惯例。
  3. 2PC 提交点语义(Prepare 落盘悬空 → Confirm 提交点 → 可见性;崩溃窗口 = 悬空丢弃 / 已确认保留)——原子批、Blob 登记、水位/业务同域均基于此。

6. 相关文档

  • 使用指南:tierkv.md / tierseries.md / tierblob.md(同目录)
  • 产品 × raft 一致性:raft-consistency.md
  • 跨层持久化稳定性:persistence-architecture.md
  • 底层结构:Ring/session/versioned-metadata(src/KernLab.Tier.Runtime/docs/)
  • 内部设计稿(不随包发布):tierkv-design / tc-tier-timeseries-spec / tierblob-spec / tierkv-ttl-cas-addressread-design