Table of Contents

存储引擎使用指南(StorageEngine)

给谁看:用 StorageEngine 存取数据的上层(Structures / Session / 应用与测试)。 回答什么:引擎怎么建、数据怎么写读、怎么落盘、怎么回收、怎么恢复、什么不要做。 文件系统(fs/spec/TierFs/四类文件系统差异)不在本档——见 ../TC.Tier.Core/docs/io.md


0. 快速上手(可运行)

using TC.Tier.Core.IO;
using TC.Tier.Runtime.Storage;
using TC.Tier.Contracts.Storage;

// ① 文件系统(换 URI 即换文件系统,代码不变)
using var fs = TierFs.New("memory:");

// ② 三段式装配:Options 配置 → Builder 构建 → StartAsync 启动(含恢复,异步首选)
await using var engine = await new StorageEngineOptions("my-engine").Builder(fs).StartAsync();

// ③ 模式 A(推荐):预分配 + 复写
var (region, _) = engine.Allocate(RegionBytes);          // 圈地(近免费)
var slotAddr = engine.CalculationAddress(region, slotIndex * SlotSize);   // 圈地内定位槽位
engine.Write(slotAddr, slotData);                        // 按址填充/复写(可无限次重写)
engine.Read(slotAddr, buffer);                           // 按址读回

// ④ 模式 B(WAL):Append
var logAddr = engine.Append(record.Span);                // 顺序追加,返回起始地址
engine.Flush();                                          // 批末落盘

1. 概念(术语在此定义)

  • 引擎地址空间 = 一块可复写的持久化内存:地址 = 段号+段内偏移(LogicalAddress,16B)。
  • 模式 A(预分配 + 复写,推荐):先 Allocate 圈地、再按址 Write 填充/覆写——址可原地无限次 重写(页回收再利用);不相交区真并行(4 线程实测 3.1× 扩展)。
  • 模式 B(Append,WAL 简单路径):只管追加,地址是返回值——地址只进不退(靠 Reclaim/Compact 回收);小包并发反扩展(全局尾串行)。
  • 三水位MinAddress ≤ CommittedTail ≤ AllocatedTail—— CommittedTail(真实已写水位)= 读/扫描/回收的合法上界(记住这条就够); AllocatedTail(已预留水位,含未写空洞)= Append 内部起点;MinAddress(最小有效地址)头部回收后移。
  • 持久化:"已写完" ≠ 持久化——以 Flush() 返回为准。
  • 选型口诀:数据有"页/槽"概念、要复写要并行 → A;纯顺序日志 → B。大多数数据结构是 A。

2. 建引擎

2.1 两个入口

入口 语义
new StorageEngineOptions(name).Builder(fs).StartAsync() 首选——构造 → 恢复 → 就绪,一步到位(异步)
同上 .Start() 同步便捷版——仅无同步上下文的环境(控制台/测试);UI/ASP.NET 等同步上下文下同步阻塞后台任务会经典死锁

引擎名可直接进构造:new StorageEngineOptions("my-engine")。一个 fs 卷里放多个引擎:不同 EngineName(各占一个子目录,互相隔离)。

2.2 选项(StorageEngineOptions,默认值即生产值)

选项 默认 说明
EngineName "tier-engine" 引擎子目录名 + 段文件名前缀
SegmentGrowthLimit 256MB 单段增长上限,到顶自动切下一段
EnableSegmentation true false = 单段模式(只有 seg0,适合小数据量)
PreallocateFile true 建段真实预分配;false = 稀疏按需增长
DeleteOnClose false Dispose 时删除本引擎全部产物(测试清理常用)
Hints None WriteThrough = 每写同步落盘;NoBuffering = 请求 DIO。见 §3.4
MinSegId 0 段表最小有效段号(恢复路径首段)

流式配置:.WithSegment(limit, enable) / .WithPreallocateFile(b) / .WithDeleteOnClose(b) / .WithHints(hints) / .WithMinSegId(id)

2.3 Dispose

await using 即可。内部编排(停 worker → 停 epoch → 清段池 → 补写段元组 → 按需清目录)自动完成。


3. 怎么用

3.1 动词速查

动词 语义 注意
Allocate(len) 圈地:预留区间返回 (start, end) 无 lease 协议(纯 CAS),近免费;模式 A 第一步
Write/WriteAsync 按址复写(模式 A 主力) 址可无限次重写;目标须 ≤ CommittedTail(Allocate 过即可)
CalculationAddress(addr, ±len) 址上推算(跨段进位/借位) 圈地内定位槽位的唯一正道;禁手算 Offset 差
Append/AppendAsync 尾部追加(模式 B) 每笔付 lease+双尾推进;地址只进不退
Read/ReadAsync 按址读,跨段自动切分 返回实际读数(0 = 到尾);地址已被回收则抛异常
OpenSequentialReader 顺序游标,读/跳分离、可双向 全量扫描(usePageCache/snapshotMode 按需选)
Flush() / Flush(upTo) 落盘(fsync 族) 仅同步(OS 无异步 fsync);upTo 版自动对齐段边界
GetDistance 两址距离(跨段正确) 与 CalculationAddress 同为地址算术正道
ReclaimHead/Tail/Reclaim 回收区间释放空间 模式 B 日志消费后收头;StartReclaim 后台版(0 等待)带事件+进度
StartCompact / StartRangeCompact 碎片整理(整段搬迁) 一律后台句柄(同步入口已废除);超时经 await op.WaitAsync(ct) 调用方自控
GetHoleRatio 查区间物理空洞占比 0.0 全实 / 1.0 全洞

