Table of Contents

Index 使用指南——点查 Hash / 有序 BTree / SkipList

给谁看:需要索引(点查/范围/前缀批量删)的 KV 组合开发者 回答什么:HashIndex vs BTreeIndex vs SkipListIndex 怎么选 / Settings 全参数 / KeyResolver 闭环 / 持久化两形态 / 恢复 本篇只讲机制——帧字节布局见源码;性能数字见 perf/structures-perf-baseline.md 定位:Structures/ 6 子族之一,派生数据(与 Ring=真相源 组合为 KV)


0. 一句话总纲

索引 = 派生数据 + KeyResolver 闭环判等 + 自建主存储(PersistenceKind 可关)。

  • 写编排正序:先 ring.Write 得地址 → index.Insert 指向;反序 = 索引指向不存在数据。
  • 两族持久化均为自建主存储(内置后台 dump + 帧恢复),区别在帧策略—— Hash 走链 + N 版轮替,Sorted 固定锚点覆写。不配也安全,走全量重放 fail-safe。
  • 两族不设公共基类——选族即选消费形态:有序遍历/range scan 选比较族,极省内存点查选探测族。

1. 定位

1.1 派生数据

索引是派生数据(可重建,存在性 = 优化非正确性)——索引丢失不损数据,只损恢复速度。 Ring 是数据真相源(record 流),索引是派生数据(可重建)。写编排正序:先真相源写、再索引插; 反向 = 索引指向不存在的数据。读两段合口径:先经索引发现地址,之后永久直达取值(跳过 hash)。

1.2 地址一等公民

16B LogicalAddress 是唯一的取值句柄——先经索引发现地址,之后永久直达取值(跳过 hash); 判等/哈希只比较地址本身。地址即句柄:ring.GetValue(addr) 永久零税,无需会话/索引保护。

1.3 三选一决策树

需要有序遍历 / range scan / 前缀批量删?
├─ 否 → HashIndex(点查为主,内存最省,tag-only 桶不物化 key)
└─ 是 → 写并发模式与记录尺寸?
    ├─ 高写并发 + 小记录 → SkipListIndex(无分裂重排,CAS 塔链)
    └─ 读重 / 大记录 / 紧凑内存 → BTreeIndex(B+树扁平节点缓存全 L3 常驻)
        └─ 需字节字典序(RocksDB/LMDB 同款范围通用序)→ ByteOrderBTreeIndex
选项 优势 注意
HashIndex tag-only 桶不物化 key,内存最省 构造必注入 KeyResolver(判等闭环);无有序遍历
BTreeIndex B+树扁平节点缓存全 L3 常驻(100k 条 ≈ 2.2MB) 内部操作闸读写全互斥;游标仅 Forward
SkipListIndex 无分裂重排(高写并发小记录最优) arena 驻留节点(100k 条 ≈ 6.5MB);游标仅 Forward
ByteOrderBTreeIndex 字节字典序范围通用序 sealed 子类,比较器固定非可注入

2. Settings 全参数

2.1 三 Settings 对比

参数 所属 Hash 默认 BTree 默认 SkipList 默认 说明
PersistenceKind 基类 Builtin Builtin Builtin None=纯内存+重放;Builtin=自建主存储
PersistencePolicy.Interval 基类 30s 30s 30s 后台 dump 时间间隔(自上次 dump 起)
PersistencePolicy.EntryDeltaThreshold 基类 10_000 10_000 10_000 条目增量水位阈值(任一命中触发 dump)
PersistenceKeepVersions 基类 2 2 2 版本保留数(N 版轮替,对齐 Mirror/Metadata 家族)
HashTableCapacity Hash 专属 1<<14 哈希表初始桶数(2 的幂,超 0.7 自动翻倍增长)
OverflowPoolCapacity Hash 专属 1<<12 初始溢出池桶数(增长换代新池=新表/2,下限 1024)
NodeSize BTree 专属 256 节点大小(实际取 max(结构体大小, 256, 本值))
MinFillPercent BTree 专属 50 最小填充率(节点合并触发阈值——百分比)
MaxLevel SkipList 专属 16 塔最大层数(head 哨兵按满层建;几何层分配 p=1/2 封顶)
HighLevelCacheThreshold SkipList 专属 8 高层缓存阈值(≥此值全量内存缓存——塔高层稀疏,代价低)
MaxRetryCount SkipList 专属 3 CAS 插入最大重试次数(塔链竞争失败重试上限)
SafeReclaimDelayMs SkipList 专属 1000 物理回收延迟(ms——等 epoch 退出后回收被删节点)
NodeCacheInitialCapacity Sorted 基类 1024 1024 节点缓存初始槽位(非上限,按数据量增长至索引量级)

