Table of Contents

TierWAL 使用指南

TierWAL = 协议中立 WAL 产品(raft/p2p 的存储中线):底层 EntryLog 封装 + 自定 opaque 容器 布局。全部持久化语义做齐、内容零知识——entry 与元数据均不解析(Opaque 槽), raft/p2p 是纯消费方,直接接线。核心面:批/单条追加 × 显式提交(组提交三维度自动触发) × 随机起点重放 × 头/尾截断 × 元数据 Opaque 槽 × 镜像快照(导出/导入)。 泛型产品类 TierWal,经 TierWalBuilder 装配(StartAsync 一步到位 = 构建 + 恢复 + 就绪)。

快速上手

using TC.Tier.Core.IO;
using TC.Tier.Products.Wal;

var fs = TierFs.New("memory:");                    // 或 TierFs.New("local:///path/to/volume")
await using var wal = await TierWalOptions.Default
    .WithWalName("raft-log")
    .Builder(fs)
    .StartAsync();                                  // 构建 + 恢复 + WaitForReady 一体

// 追加一批 entry(每条 = 一帧 payload——帧格式知识归调用方)
var entries = new List<ReadOnlyMemory<byte>> { new byte[] { 1, 2 }, new byte[] { 3 } };
var r = await wal.AppendBatchAsync(entries, default);
// r.StartIndex = 本批起始 index;r.Count = 条数

await wal.CommitAsync(default);                     // 显式提交(一次 fsync = 一批持久化)
Console.WriteLine(wal.PersistedIndex);              // ≥ r.StartIndex + r.Count - 1

// 从任意 index 顺序重放
await foreach (var e in wal.ReadFromAsync(r.StartIndex, default))
    Console.WriteLine($"{e.Index}: {e.Data.Length}B");

组件入口也可直接 new TierWalBuilder(fs, options)Options.Builder(fs) 是糖)。 恢复 hints 通常无需提供(default 让底层自恢复);进程外已知水位时注入 WalRecoveryHints (TailAddress/BeginAddress/CommittedOffset——地址直透 EntryLog)。

心智模型(三条铁律)

  • index = 起点 + 顺序计数(设计 §8.7):raft 日志连续无空洞——帧内零 index,恢复按 「快照起点/重放起点 + 扫帧计数」推导;段 anchor 表随 opaque 原子落盘(恢复 O(1) 读全表)。
  • 双水位分离AllocatedIndex = 内存分配尾(含攒批窗口,未持久); PersistedIndex = 本地 fsync 水位(last persisted index,CommitAsync 后推进——自动提交 只提前落盘不推进)。raft 语义注意:PersistedIndex 不是集群 commitIndex(多数派确认 归协议层按复制进度计算),是应答/推进 matchIndex 的本地依据。
  • 分配与持久化分离:Append 只写入不 fsync;持久化时机 = 组提交策略自动触发,或调用方 显式 CommitAsync——raft 攒批 → CommitAsync → 应答(一次 fsync = 一批持久化)。

追加与提交

路径 形态 适用
AppendBatchAsync(entries) 批 → WalAppendResult(StartIndex, Count) raft 攒批主路径
AppendSingleAsync(entry) 单条 → [index, 1] 稀疏写;三维度全 0 配置 = 每写即提交
BeginAppendBatch() 租约 ref struct,逐条 Append/AppendAsync 返回 index 适配器甜区:scratch 逐条帧化直写(集合形态的 N+1 次分配 → ~0)
// 批追加租约:using 包裹,绝不跨 await 持有(持 WAL 写锁——ref struct 编译器强制不逃逸)
using (var lease = wal.BeginAppendBatch())
{
    foreach (var frame in frames)
        lease.Append(frame);                        // 页满 = 底层让渡零阻塞;双页在途才背压
    // lease.StartIndex = 批起点;lease.Count = 已追加条数
}

持久化触发(TierWalOptions 组提交三维度,任一满足即提交):

维度 参数 默认 说明
时间 CommitInterval 10ms 距上次提交;-1ms 禁用
数据量 MaxUnflushedBytes 64KB 未提交字节
条数 MaxUnflushedCount 1000 未提交条数
  • 单条提交形态 = 三维度全 0(每次 Append 即触发提交);显式 CommitAsync 与自动提交并存。
  • WaitForPersistedAsync(index):阻塞等到 index 已持久化(raft 推进 commitIndex 的依据); IsPersisted(index) 非阻塞查询。

重放(随机起点顺序读)

// 异步轨:冷启动/追赶主路径
await foreach (var e in wal.ReadFromAsync(SnapshotIndex + 1, ct)) { ... }

// 同步轨:专用 worker 线程(apply 管道)——零异步排队,页切片零拷贝
foreach (var e in wal.ReadFromSync(startIndex)) { ... }   // e.Data 仅枚举存活期有效,跨枚举持有须拷贝

// 批读租约:复制读热路径零分配(页切片视图流)
using (var lease = wal.ReadBatchLeaseSync(startIndex, maxCount, ct))
    await foreach (var e in lease.Entries) { ... }        // Dispose 后视图失效(页归还池)

定位 = O(1):任意起点 = 段表二分 + 段内扫帧计数(与起点无关、与总量无关,~5-11ms 量级, 见性能契约②)。重放吞吐 ~3M 条/s(64B entry,mem/local 同量级)。

截断(冲突修正 / 日志压缩)

await wal.TruncateSuffixAsync(newTailIndex, ct);    // 截尾 [newTail+1, ∞)——raft 冲突日志修正
await wal.TruncatePrefixAsync(newHeadIndex, ct);    // 戴头 [0, newHead)——快照压缩回收,与写完全并行