3.2 模式 A 完整范式:预分配 + 复写

// ── 圈地(页缓冲模型的开销仅 ~6 µs / 256MB)──────────────
var (region, _) = engine.Allocate(PageCount * PageSize);

// ── 定位槽位:CalculationAddress(圈地内唯一正道)──────────────
var page3 = engine.CalculationAddress(region, 3 * PageSize);
var slot  = engine.CalculationAddress(page3, slotNo * SlotSize);

// ── 写:填充 / 原地更新(复写不增长空间,页回收后重写即复用)────
engine.Write(slot, data);

// ── 并发写:每写者认领不相交区(lease 区间所有权 → 真并行 3.1×)──
var regions = Enumerable.Range(0, writers).Select(w => engine.Allocate(RegionSize)).ToArray();
// 每线程只写自己的 regions[w],互不干扰

// ── 读:按址直读;全量走游标 ─────────────────────────────────
engine.Read(slot, buf);
using var reader = engine.OpenSequentialReader(engine.MinAddress, engine.CommittedTail);

上层自管什么:槽位→地址映射(自由表/位图)、页水位、脏页回写时机。 引擎保证:地址稳定(复写不改址)、区间并发安全、崩溃后圈地与已写数据都在。

3.3 模式 B 范式:Append WAL

var addr = engine.Append(frame.Span);    // 帧格式自带 CRC/长度/序号(上层协议)
// ……
engine.Flush(upTo: lastApplied);         // 按应用水位落盘
// 消费完的头部区间回收:
engine.ReclaimHead(consumedUpTo);

3.4 持久化姿势(引擎自有开关)

  • 默认(Hints=None:写进页缓存攒批,调 Flush() 才保证落盘——吞吐高,按事务/批次 Flush 即可;
  • WithHints(FileOpenHints.WriteThrough):每写同步落盘——每笔都必须稳的场景(如 WAL 提交点), 牺牲吞吐。

WithHints(FileOpenHints.NoBuffering) 是请求直 IO(绕页缓存);真实生效与否经 engine.UnbufferedSupport 报告(部分文件系统不吃 DIO,报 Ignored)——它是探测结果, 别拿它当文件系统判断做分支


4. 崩溃恢复:默认全自动,不用管

Builder.Start* 内部已做:扫盘重建段表与地址表 → 重建容量计数 → 启动保护。空目录 / 新卷自动从零 开始,不报错。启动一步到位(内部 Initialize + WaitForReady),外部无需也不允许手动调 Initialize (设计决策:初始化不在接口面);恢复后的状态经 RecoveryState 可观察。

两模式恢复语义:

  • 模式 A:圈地与已写槽位原样恢复(Allocate 占位即 Committed+sparse,持久);上层自管的槽位映射 从自己的元数据恢复。
  • 模式 B:物理尾部自动截断半写帧由上层帧协议决策;引擎侧高级口 EngineRecoveryHintsCommittedTailHint / AllocatedTailHint)——上层比引擎知道更多时(如事务日志持有自己的提交 水位)修正双尾,防尾部半写数据被当作有效。经 Builder.Start(hints) / StartAsync(hints) 传入; 只属于直接构造引擎的消费者;数据结构内部建引擎时一律不带 hints(物理真相引擎自恢复, 逻辑水位结构自管,两回事)。

5. 文件系统选型与测试接线

5.1 极速吞吐:内存文件系统不只是测试用

memory: 内存文件系统是生产级极速文件系统——直址零拷贝、纳秒级元数据,引擎端到端 ~0.5 µs/op(本地文件系统 2.75 µs)。需要极速吞吐、数据生命周期 = 进程生命周期的场景(缓存、 计算中间态、高频写临时区)首选内存文件系统。

数据不是"Dispose 即丢":RootSpaceImage(Core IO)随时把内存卷导出为镜像/转化为任意文件系统 (四类文件系统全部支持,4 源 × 4 目标 16 格全成立——详见 ../TC.Tier.Core/docs/io.md §5.2)。

5.2 测试接线

整套测试可用环境变量切换文件系统、零重编译: TC_TEST_FS_SPEC=local:///tmp/tier-test dotnet test …(缺省 memory:——CI 零本地盘极速)。 测试常用:new StorageEngineOptions("x").WithDeleteOnClose(true) 自动清理产物。


5.5 故障注入面(Faults——引擎缝常设对抗面)

