存储引擎使用指南(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:物理尾部自动截断半写帧由上层帧协议决策;引擎侧高级口
EngineRecoveryHints(CommittedTailHint/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 节流系数强制饱和:同步路径自旋至SpinMilliseconds抛TimeoutException;异步路径自旋由 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 注释 |