Table of Contents

TierKv 性能基线(产品 API 口径)

数字实测于本机(i5-12400 / .NET 8.0.31 / x64 RyuJIT AVX2 / BDN 0.13.12),文件系统=内存文件系统 (TierKv 侧 mem 卷 ≙ FASTER 侧 NullDevice——双侧全内存同形口径,引擎 IO 噪声为零); FASTER 对标为 FASTER.core 2.6.5 同进程同轮背靠背。

配置

  • 双侧同形:100k 预填 longlong(8B:8B),随机游走键热访问;TierKv 覆写口径 = KvCommitPolicy.FireAndForget(不刷盘 ≙ FASTER 内存 Upsert)
  • TierKv 实例:TierKvOfLongLong([KvStore] 生成封闭形态,缺省 Hash 主索引 + meta Managed)
  • 复现:
    dotnet run -c Release --project benchmarks/TC.Tier.Runtime.Benchmarks -- --filter "*TierKvVsFaster*"
    dotnet run -c Release --project benchmarks/TC.Tier.Runtime.Benchmarks -- --filter "*TierKvBenchmarks*"
    

vs FASTER(产品 API 全链,100k 热区)

基准 Mean Allocated Ratio
FASTER.Read(session.Read) 113.3 ns 0 B 1.00
TierKv.TryGetFormattedAsync 348.0 ns 80 B 3.07×
FASTER.Upsert(session.Upsert) 160.8 ns 0 B 1.42
TierKv.PutFormattedAsync(F&F) 387.2 ns 80 B 3.44×
  • 差距构成:FASTER 的 Read/Upsert 是裸哈希表语义(无记录帧、无 formatter、无 async 状态机); TierKv 产品点查全链 = async 状态机 + 索引 Find + Ring 取值 + 值帧校验([0xC8] 帧 + 过期戳)+ formatter Parse,80 B/读 = 读缓冲分配(值字节副本)。
  • 延迟敏感的热路径可下探组合层直读形态(绕过产品面):组合层批量 scope 零拷贝口径 94.67 ns/查(0.75× vs FASTER 同形 126.96 ns),见 Runtime 包 docs/perf/kv-composition.md

Ring 读保护模型 v2:内容自愈读(read-protection-tiering,2026-09-11 落地)

Ring 读语义已从「epoch 协议保证(EnterReadScope 调用方编排)」重构为「内容保证(读时 magic+CRC 校验 + 失败设备回退自愈)」——调用方零保护零感知,EnterReadScope 退役。

换来的正确性(旧形态下真实存在的缺陷):

场景 旧行为 自愈读行为
恢复后页池空壳(safe=dataStart) FlushedUntil 判据误判热区 → 直读空壳垃圾 判冷 → 设备权威 ✓
环形回绕覆盖(addr < head) 复用别名(同偏移同形记录校验全通过 → 假数据) head 守卫判冷 → 设备历史快照 ✓
页池内容撕裂/残留 静默读垃圾 校验失败 → 设备回退或明确 NotFound ✓

成本(同机 A/B 实测收口 + 优化后复测,各自运行内 FASTER 基线归一)

口径 旧内核(scope 时代) 自愈读内核 差值
Ring.GetValueSpan 零拷贝全链(112B 记录,隔离探针) 12.5 ns 44.6 ns +32 ns
Ring.GetValue 拷贝交付(同上) 19.0 ns 49.2 ns +30 ns
其中:CRC 校验(VerifyCrc 内联段循环) ~13 ns crc32q 依赖链(3 cycle×迭代)是地板
其中:header 轻量门(magic/flags/payloadLen 直读) (2 字段直读) ~3 ns 免全量解码
Ring 读热判据 FlushedUntil 比较 head+safe 双水位 volatile 读 +2 ns(含回绕别名守卫)

第一版实现(跨模块三段 UnifiedCrc + 全量 header 解码)实测 +54~60ns;经 VerifyCrc 内联 段循环 + 轻量门优化压至 +32ns。剩余地板 = crc32 指令 3 cycle 延迟的串行依赖链—— PCLMULQDQ 折叠可再压(预期校验 ~5ns、整链 ~20ns),但折叠常数必须取自经充分验证的 参考实现(自推常数已被差分测试证伪,差分测试组已留存为护栏),列为独立优化项。

消费面 API 终态GetValue/TryGetValue(拷贝交付,跨 await 消费必须走拷贝形态)、 GetValueSpan(零拷贝,仅同步栈内合法)、GetKey/TryGetKey/GetRecords/ScanAsync 同自愈 语义;false/0/空 span = 无有效记录(未命中/墓碑/校验失败),不产生假数据。

TierKv 产品面基线(1k 预填,mem 卷,含范围索引双写)