2.2 构造形态

三 Settings 均接受 StorageEngineOptions 直构(对齐 BlittableRingSettings/EntryLogSettings 双 ctor 形态):

var ringSettings    = new BlittableRingSettings(new StorageEngineOptions("kv-ring", 64L << 20));
var hashSettings    = new HashIndexSettings(new StorageEngineOptions("kv-hash", 64L << 20));
var btreeSettings   = new BTreeIndexSettings(new StorageEngineOptions("kv-btree", 64L << 20));
var skipListSettings = new SkipListIndexSettings(new StorageEngineOptions("kv-skiplist", 64L << 20));

基类(ProbingIndexSettings/SortedIndexSettings)收持久化机制配置(Kind/Policy/KeepVersions), 子类只填格式布局参数(对齐 LogBase/RingBase/MirrorBase:机制容器在基类,子类只实现 codec)。 segment 增长上限固定 1G(AlignmentConst.Alignment1G)。


3. 构造与生命周期

3.1 [RingKey] 源生成器

开放泛型(HashIndex<TKey>/BTreeIndex<TKey>/SkipListIndex<TKey>)的构造是 protected internal—— 消费面只经 [RingKey] 生成的封闭薄类(编译期封闭 + CreateAsync 工厂)。内置 long 已声明(TC.Tier.Runtime 程序集),HashOfLong/BTreeOfLong/SkipListOfLong 开箱即用;自定义 Key 在消费程序集加一行:

[assembly: RingKey(typeof(OrderId))]   // 产出 RingOfOrderId / HashOfOrderId / BTreeOfOrderId / SkipListOfOrderId

3.2 CreateAsync 工厂(三索引)

using var fs    = TierFs.New("memory:");
await using var ring  = await RingOfLong.CreateAsync(ringSettings, fs);
await using var hash  = await HashOfLong.CreateAsync(fs, hashSettings, ring);         // KeyResolver=ring(判等闭环必注入)
await using var btree = await BTreeOfLong.CreateAsync(fs, btreeSettings, ring);       // KeyResolver=ring(恢复重放需要)
await using var skipl = await SkipListOfLong.CreateAsync(fs, skipListSettings, ring); // KeyResolver=ring(恢复重放需要)
  • CreateAsync = 构造 + Initialize + WaitForReadyAsync(一步就绪——LifecycleBase 编排)。
  • HashIndexKeyResolver 构造期必注入(tag 命中后回读真 key 校验——判等闭环硬依赖, null 抛 ArgumentNullException:"探测族判等闭环强依赖 IKeyResolver")。
  • SortedIndexKeyResolver 可选注入——判等不需要(key 物化条目内),恢复重放需要; 有重放窗口而无 resolver 者,恢复核心 fail-fast:"比较族重放窗口需要 IKeyResolver"。
  • ByteOrderBTreeIndex<TKey>BTreeIndex<TKey> 的 sealed 子类,内置 KeyByteOrderComparer<TKey> (字节字典序——RocksDB/LMDB 同款范围扫描通用序,非可注入,类型语义即排序语义)。

3.3 KeyResolver 闭环

判等机制 KeyResolver 角色
HashIndex tag 命中后回读真 key 比对(FASTER 判等闭环) 构造期必注入——tag 只是加速器,冲突会假阳性
BTreeIndex key 物化条目内,比较即路由 可选——恢复重放需要(拉流自建节点)
SkipListIndex key 物化条目内,比较即路由 可选——恢复重放需要(拉流自建节点)