元数据 Opaque 槽(term/vote/config)

await wal.WriteMetaAsync(opaqueBytes, ct);          // 原子替换:搭 EntryLog 水位线落盘 + CRC 校验兜底
ReadOnlyMemory<byte> meta = wal.ReadMeta();          // 未写 = Empty
  • 内容零知识:blob 由协议层定义(如 term/vote/集群配置序列化);TierWAL 只做原子替换。
  • 容器布局[TailIndex 8B][HeadIndex 8B][段表条目数 4B][pad 4B][段 anchor 表 N×24B][raft 元数据预留区] ——段表与协议元数据共用容器(MetaOpaqueBytes,默认 16KB,配置多大都可以); 段表容量 = (总容量 − 24 − 预留区) / 24,预留区 = 剩余(配置表达)。
  • meta 策略:默认 Managed(独立 .meta 引擎、恒单段、单槽覆盖原子语义、O(1) 恢复); 外部/远程托管走 WithMetaPolicyKind(Transport) + WithMetaTransport(...) 构建期一行注入。

镜像快照(日志压缩 / 冷节点追赶)

long n0 = await wal.SnapshotAsync(ct);              // N₀ = 调用时 PersistedIndex;一体:镜像生成 → 本地存储 → 增量压缩截断
Console.WriteLine($"{wal.SnapshotHeadIndex}..{wal.SnapshotIndex}");   // 快照覆盖的 index 区间

// 冷启动:本地快照自动载入;重建状态机消费帧流(与导出格式同构)
await foreach (var frame in wal.ReadSnapshotEntriesAsync(ct)) { ... }
  • 快照内容 = 主数据 [Head..N₀] 的原始条目帧流(WAL 自己生成,非 raft 状态机镜像); 快照后自动 TruncatePrefixAsync(N₀+1) 压缩已镜像段(先快照后截断——被截区有覆盖)。
  • 本地存储 = IncrementalSnapshot 段增量(每次只写增量段;raft 冷启动必须本地载入, 低频合并阈值内建)。
  • 跨节点传输 = 注入 WithSnapshotPersistence(IAsyncTransferPersistence)(网络/对象存储/ 备份介质),随后 ExportSnapshotAsync(Header(N₀) + 帧流 + Footer CRC)/ImportSnapshotAsync (读入校验 → 替换本地快照;导入后 SnapshotIndex = N₀,应用快照内容后向 leader 汇报 N₀)。 未注入传输面 = 单机形态:快照本地化开箱即用,Export/Import 抛 InvalidOperationException

装配与介质选型

TierWalBuilder 注入面(不注入回落默认;Meta 校验兜底不可绕过):

注入 用途
WithMetaPolicyFactory / WithMetaTransport 自定义 meta 策略 / 元数据托管版本链(Transport 模式)
WithCommitPolicy 替换组提交策略(默认 = Options 三维度阈值)
WithSnapshotPersistence 快照跨节点传输面(Export/Import 前提)
WithLogger 日志器

持久化质量地板DurabilityValidation,默认 true——StartAsync 零 IO fail-fast):

介质 判定
Network(network://) 拒绝——远端对象存储无 fsync 语义、延迟无界,契约① 无保障
Virtual(.tier) 须挂载 CarrierWriteThrough(载体写穿档——四 IO 模式全达标;基线档 p99.9 ≥150ms 不达标)
Local / Memory 通过(mem = 零 fsync 地板,稳定环境高吞吐首选;持久化取舍由部署方自权衡)

IO hints(WithHints,默认 NoBuffering):TierWal 页模型 4MB 页 = 大块顺序写,落 DIO 甜区; 磁盘 raft 部署建议 NoBuffering | WriteThrough(DIO+WT 全介质最优)——机械盘上 buffered 与 DIO 单独在选举窗口口径不达标(DIO 单独是反模式,p99.9=178ms;DIO+WT p99.9=0.43ms), 实测矩阵见性能契约①。读侧不受影响(重放经游标自管页缓存吸收)。

反模式

  • 租约跨 await 持有——BeginAppendBatch 租约持 WAL 写锁(ref struct 编译器强制不逃逸); AppendAsync 的背压 await 发生在底层批内部,租约本体不跨 await。
  • 把 PersistedIndex 当集群 commitIndex——它是本地 fsync 水位;多数派确认归协议层。
  • 跨枚举持有重放切片——ReadFromSync 产出的 Data 是页缓冲切片(仅枚举存活期有效); 复制读收集后发送须自行拷贝(或用 ReadBatchLeaseSync 并在租约存活期内消费)。
  • 网络介质挂载 / virtual 不带 CarrierWriteThrough——DurabilityValidation 直接 fail-fast (测量/非 raft 用途显式 WithDurabilityValidation(false))。
  • 机械盘 DIO 单独用 / buffered 顶选举窗口——见上(DIO+WT 全介质最优)。
  • opaque 容量配 0——TierWAL 必须显式 MetaOpaqueBytes > 0(底层 Settings 基类默认 0 = 无 opaque 区,段表/元数据搭车通道不可用;Options 默认 16KB 已兜底)。
  • 高频 SnapshotAsync——快照 = 镜像生成 + 截断(raft 惯例 = leader 定期低频压缩)。

想深入

  • 设计文档:docs/design/tierwal-design.mddocs/design/tierwal-mirror-snapshot-design.md(内部仓)
  • 性能数据:perf/tierwal.md(三契约 + 稳定性矩阵,随包发布)
  • 结构层积木:EntryLog / 存储引擎使用指南(TC.Tier.Runtime 包)