基准 Mean Allocated 说明
Get_Hit(点查命中) 217.6 ns 80 B 1k 工作集热区命中
Get_Miss(点查未命中) 70.4 ns 0 B 索引 miss 快路径,零分配
Put_FireAndForget(新 key 插入) 650.3 ns 80 B Ring 追加 + 主索引换绑 + 范围索引双写(EnableRangeIndex)
ScanRange_10(范围扫描 10 条) 8.22 µs 2,496 B 字节序 BTree 游标 + 逐条 Ring 取值解帧(含迭代器/枚举分配)
  • 范围扫描无 FASTER 对标项:FASTER 无范围扫描能力,Scan 为 TierKv 独有。
  • 覆写已有 key 的 Put(不含范围索引)为 387.2 ns(上表 F&F 口径);本表 Put 为新 key 插入 且开启范围索引(双索引双写)——口径不同,不与上表互比。

场景矩阵(并发/提交档/原子批/RMW/规模/扫描深度)

同环境;ShortRun 精度(3 warmup / 5 iter);产品 API;mem 卷;缺省 Hash 主索引。 复现:dotnet run -c Release --project benchmarks/TC.Tier.Runtime.Benchmarks -- --filter "*TierKvScenario*"

并发扩展(100k 预填,单/多会话)

场景 1 会话 8 会话 扩展
点查(每线程 64 次随机热) 264 ns/op 172 ns/op 无锁读线性扩展
覆写(F&F 新 key) 312 ns/op 250 ns/op 无锁写(CAS 换绑)扩展正常

规模 scaling(点查随机热)

预填 点查延迟
1k(全 L2) 235 ns
100k(L2/L3 边界) 400-441 ns

规模退化 ~1.7-1.9×,与工作集跨 L2 的 cache miss 一致;索引哈希查找 O(1) 无规模放大。

扫描深度(100k 预填,范围索引 BTree)

深度 总耗时 每条
10 条 812 µs 81 µs
100 条 8.44 ms 84 µs
1000 条 21.4 ms 21 µs

深度增大摊薄扫描启动成本(每条 ~21µs 稳态)。对比 1k 预填的 0.8µs/条,100k 规模下 首段扫描有 ~100× 退化——范围索引 BTree 大规模下的节点定位/页局部性问题,优化候选。

组合调优面(三层全量——默认值是稳定兜底,非最优解)

TC.Tier 的核心是组合式设计:fs / 存储引擎 / 数据结构三层全部有大量配置与注入策略, 配置组合不同性能天差地别。TierKvOptions 暴露常用轴(全量微调经 TierKvBuilder 工厂注入任意 Settings/策略);默认值只保证稳定运行。

调优轴(TierKvOptions) 机制/适用
Ring WithRingPageSize(默认 256KB) 写穿粒度=整页,拷贝量线性(32MB 默认曾致 ms 级)
Ring WithRingMemorySize(64MB) 热数据页池容量
Ring WithRingColdReadRatio(0.25) 冷页缓存占比——大冷读工作集建议 1.0
Ring WithRingMutableFraction / WithRingMaxPageCount mutable 区比例 / 页槽数上界
Ring WithRingOverflowPolicy(policy, minSize) WiscKey 式 KV 分离——大 value 写放大/页池占用调优
引擎 WithMetaTupleFlushInterval(200ms) meta 回扫泵周期——窗口内变更合并落盘
引擎 WithHints(NoBuffering) 磁盘建议叠加 WriteThrough(TierWal 实测全介质最优);mem 零差异
引擎 WithSegmentGrowthLimit(256MB) 段生长上限
fs mem 卷创建时 TierFs.New("memory:", opts)Allocation(Sparse/Reserved)/PageSize/QuotaBytes mem 分配模式与硬配额(fs 创建参数,非 TierKv 选项)
注入 Builder:WithRingFactory / WithIndexFactory / WithKeyComparer / WithKeyResolver 任意 Settings/比较器/解析器策略注入

组合调优(默认值依据,mem 卷 100k 预填实测矩阵)

Ring 页大小 × Committed 覆写(写穿粒度=整页,拷贝线性)

Ring 页大小 Committed 覆写 p50
1MB 43.7 µs
256KB(现行默认) 7.1 µs

较结构默认 32MB 累计 ~480×;更大顺序吞吐场景可 WithRingPageSize 上调。

冷读缓存占比(ColdReadRatio 0.25 vs 1.0)

点查(354 vs 361 ns)与范围扫描 100 条(8.3 ms vs 8.3 ms)均无差异—— 默认 0.25 维持;扫描退化的真因不在冷读缓存(见下 P1)。

其它

  • Hints:mem 上 DIO 模拟零开销(NB ≈ None);磁盘部署建议 NoBuffering | WriteThrough (写即落盘,引擎 Flush 短路,TierWal 同款实测全介质最优)。
  • 哈希表容量:默认 64K 槽(原 1K)——百万级 key 免启动期扩容(扩容=函数式重建)。
  • Options 化的 Ring 调优面WithRingPageSize / WithRingMemorySize / WithRingColdReadRatio / WithRingMutableFraction / WithRingMaxPageCount

已知瓶颈与修复记录