IKeyResolver<TKey> 三方法:

  • TryGetKey(addr, out key)——按地址读单条 key(false = 读不到/无效地址/record 无效)。
  • GetFlushedWatermark()——真相源已落盘水位 W(主存储 dump 的 footer 锚点)。
  • ScanAsync(begin, end, ct)——范围扫描吐 (Key, Address, IsTombstone) 三元组(墓碑感知重放)。

3.4 写读 KV 编排

// 写编排(正序两步写——先真相源得地址,再索引插指向)
static LogicalAddress KvPut(RingOfLong ring, IIndex<long> index, long key, ReadOnlySpan<byte> value)
{
    var addr = ring.Write(key, value);               // ① 真相源先写,得地址
    index.Insert(key, addr, LogicalAddress.Empty);   // ② 索引后插(指向该地址)
    return addr;
}

// 读编排(两段合口径——索引命中得地址,真相源按址取值)
static bool KvTryGet(RingOfLong ring, IIndex<long> index, long key, Span<byte> buf, out int len)
{
    len = 0;
    var addr = index.Find(key);               // ① 索引命中得地址
    if (addr == LogicalAddress.Empty) return false;
    len = ring.GetValue(addr, buf);           // ② 真相源按址取值
    return true;
}

// 批量读(EnterScope——一批一进出,零逐查 epoch 进出开销 ~10ns/op)
using var scope = index.EnterScope();
Span<long> addrs = stackalloc long[64];
index.FindBatch(keys, addrs);                // 批量点查(同一轮 epoch 内完成)
for (int i = 0; i < keys.Length; i++)
    if (addrs[i] != LogicalAddress.Empty)
        ring.GetValue(addrs[i], buf);

终态读形态(批量最快):index.EnterScope() 一批一进出 + IndexScope.Find + Ring.GetValueSpan (零拷贝切片——仅同步栈内合法;跨 await 必须走拷贝 API)。


4. API 详表

4.1 IIndex<TKey> 五成员最小协议

成员 签名 返回 / 语义
Find LogicalAddress Find(TKey key) 命中 = value 逻辑地址;Empty = 不存在
Insert LogicalAddress Insert(TKey key, LogicalAddress valueAddress, LogicalAddress beginAddress) 同 key 覆写 value 不增计数;返回插入后地址
Delete bool Delete(TKey key) true = 真删到;false = 不存在
EntryCount long EntryCount { get; } 条目数(写者维护——O(1))
IndexSize long IndexSize { get; } 索引内存占用估算(字节)

beginAddress 参数:探测下限地址(重放路径约定)——槽内旧条目地址小于它视为陈旧,可覆写落位。 Sorted 族插入不消费此参数(保留接口对称)。

4.2 两族共有扩展 API

