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 预填
long→long(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);并发扩展上限仍待测 |