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编排)。 - HashIndex:
KeyResolver构造期必注入(tag 命中后回读真 key 校验——判等闭环硬依赖, null 抛ArgumentNullException:"探测族判等闭环强依赖 IKeyResolver")。 - SortedIndex:
KeyResolver可选注入——判等不需要(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 槽 × 16BLogicalAddress)、表+溢出池同代原子对InternalHashTable、 条带写锁(64 条——Insert/Delete 按 hash 低位分条带)+ 溢出链条带锁(16 条)。 槽稳定读协议:16BLogicalAddress普通读可能撕裂——双读比对一致才接受,不一致走 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 版轮替(PersistenceKeepVersions,ReclaimHead 回收最老帧) |
单帧覆写(帧长固定,无版本链需求) |
| 帧体内容 | 几何 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 原子读拷贝(槽 =
LogicalAddress16B 原子单元,零撕裂)
- 跳 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.Insert 再 ring.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 注释为准。