成员 签名 语义
BeginAddress LogicalAddress BeginAddress { get; } 探测/结构起始地址(引擎 MinAddress)
EnterScope IndexScope EnterScope() 读保护 scope(ref struct——创建即 Resume epoch,Dispose 即 Suspend)
IndexScope.Find LogicalAddress Find(TKey key) scope 内单查(走 FindNoEpoch——省逐次 epoch 进出 ~10ns/op)
FindBatch void FindBatch(ReadOnlySpan<TKey> keys, Span<LogicalAddress> results) 批量点查(同一轮 epoch 内完成——零逐查 Resume/Suspend 开销)
EnterEpoch / ExitEpoch void EnterEpoch() / void ExitEpoch() epoch 读保护协议(IEpochProtected——Session 读 scope 聚合入口转发)
CheckpointFrame bool CheckpointFrame() 显式帧落盘(公开检查点触发口——语义同内部 TryDump
MainStorageAppliedLastRecovery bool { get; } 诊断位:上次恢复是否走了主存储载入路径(false = 全量重放 fail-safe)

4.3 HashIndex 专属 API

成员 签名 语义
GrowIndex void GrowIndex() 扩容(装载超 0.7 由 Insert 自动触发——函数式换代表,均摊 O(1)/插)
DiagDumpBucketRaw internal string DiagDumpBucketRaw(TKey key) 诊断:dump 某 key 所在桶全部原始槽位(排障用)
DiagScanKeyAllSlots internal string[] DiagScanKeyAllSlots(TKey key) 诊断:全表扫描收集某 key 的全部实体(幽灵实体定位——O(全表))

HashIndex 内核:128B HashBucket(8 槽 × 16B LogicalAddress)、表+溢出池同代原子对 InternalHashTable、 条带写锁(64 条——Insert/Delete 按 hash 低位分条带)+ 溢出链条带锁(16 条)。 槽稳定读协议:16B LogicalAddress 普通读可能撕裂——双读比对一致才接受,不一致走 CAS 环读兜底。

4.4 SortedIndex 专属 API

成员 签名 语义
CreateScanCursor IIndexScanCursor<TKey> CreateScanCursor(ReadDirection direction) 有序遍历游标(仅 Forward——叶链/层 0 链单向,Backward 显式抛 NotSupportedException
TryGetMax bool TryGetMax(out TKey key, out LogicalAddress value) 键序最大条目(Latest 语义——时序/版本链"最新点查"),O(log n)
TryGetFloor bool TryGetFloor(TKey key, out TKey floorKey, out LogicalAddress value) 键序 ≤ key 的最大条目(floor/前驱语义——Prometheus @ 采样/前驱点查),O(log n)
TruncatePrefix long TruncatePrefix(TKey boundExclusive) 键序前缀批量删(删除全部 key < bound 的条目——retention trim 专用,批量 O(覆盖区))

BTreeIndex 内核:160B BTreeNode(9 槽定长 (TKey, LogicalAddress) + Next 叶链指针 + 叶/根标志位)。 SkipListIndex 内核:arena 驻留变长节点(32B 头 + 16B×实际层高),addr→指针缓存(零结构拷贝下降)。 操作闸(MonitorScope):全部公开操作(读/写/游标单步/后台 dump)经内部操作闸互斥—— 单操作粒度全互斥,结构自保证任意并发组合安全。粒度 = 单操作(游标 = 单步推进),长扫描经逐步持闸与写者交错。

4.5 IIndexScanCursor<TKey> 游标

成员 签名 语义
CurrentKey TKey CurrentKey { get; } 当前条目的 key(从 RecordStore 读回真 key,非占位符)
CurrentValue LogicalAddress CurrentValue { get; } 当前条目的 value 逻辑地址(指向 record)
SeekLowerBound bool SeekLowerBound(TKey key) 定位到首个 key ≥ key 的条目(lower_bound;可在迭代前或迭代中调用)
MoveNext bool MoveNext() 推进到下一条目(操作闸单步粒度——与写者交错;首次调用定位起点)
MoveNextAsync ValueTask<bool> MoveNextAsync(CancellationToken ct) 异步推进(同步委托 MoveNext——无真异步 IO)
Dispose / DisposeAsync void Dispose() / ValueTask DisposeAsync() 释放游标(幂等——游标不持有页资源,仅置标记)

范围查询 [from, to) = SeekLowerBound(from) + 逐条 MoveNext 推进至 key ≥ to 停(无需从头跳过被略键)。 BTree 游标:最左叶定位 + 叶链 Next 前向推进 + 跨空叶(删除不重平衡可留 Count=0 的叶)。 SkipList 游标:层 0 链前向推进 + lower_bound 塔链定位。


5. 自定义注入工厂

注入点 接口 必填 用途
KeyResolver IKeyResolver<TKey> Hash 必填 / Sorted 可选 判等闭环核心——Hash tag 命中后回读 Ring 拿 key 比对;两族恢复拉流重放
KeyComparer IKeyComparer<TKey> 可选(默认 KeyComparer<TKey> 全字段判等) 哈希/比较路由;Hash 点查 latest 版本语义可注入版本无关比较器
Codec IProbingIndexCodec / ISortedIndexCodec 族私有(子类内置单例) 主存储帧格式(magic/version/kind/字段布局/CRC 偏移)——禁跨族共用
PersistenceKind ProbingIndexPersistenceKind / SortedIndexPersistenceKind 默认 Builtin None=纯内存+重放;Builtin=自建主存储
PersistencePolicy ProbingIndexPersistencePolicy / SortedIndexPersistencePolicy 默认 30s / 10k 条目 后台 dump 触发策略(时间间隔 / 条目增量水位阈值,任一命中)
RecoveryHints ProbingIndexRecoveryHints / SortedIndexRecoveryHints Initialize(hints) 注入 重放窗口 [Begin, End)——组合层锚点 W
Epoch LightEpoch 可选(null=索引自建并持有) 读保护(IEpochProtected——Session 读 scope 聚合入口)
ITransactionParticipant 显式实现(六件套) 内置 2PC:Prepare / ConfirmCommitted / OnCommitted / Abort

IKeyResolver 闭环TryGetKey(addr, out key) 按 address 回读真 key(Hash 判等);GetFlushedWatermark() 返回已落盘水位 W(主存储 dump footer 锚点——组合层契约:Insert 先于落盘、失败回滚,已落盘必已入索引); ScanAsync(begin, end, ct) 异步迭代器流式回源(冷区真异步 IO 不阻塞,墓碑感知)。

ISortedIndexCodec / IProbingIndexCodec 契约律(对齐 ILogCodec):基类只管机制(帧走链/CRC 总验收/体长定界), 格式知识(magic/version/kind/字段布局)全在实现侧——接口只传机制需要的原始值,不暴露任何具体格式类型。

ITransactionParticipant 六件套(显式实现):Prepare(seq) / PrepareAsync(seq, ct) / ConfirmCommitted(seq) / OnCommitted(seq, callback) / Abort(seq) / AbortAsync(seq, ct)。 协调器 = Transactions/TransactionLog(写独立 commit record 文件驱动);上层编排归 Session(session.md)。

5.1 RecoveryHints 注入

// 组合层职责:恢复 Ring 后从 opaque 取锚点 W,连同 Ring 尾注入 index 恢复窗口
var opaque = ring.ReadOpaqueMeta();                              // opaque 容器(ReadOnlySpan——同步栈内消费)
//   W 由组合层自行编解码(如 8B LogicalAddress 编码在容器内);IsEmpty = 无锚点/损坏
var begin  = DecodeW(opaque, ring.BeginAddress);                 // 无锚点 → W = BeginAddress(宁可旧多重放)
var end    = ring.TailAddress;                                   // Ring 尾(半开区间不含)
var hints  = new ProbingIndexRecoveryHints(begin, end);          // [Begin, End) 重放窗口
hash.Initialize(hints);                                         // 注入恢复窗口
await hash.WaitForReadyAsync(ct);                                // 等恢复完成(载帧物化或重放)

// 每次 dump/重放后更新锚点:CheckpointFrame 落帧 → W 编码进 opaque(随 meta 写原子落盘)
hash.CheckpointFrame();                                          // 显式帧落盘(W = ring 已落盘水位)
ring.SetOpaqueMeta(EncodeW(watermark));                          // 更新 opaque 锚点 W(ReadOnlySpan<byte>)

HasReplayWindow = End > Begin 才重放(默认 default hints = Empty/Empty = 不重放,空结构首开)。 W = Begin 走同一条 ScanAsync 路径——降级全量重建不是第二条路。


6. 持久化机制(两族对比)

两族均自建主存储(内置后台 dump worker + 帧恢复载入),区别在帧策略:

维度 HashIndex SortedIndex(BTree/SkipList)
帧定位 帧走链——从 MinAddress 前向逐帧扫描选最新完整帧 固定锚点——首开预留 84B 锚点槽(MinAddress),dump 覆写
版本管理 N 版轮替(PersistenceKeepVersionsReclaimHead 回收最老帧) 单帧覆写(帧长固定,无版本链需求)
帧体内容 几何 32B + 桶区 size×128B + 溢出池 ofbCap×128B(fuzzy 逐槽原子读拷贝) 几何 32B(root/head 指针 + 计数 + structSize)——节点本就写时持久化在引擎内
物化 整读体重建表+溢出池 → 重数实收(fuzzy 帧内混入条目以实收为准) 设根 + 引擎读回根节点 → 计数(recountNeeded 时全树/全链实收)
dump 与写并发 fuzzy 一致性(128bit 原子读拷贝,跳 Tentative 只收 Occupied;换代 stale-but-valid) 操作闸全互斥(脏节点延迟写回——插入/分裂路径零引擎写,dump 时批量写回)
后台触发 BackgroundWorkerLoop(1s 轮询粒度,到点按策略判定) 同左
帧格式 三段式 [头][体][尾]——头先行校验、体自定界、尾总验收 同左(codec 族私有)
关闭 PersistenceKind=None = 纯内存 + Ring 全量重放 同左

不配也安全——PersistenceKind=None 走全量重放 fail-safe(恢复核心三级回退兜底)。 帧格式(自校验三段式,对齐段表同款理由——写入的是外部存储就不完全相信):

  • 头(先行校验,快速失败):magic/version/flags/kind + 体长——不符立即判无效,不读体。
  • 体(自定界):结构内容由族自己的几何块推出边界。
  • 尾(总验收):footerMagic + 水位 W + CRC64(覆盖头+体+尾前缀)——有效性最终裁决。

W = IKeyResolver.GetFlushedWatermark()(已落盘水位——组合层契约:Insert 先于落盘、失败回滚, 已落盘必已入索引;在途 = 未落盘 = 崩溃即失)。调用 CheckpointFrame 前须先落盘真相源(如 Ring FlushUntil(Tail))。

HashIndex fuzzy 一致性(FASTERKV 同构):逐槽 128bit 原子读拷贝(槽 = LogicalAddress 16B 原子单元,零撕裂)

  • 跳 Tentative 只收 Occupied + 换代 stale-but-valid(dump 期间 GrowIndex 照常)。正确性靠组合一致性底座: dump 表覆盖 [?, W] 完整折叠;> W 混入条目靠重放 (W, End] 幂等收敛或恢复后 ring 裁决惰性失效。

SortedIndex 节点即数据教义**:节点写时持久化在自持引擎(节点变更即 WriteNodeContent 记脏标记, dump 时批量写回引擎——插入/分裂路径零引擎写)。物化只需设根 + 引擎读回根节点,零逐节点流。

BTreeIndex 脏节点延迟写回_dirtyNodes 地址集(只存地址 16B/项——值快照版 240B×n 超 L3 污染点查,已销案); 写回从缓存取值——不变式:每次 WriteNodeContent 前节点必已入缓存(RefreshCache/根特例 _cachedRoot), 读路径 miss = 从未变更 = 引擎内容最新,无需脏兜底。崩溃窗口(dump 前)由恢复重放 (W, End] 修复—— W = 上次 dump 水位,重放补齐全部变更,引擎旧链无害。

SkipListIndex arena 驻留:变长非托管块(32B 头 + 16B×实际层高)——工作集从 288B/节点 压到均值 ~64B/节点 (100k 条 28.8MB→~6.5MB,L3 常驻);块只增不搬移 = 指针恒稳。addr→指针缓存(LogicalAddressMap<nint>, 8B 值)——跳一跳 = 探测+指针直访,零结构拷贝。几何层分配 p=1/2 封顶 MaxLevel,与插入时间/条目数解耦。


7. 恢复协议

7.1 三级回退(统一生命周期)

① hints(组合层最强知识)——重放窗口 [Begin, End)
② 主存储最新完整帧(载入物化 + 重放 (W, End])——Hash 帧走链 / Sorted 固定锚点
③ 建空结构 + Ring 全量重放兜底(W=Begin 走同一条 ScanAsync 路径)

每级失败自动降级到下一级——恢复不变量不变(索引 = 派生数据,重建数据面 = 真相源 record 流)。

7.2 锚点 W 协议

① Ring.Create → 恢复水位;锚点 W = ring.ReadOpaqueMeta()
   (无锚点 / 损坏 / W 越过当前尾 → 回退 BeginAddress——宁可旧多重放)
② index.Initialize(hints) → hints.Begin = W, hints.End = ring.TailAddress
③ 主存储帧有效且 W∈[Begin,End] → 载帧物化 + 只重放 (W, End)
   无帧 / 损坏 / W 越界 → 建空结构 + 全量重放 [Begin, End)
④ 每次 dump/重放后 ring.SetOpaqueMeta(W) 即可

7.3 重放核心

恢复核心拉 KeyResolver.ScanAsync(W, End) 流逐条处理:

  • 普通记录 → Insert(key, addr, LogicalAddress.Empty)(同 key 覆写——流序折叠 = 最新写胜出)。
  • 墓碑记录 → Delete(key)(已删 key 不复活;窗口内旧 put 先插后删收敛)。

Sorted 族锚点槽预留(EnsureAnchorReserved):首开空引擎 = 第一个分配(MinAddress,节点分配自然在其后); 重开 = 旧锚点原位(MinAddress 恒为锚点——节点 ReclaimTail 不动 MinAddress)。

Hash 族帧走链(ScanFrames):从引擎 MinAddress 起,头(magic/version/kind)+ 帧长推导 + 尾 magic + CRC 总验收; 写中断的尾帧 CRC 不过即停——最新完整帧 = 最后一个通过验收的帧。账本同时重建(轮替用)。

恢复全程经 LifecycleBase 模板编排(RecoveryBase 派生——只填恢复算法钩子, 底层生命周期骨架是压测背书的信任边界,结构层只填钩子)。详见 meta.md(opaque 搭车)与 storage-engine.md(引擎恢复)。


8. 反模式

# 反模式 后果 / 正解
1 写编排反序:先 index.Insertring.Write 索引指向不存在的数据——必须先真相源写再索引插
2 增长后检查索引阈值 增长必须在插入之前检查(增长时刻表内条目必可经 KeyResolver 解析)
3 跨实例组合 DeleteOnClose=true 跨重启组合必须 DeleteOnClose=false(否则索引重建时真相源被清)
4 绕过 KeyResolver 判等 Hash tag 冲突静默错误——tag 只是加速器,命中后必读回真 key 校验
5 为 Sorted 手搭 checkpoint 引擎/存储 内置自建主存储(CheckpointFrame)——无需自搭
6 BTree/SkipList 游标传 Backward 叶链/层 0 链单向——NotSupportedException;倒序经 TryGetMax/TryGetFloor 组合
7 SortedIndex 重放窗口不注入 KeyResolver 恢复核心 fail-fast——构造期须注入(判等不需要,重放需要)
8 给地址直达读加会话/索引 地址即句柄——ring.GetValue(addr) 永久零税(ring.md
9 零拷贝 span 跨 await GetValueSpan 返回值仅同步栈内合法——跨 await 走拷贝 API(TryGetValue/GetValue
10 绕过 [RingKey] 直接继承开放泛型 封闭薄类是唯一消费面(外部直接 new = CS0122)
11 dump 前未落盘真相源 W = 已落盘水位——调用 CheckpointFrame 前须先 ring.FlushUntil(Tail)

9. 想深入?指路

想懂什么 去哪
总览(七种结构速查 + KV 组合三步上手) structures.md
Ring 真相源(写读/水位/截断/快照/内容自愈读) ring.md
Log WAL(组提交与回放/单写者) log.md
meta 统一协议(opaque 搭车/块格式/锚点 W) meta.md
Session 协调协议(写/读/检查点三 op、悬挂裁决) session.md
引擎使用(读写/水位/恢复/Compact) storage-engine.md
镜像/快照(WholeMirror/StreamSnapshot 基线形态) mirror.md / snapshot.md
版本链元数据(跨重启单值状态) versioned-metadata.md
性能基线(点查/写/恢复/并发扩展) perf/structures-perf-baseline.md
帧格式/主存储机制/恢复细节 源码注释(Structures/ProbingIndex/ + Structures/SortedIndex/

机制级细节(帧字节布局、dump 协议内部状态机、环防御阈值 MaxDescentPath、 槽稳定读双读比对协议、arena 驻留节点偏移契约)按需从源码与设计稿查阅—— 使用面不需要这些细节;能力全集以类型 XML 注释为准。