已修复:持久化提交档单次 ~3-6 ms(根因=Ring 巨页默认值)

  • 场景:Committed 档覆写 6.3-6.7 ms、原子批 16 条 6.3 ms、RMW 6.2-6.5 ms (BDN 稳定 StdDev,非首次冷启动拉高)
  • 根因:Ring 页池页大小结构默认 32MB——增量 flush 的写穿粒度=整页,每次 Committed 提交都把当前 32MB 页整页写回设备(mem Sparse=逐 4KB 分配 8192 次; 磁盘=32MB 真实 IO),与页内增量大小无关(1 条 16B 与 64KB 增量同耗时,页数 线性实验证实)
  • 修复:TierKvOptions.WithRingPageSize/WithRingMemorySize(彼时默认 1MB/64MB——现行默认 256KB/64MB),RingSettings 显式配置。修复后 Committed 覆写 p50=43.7 µs(144× 改善); 原子批/RMW 同步受益
  • 教训:逐层探针定位(F&F 全链 81µs / 纯引擎 Flush 0.6µs / 设备大块写 1.9µs / record 写 0.1µs 全部正常 → 成本专属于页写穿粒度)→ 结构默认值是日志顺序写 形态的调优,KV 高频增量 flush 场景必须显式配小页

P1:扫描性能重估(历史「退化」结论撤回)

  • 早期「100k 扫描 83µs/条(退化 100×)」系测试键语义错误:范围扫描按 key 字节序(Ring/范围索引的字节序模型),ScanByRangeAsync(1, 101) 的字节序 区间实际包含全部 byte0∈[0x01,0x64] 的 key(约 3.9 万条)——总时长 6.5ms ÷ 3.9 万条 = 166ns/条,扫描性能正常
  • 教训:范围/前缀的「值」必须按字节序语义构造(参见 W7 测试键的字节模式构造法)
  • 三模式选择器(已实现)价值重估:模式 A(Ring 区间扫)实测 5-6µs/100 条 (~60ns/条),对聚集负载(addr 跨度窄)仍有 ~3× 收益;保留为聚集负载优化

P7 定位:磁盘并发 Committed 的引擎 extent 公平门闩瓶颈(dotnet-trace 实证)

实测(local 卷 DIO+WT,100k 预填):W1=526µs/条 ✓;W2=22µs/条(组提交正常); W8=64 条 27s(425µs/条,75 倍退化)。dotnet-trace CPU 采样(SampleProfiler): **Monitor.Wait 42.6% + LowLevelLifoSemaphore 16.5% + WaitOne 12.2% + Thread.Sleep 3%

  • AcquireExtent 16.6%(inclusive)+ Segment.CompleteAndMerge 3%**——75% 时间在 引擎地址空间的 extent 获取/合并等待。
  • 根因:并发写穿高频触发引擎 AcquireExtent(写租约占地址区间),竞争经 FairGate park(ParkTimeoutMs=50ms 兜底轮询)——唤醒失效/竞争时退化为 50ms 一轮的重试,单条延迟被 park 周期支配
  • 同源已知现象:「同地址双写者 WriteThrough 下每写 ~150ms 公平税(FairGate 50ms park 周期)」
  • 修复方向(引擎层专项):①FairGate 唤醒语义核查(唤醒丢失则修 Wake 路径); ②extent 预分配深度(写窗口前移,减少运行期 AcquireExtent 竞争);③FairGate park 周期参数化。影响面:所有磁盘+并发+持久化写路径(F&F/点查/读不受影响)
  • mem 介质不受影响(无设备 extent 语义)

优化路线图(剖析实测排序)

当前最优配置分层剖析(mem,100k):点查全链 ~1104ns = index.Find 133 + ring.GetAsync 两跳 590 + 解帧/Parse/async 状态机 ~380;扫描 KV 层 = Ring 直扫的 12×。

优先级 预期收益 状态
P1 扫描模式选择器(三模式成本模型) 聚集负载 ~3×(166→60ns/条) 已实现
P2 点查热路径:已定稿一跳直读 TryGet/TryGetFormatted(GetValueSpan 单次页定位+零拷贝+时钟缓存)——BDN 实测 454→167.7ns 零分配(2.7×,vs FASTER 1.40×) 完成(剩余 epoch scope=必要保护,session 常驻化为独立大件) 完成
P3 Ring async 读薄化(GetKey/GetValue 同步快路径,Runtime 面) 两跳 590→~250ns,收益面广(扫描/批/RMW) 待设计
P4 磁盘介质性能基线(DIO+WT 真实持久化数字) 生产部署依据 未测
P5 恢复时长基线(索引帧物化+增量重放 vs 全量重放) 检查点价值量化 未测
P6 并发扩展上限(16/32 线程)与 WiscKey 溢出分离基线 多核/大 value 场景 溢出基线已测(2026-09-13,报告 docs/benchmarks/ring-overflow-chunked-report.md:值 128KB~1GB × 并发 1/8/32 双介质对照,1GB 档整帧租赁崩溃 → 分块化后 mem 1.3GB/s / local 608MB/s);并发扩展上限仍待测