引擎缝 = 引擎对上层的契约级失败与状态窗注入(盘的故障类归 Core IO 的 FaultInjectingFileSystem, 见 ../TC.Tier.Core/docs/fault-injection.md)。 显式开启面——缺省关闭零开销(各公开 op 入口一次空引用短路):

var builder = new StorageEngineOptions("eng", segmentGrowthLimit: 4096).WithFaults().Builder(fs);
var dev = builder.Start();
dev.WaitForReady();
var faults = builder.Engine.Faults;      // IStorageEngineFaultInjector?(开启后非 null;
                                         // StorageEngine 与 Builder.Engine 均 internal——
                                         // 经 InternalsVisibleTo 的测试项目可达;外部消费方
                                         // 在文件系统缝用 Core 的 FaultInjectingFileSystem)

faults!.FailOn("Append", IOError.DiskFull, atCallIndex: 2);   // op 级失败——语义码透传
faults!.DelayOn("Flush", TimeSpan.FromMilliseconds(80));      // op 级慢——watchdog/有界等待触发器
faults!.HangOn("ReadAsync");                                  // op 级挂——永不到达业务体,Reset 放行
faults!.EnterState(EngineFaultState.CompactActive);           // 状态窗
faults!.Reset();                                              // 全清(规则/挂起点/状态窗)
  • op 名全集opPattern 精确名或 "*"):Allocate / Write / WriteAsync / Append / AppendAsync / Read / ReadAsync / Flush / Reclaim / ReclaimHead / ReclaimTail / StartReclaim / StartCompact / StartRangeCompact / OpenSequentialReader
  • 三状态窗(确定性进入,Reset 退出,多窗可并存):
    • RecoveringWindow——恢复核心延后启动(Start 前进入 = 上层 WaitForReady 超时容忍路径验证); 存续期间 op 入口按"尚未完成恢复"抛 InvalidOperationException(EnsureReady 同型)。
    • CompactActive——CompactLease 排他占住 [MinAddress, 数据尾段段末)(占区间协议同真实 Compact); 冲突写者走让位自旋 → SpinMilliseconds 有界超时出口。
    • ThrottleSaturated——CPU 节流系数强制饱和:同步路径自旋至 SpinMillisecondsTimeoutException;异步路径自旋由 ct 解围(OCE)。
  • 与 fs 脸可组合(如"引擎 DelayOn + fs DiskFull"双缝叠加);引擎缝不做数据伪造(错值/短读)—— 数据破坏归 fs 腐败规则。

6. 反模式(禁忌)

# 规矩
1 别绕过引擎直开段文件——句柄池与水位契约会被打穿
2 读/扫/回收上界用 CommittedTail,不是 AllocatedTail(后者含未写空洞)
3 地址距离/推算只用 GetDistance/CalculationAddress,禁手算 Offset
4 "已写完" ≠ 持久化——持久化以 Flush() 返回为准
5 Compact 一律后台句柄(StartCompact/StartRangeCompact)——同步入口已废除;禁 GetAwaiter().GetResult() 强制等待(线程池耗尽死锁);超时/取消经 await op.WaitAsync(ct) 调用方自控
6 依赖段文件名格式做外部扫描(段命名是引擎私有规则)
7 fs 用法(文件系统构造/spec/options)归 Core IO——本层代码只收 IFileSystem,不自建、不判断文件系统
8 别上来就 Append——数据有页/槽概念就用模式 A(预分配+复写);Append 只给纯顺序日志
9 Faults 面缺省关闭别当生产配置——WithFaults() 只进测试/对抗验证装配;挂起规则(HangOn)永不自行放行,被测方须以自身超时/取消路径先行处置
10 别用引擎缝伪造数据——引擎缝只做契约级失败与状态窗;错值/短读/字节翻转归 fs 脸 AddCorruptRule(介质寻址)

7. 决策速查

问题 答案
怎么用? 默认模式 A:Allocate 圈地 → CalculationAddress 定位 → Write 复写;纯 WAL 才模式 B Append
换文件系统? 改 spec 一行(fs 层的事,见 Core IO io.md §2);引擎/业务零改动
大批量随机读写? 模式 A 直接循环 Write/Read(无批量面,外部循环等价)
顺序扫全量? OpenSequentialReader
每笔必稳? WithHints(FileOpenHints.WriteThrough);否则默认 + 批末 Flush()
要 DIO? WithHints(FileOpenHints.NoBuffering) 请求,UnbufferedSupport 看真实结果
空间回收? 模式 A 槽位复用(原地重写,无需回收);模式 B ReclaimHead 收头 / Compact 整理
一个卷多个引擎? 不同 EngineName(各占一个子目录)
性能基线? perf/storage-engine-perf-baseline.md

8. 想深入?指路

想懂什么 去哪
文件系统/fs/spec 用法(四类文件系统、TierFs、options、卷镜像) ../TC.Tier.Core/docs/io.md
段表(地址空间怎么切) segment-table.md
段句柄租约 lease-protocol.md
引擎内部机制(恢复/池/worker) 源码 src/TC.Tier.Runtime/Storage/ 各 partial 注释