Table of Contents

Ring 使用指南——追加式 KV 环 / RCU 原地更新 / 环形驱逐

给谁看:用 Ring 作 KV 真相源 / WAL 的组件开发者 回答什么:Ring 是什么 / Settings 全参数 / 8 水位 / Codec 注入 / 写读 API / 恢复 / 反模式 本篇只讲机制——帧字节布局见源码;性能数字见 perf/structures-perf-baseline.md 定位:Structures/ 6 子族之一,主结构(数据真相源),与 Log 并列 2 主结构


0. 一句话总纲

Ring = 固定槽数环形页池 + 全局单调逻辑地址 + 8 水位指针 + RCU 原地更新 + 环形驱逐

  • 追加写返回 LogicalAddress(16B 结构 SegId+Extension+Offset)= 取值句柄,地址永不重写
  • 同尺寸 value 经 UpdateValue 原地覆写(RCU 语义),同槽位复用
  • 环形满自动驱逐最老页(Head 推进);地址空间永远单调向前
  • 8 水位指针经 CAS128 原子推进(cmpxchg16b 快路径 / 非对齐分片锁兜底),并发 Flush/Truncate 不会回退
  • 地址运算只用 engine.GetDistance / engine.CalculationAddress(铁律:LogicalAddress.Offset 跨段无意义,不参与算术)

1. 定位

  • 与 Log 同为主结构:Ring 是数据真相源(record 流),Log 是操作流;二者均以 LogicalAddress 为一等公民
  • 与 Index 组合:Ring=真相源 / Index=派生数据;写编排正序:先 Ring.Write → 再 Index.Insert
  • 与 Mirror/Snapshot 关系:Ring 自带 OpenSnapshotReader/Writer 区间导出/导入能力(数据快照),与独立的 mirror.md / snapshot.md 形态无引用关系
  • 与 Meta 关系:外部 opaque 经 SetOpaqueMeta 随水位线原子落盘,是 Index 锚点 W 的搭车通道
  • 与 StorageEngine 关系:结构层是 internal 使用者;文件系统 = 构造传哪个 IFileSystem(详见 storage-engine.md)。引擎自恢复 MinAddress/AllocatedTail;Ring 自管 8 水位
  • 与横切协议:meta 持久化经统一 IMetaPolicy(见 meta.md);2PC 跨结构原子经 ITransactionParticipant(协调器见 session.md

2. Settings 全参数

BlittableRingSettingsRingSettings 的 sealed 派生——无额外字段(key 长度由 TKey 类型参数提供 sizeof(TKey))。所有可配置项在基类 RingSettings

参数 默认 说明
PageSize AlignmentConst.Alignment32M 页大小(字节)。校验:2 的幂 / [4KB, 1GB]。DIO 对齐契约下限
MemorySize AlignmentConst.Alignment16G 总内存容量(字节)。校验:≥ PageSize 且整除为 2 的幂页数。PageCount = MemorySize/PageSize 是派生量,不在 Settings 直配
MaxPageCount 8192 页数上界。页数过多(PageSize 太小)扫描/驱逐开销随页数线性增长,校验时拒绝
MutableFraction 0.9 mutable 区占比 (0,1) 开区间。决定 mutable→readonly 转换点(ReadOnlyLag = MutableFraction × (PageCount-1) × PageSize
Preallocate false 构造时是否全量预分配所有页(false = 懒分配,按需 Allocate)
OverflowPolicy Disabled 溢出策略(WiscKey 式 KV 分离)。Disabled=Value 内联主 record;Enabled=Value > MinOverflowSize 时分离到溢出引擎
MinOverflowSize 0 Value 溢出阈值(字节)。Enabled 时 0 = 全部溢出
ClockCacheCapacity null 冷页缓存槽位。null=按 ColdReadRatio 派生(默认);显式设置覆盖 ratio,向上取整到 2 的幂
ColdReadRatio 0.25 冷回源比例 [0.0, 1.0]。0.0=不缓存(每次冷读走设备);1.0=缓存全部页(近乎全热)
ColdRecordBufferLimit AlignmentConst.Alignment1M 单条 record 部分页回源的 buffer 上限。超过此值时回退到整页路径(LoadColdPage)。校验:须 ≥ HeaderSize
MainEngine StorageEngineOptions,主引擎配置(引擎名 / 段大小 / 预分配 / 删 onClose / DirectIo / Hints 等)。构造时直传
MetaPolicyKind meta 策略模式(Managed / Transport / Disabled)。Managed=独立 meta 引擎;Transport=外部传输(未注入回落 MetaHost 嵌入 ring 流);Disabled=不持久化(opaque 拒写)
MetaOpaqueBytes 256 opaque 槽字节数(结构级缺省;初始化器可覆盖)。上限受 IMetaPolicy 容量约束

构造校验(fail-fast,RingBase.Validate——源码 Structures/Ring/RingBase.cs):

  1. PageSize 是 2 的幂 / [4KB, 1GB]
  2. MemorySize ≥ PageSize 且整除为 2 的幂页数
  3. PageCount ≤ MaxPageCount
  4. SegmentGrowthLimit ≥ PageSize
  5. MutableFraction ∈ (0,1) 开区间
  6. ColdRecordBufferLimit ≥ HeaderSize

3. 构造与生命周期

3.1 [RingKey] 源生成器机制

开放泛型 BlittableRing<TKey> 的构造是 protected internal——消费面只经 [RingKey] 生成的封闭薄类(编译期封闭 + CreateAsync 工厂)。内置 long 已在 TC.Tier.Runtime 程序集声明,RingOfLong 开箱即用;自定义 Key 在消费程序集加一行:

[assembly: RingKey(typeof(OrderId))]   // 产出 RingOfOrderId / HashOfOrderId / BTreeOfOrderId ...

分析器诊断不满足 unmanaged 约束的声明。绕过 [RingKey] 直接继承开放泛型 = CS0122 编译错(internal 不可见时绑定器误绑 IO 扩展同名方法)。

3.2 CreateAsync 工厂 = 构造 + Initialize + WaitForReadyAsync 一步就绪

using var fs = TierFs.New("memory:");
var settings = new BlittableRingSettings(new StorageEngineOptions("kv-ring", 64L << 20))
{
    PageSize = 4096, MemorySize = 64 * 1024, MutableFraction = 0.5,
};
await using var ring = await RingOfLong.CreateAsync(settings, fs);   // 就绪可读写

或分步(观察恢复进度 / 注入恢复窗口):

var ring = new RingOfOrderId(settings, fs);
ring.Initialize();                       // 触发 OnInitializeBegin → 三引擎并行启动 + 恢复
ring.WaitForReady();                     // 等恢复完成(默认四级回退)

3.3 写读最小用例(正序两步写 / 两段合口径读)

static LogicalAddress KvPut(RingOfLong ring, IIndex<long> index, long key, ReadOnlySpan<byte> value)
{
    var addr = ring.Write(key, value);           // ① 真相源先写,得地址
    index.Insert(key, addr, LogicalAddress.Empty);  // ② 索引后插(指向该地址)
    return addr;
}

static bool KvTryGet(RingOfLong ring, IIndex<long> index, long key, Span<byte> buf, out int len)
{
    len = 0;
    var addr = index.Find(key);                  // ① 索引命中得地址
    if (addr == LogicalAddress.Empty) return false;
    len = ring.GetValue(addr, buf);              // ② 真相源按址取值
    return true;
}

3.4 Dispose

幂等(CAS 排他门 _disposeGate)。落盘契约(存储底线):先把 mutable 区 [FlushedUntilAddress, TailAddress) 落盘(FlushUntilCore 免门直走)→ 写 meta → 释放 meta 策略 → 释放页 native 内存。各步异常吞掉互不阻断(尽力落盘)。Dispose 与并发读写经 EnsureReady/EnsureNotDisposing fail-fast 隔离。


4. API 详表(按功能分组)

4.1 写

方法签名 用途
LogicalAddress Write(TKey key, ReadOnlySpan<byte> value) 公开写入。大 value 自动溢出(按 OverflowPolicy)。永不返回 Empty
LogicalAddress Write(TKey key, ReadOnlySpan<byte> prefix, ReadOnlySpan<byte> value) 分段写入(免临时数组拼接);Overflow=Enabled 时超阈值抛——用连续 value 的 Write/WriteAsync
LogicalAddress WriteTombstone(TKey key) 写墓碑(FLAG_RINGRECORD_TOMBSTONE + 空 value);恢复时 RecordKey.IsTombstone 过滤
ValueTask<LogicalAddress> WriteAsync(TKey key, ReadOnlyMemory<byte> value, CancellationToken ct = default) 公开异步写。inline 快路径同步完成(零 async 开销);溢出慢路径真异步
ValueTask<LogicalAddress> WriteTombstoneAsync(TKey key, CancellationToken ct = default) 异步墓碑写(同步完成)
void UpdateValue(LogicalAddress addr, ReadOnlySpan<byte> newValue) RCU 原地更新。四分支翻转:overflow→overflow/inline→overflow/overflow→inline(受原 inline 槽容量约束,超抛)/inline→inline
ValueTask UpdateValueAsync(LogicalAddress addr, ReadOnlyMemory<byte> newValue, CancellationToken ct = default) 异步版。纯 inline 翻转同步完成;溢出翻转走 WriteOverflowAsync
WriteBatch BeginWriteBatch() 批量写(ref struct,using 包裹)。多写者各持独占窗口段,批内无锁推进;详见 §4.6

WriteBatch.Append 签名:

LogicalAddress Append(TKey key, ReadOnlySpan<byte> value);
LogicalAddress Append(TKey key, scoped ReadOnlySpan<byte> prefix, scoped ReadOnlySpan<byte> value);

同一 WriteBatch 不可并发调用;不同批各持窗口可并发。Dispose 释放 epoch。

4.2 读(内容自愈读)

判据 = SafeSnapshotTailaddr < 水位 → 页池直读; → 设备读 / 自愈回退)。热直读经 magic + CRC 校验,失败自动回退设备重读——驱逐复用/撕裂/残留不产生假数据(读到设备权威快照或空/NotFound,由上层定夺)。

方法签名 返回 / 语义
int GetValue(LogicalAddress addr, Span<byte> destination) 拷贝交付;0 = 无有效记录(未命中/墓碑/校验失败)
bool TryGetValue(LogicalAddress addr, Span<byte> destination, out int written) 拷贝交付;false = 未命中/墓碑/校验失败。跨 await 消费必须走此形态
ReadOnlySpan<byte> GetValueSpan(LogicalAddress addr) 零拷贝切片——仅同步栈内合法。空 span = 无有效记录。溢出 record 回退 thread-static 缓冲拷贝
RecordKey<TKey> GetKey(LogicalAddress addr) 拉到 key(+ value 长度 + flags + 地址)。无有效记录抛 InvalidOperationException(fail-fast)
bool TryGetKey(LogicalAddress addr, out TKey key) 读 key 的 NotFound 形态。false = 无有效记录
ValueTask<RecordKey<TKey>> GetKeyAsync(LogicalAddress addr, CancellationToken ct = default) 异步单条 key 读;热区同步快路径,冷区真异步整页回源
ValueTask<int> GetValueAsync(LogicalAddress addr, Memory<byte> destination, CancellationToken ct = default) 异步 value 拷贝读;溢出走 ReadOverflowAsync
ValueTask<(TKey Key, bool Success)> TryGetKeyAsync(LogicalAddress addr, CancellationToken ct = default) 异步 key 读的 NotFound 形态
void GetRecords<THandler>(ReadOnlySpan<LogicalAddress> addresses, THandler handler) where THandler : IReadOnlyRecordHandler<TKey> 批量读(按页号聚簇,减少冷区回源次数)。无有效记录的地址跳过(不回调 handler.Handle
RingRecordFields GetFields(LogicalAddress addr) 读 record header 字段(轻量)
Span<byte> GetSpan(LogicalAddress addr, int length) 不暴露 byte*,页池 native 内存可写窗口。调用方保证页未驱逐回收
void EnterEpoch() / void ExitEpoch() epoch 读保护协议实现(IEpochProtected)。读路径已退役——读安全性由内容自愈读不变式承担;写/驱逐 drain 仍使用
long GetByteDistance(LogicalAddress from, LogicalAddress to) 公开字节距离(队列积压/滞后 lag 度量原语)

4.3 8 水位(全列)

水位 含义 持久化层
BeginAddress 头截断边界(= engine.MinAddress,此地址前数据已回收) 持久化层(meta 必存)
FlushedUntilAddress 落盘边界(此地址前已写引擎,恢复可信数据上限 W) 持久化层(meta 必存)
SafeReadOnlyAddress 安全只读水位——epoch 保护下 readonly 区推进目标(读者可安全读至此处) 持久化层(meta 必存)
ReadOnlyAddress 只读水位——此地址前页面冻结(等待 flush 后驱逐) 持久化层(meta 必存)
TailAddress 写游标——下一条 record 的分配地址(TryAllocate 单调推进) 持久化层(meta 必存)
HeadAddress 驱逐边界——此地址前的旧页已被回收(head 随 flush/驱逐推进) 内存水位层(meta 可选存,恢复时从 Begin 重建)
SafeHeadAddress 安全驱逐边界(epoch 保护下的驱逐推进目标) 内存水位层
ClosedUntilAddress 关页边界——此地址前的页已关闭(新写入不得落在其前) 内存簿记层(永不落盘,恢复时初始化为 SafeHeadAddress)

辅助:TakeSafeSnapshotTail()(lock 内采集——之前全部 header 完整可见,扫描游标构造快照 + 读路径热冷分档判据)。8B 距离水位 _safeSnapshotTailDist/_headDistInterlocked CAS-max 发布 / Volatile 读,热路径无锁无撕裂。

4.4 截断

方法签名 语义
void TruncatePrefix(LogicalAddress address, bool truncateDevice = false) 头截断——推进 BeginAddress前置address ≤ FlushedUntilAddress(否则抛——未落盘数据会丢)。truncateDevice=trueengine.ReclaimHead(失败不阻断逻辑截断)
void TruncateSuffix(LogicalAddress address) 尾截断——D2 决策唯一异常路径(放松"地址单调不回退"铁律)。回退四件套:物理回收(engine.ReclaimTail)+ 内存水位条件回退(各字段独立判断)+ 页池清零 + 冷缓存失效。守卫address 不得落入已驱逐区(< SafeHeadAddress

4.5 快照

方法签名 语义
IRingSnapshotReader OpenSnapshotReader(LogicalAddress begin = default, LogicalAddress end = default) 创建快照读取器(pull 模式)。默认区间 [BeginAddress, TailAddress)。冷热透明分流(FlushedUntilAddress 判据)
IRingSnapshotWriter OpenSnapshotWriter(LogicalAddress begin, LogicalAddress end) 创建快照写入器(push 模式)。begin..end 超出当前 AllocatedTail 时先整段预留 Allocate填完所有数据须调 Complete 推进 TailAddress 到 end

IRingSnapshotReaderlong Length / int Read(Span<byte> buf) / ValueTask<int> ReadAsync(Memory<byte> buf, CancellationToken ct),页级流式支持 GB/TB。 IRingSnapshotWritervoid Write(ReadOnlySpan<byte> buf) / ValueTask WriteAsync(ReadOnlyMemory<byte> buf, CancellationToken ct) / void Complete() / ValueTask CompleteAsync(CancellationToken ct)

4.6 批量写

BeginWriteBatch() 返回 ref struct WriteBatchusing 包裹)。窗口模型:每个批 = 独占页段窗口——领取时在 _tailLock 内把 tail 一次性推进到窗口尾(预留段,批独占);批内 record 分配完全无锁(页内游标推进,零锁零 EnsureSpace)。多写者各持窗口并发写——锁竞争从"每 record 一次"降到"每窗口一次"。

4.7 扫描

方法签名 语义
IRingScanCursor OpenScanCursor(LogicalAddress begin = default, LogicalAddress end = default) 打开扫描游标(默认区间 [BeginAddress, TailAddress);工厂注入或默认 SequentialRingScanCursor)。整段持 epoch
IAsyncEnumerable<(TKey Key, LogicalAddress Address, bool IsTombstone)> ScanAsync(LogicalAddress begin, LogicalAddress end, CancellationToken ct = default) 范围扫描异步迭代器(包装 OpenScanCursor,冷区真异步 IO)。仅吐用户数据 record(过滤 FLAG_ENTRY_IS_META);墓碑旗标随三元组吐出
IAsyncEnumerable<(TKey Key, LogicalAddress Address, bool IsTombstone)> ScanAsync(CancellationToken ct = default) 全量扫描(BeginAddressTailAddress
long SpanPages(LogicalAddress from, LogicalAddress to) 两地址间页跨度(P1 扫描模式选择器成本模型输入)
LogicalAddress GetFlushedWatermark() IKeyResolver 契约——派生结构后台持久化的 footer 锚点 W(= FlushedUntilAddress

4.8 2PC(ITransactionParticipant 六件套)

方法签名 语义
void Prepare(long seq) / ValueTask PrepareAsync(long seq, CancellationToken ct) 事务准备:落盘数据 + meta 至"悬干"状态。Transport 回落 MetaHost = 1 次 fsync(meta record 随数据同页 flush 原子落盘);Managed/注入 Transport/Disabled = 2 次 fsync(FlushUntil 先 + WriteMeta 后)。经 _prepareGate 串行
void ConfirmCommitted(long seq) 推进 LastCommittedSeq,触发回调;推进 _txRollbackTail(提交边界,随 meta 持久化为 CommittedTailAddress
void OnCommitted(long seq, Action callback) 注册 seq 回调;已提交到更高 seq 立即同步触发
void Abort(long seq) / ValueTask AbortAsync(long seq, CancellationToken ct) 2PC 回滚——TruncateSuffix(_txRollbackTail) 回退到上一已确认提交边界,丢弃本轮悬干数据。守卫矩阵:seq ≤ LastCommittedSeq=no-op;seq ≠ LastPreparedSeq=陈旧;无既有提交边界/无悬干数据 = 仅复位记账

LastCommittedSeq / LastPreparedSeq 公开只读(恢复 / 运行时读)。

4.9 Opaque meta 搭车

方法签名 语义
void SetOpaqueMeta(ReadOnlySpan<byte> data) 写外部 opaque meta(stage 进策略缓冲,随下一次水位提交原子落盘)。唯一消费者形态:TierKV 组合层登记 index 镜像锚点 W。MetaPolicyKind=Disabled 时拒写
ReadOnlySpan<byte> ReadOpaqueMeta() 读最近已提交块;空 = 未写入/未开启 meta 持久化——空即答案
IMetaPolicy<RingMetaHeader, RingMetaPayload> MetaPolicy { get; } 构造期装配(永非 null 纯读)

5. 自定义注入工厂

接口 / 类型 职责 默认实现 自定义注意
IRingCodec record 三段式 codec(header/CRC/字段偏移/单字段读) BlittableRing.Codec 暴露 HeaderSize / Alignment / Magic / CrcOffset / PayloadLengthOffsetTryReadHeaderLight 热路径轻量门(magic+长度上界+Flags),免全量 header 解码——本进程写入页池热读专用;设备回源/恢复走全量 TryReadHeader
RingRecordFields record 业务字段包(Flags / PayloadLength / PaddingLength / PreviousAddress)—— readonly record struct codec 读写 header 时传参
RecordKey<TKey> Key + ValueLength + Flags + Address 组合返回值 IsOverflow / IsMeta / IsTombstone 旗标属性
IKeyResolver<TKey> Index 注入 Ring 时必需的判等闭环契约 RingBase<TKey> 实现 TryGetKey / GetFlushedWatermark / ScanAsync 三成员;扫描详见 §4.7
IRingScanCursor 扫描游标,扩 IStructureScanCursor SequentialRingScanCursor(嵌套 sealed) CurrentAddress / NextAddress / BeginAddress / EndAddress / GetFields() / CurrentRecordSize;自定义经 RingCursorFactory<out TRingScanCursor> 委托注入
RingCursorFactory<out TRingScanCursor> 扫描游标工厂委托 (begin, end) => cursor null = 默认 SequentialRingScanCursor 自定义经构造参数 cursorFactory 注入
IRingSnapshot 快照统一抽象(pull/push 分离) RingSnapshot<TRing>(嵌套) Reader(begin, end) / Writer(begin, end);自定义经构造参数 ringSnapshot 注入
RingSnapshotReaderFactory / RingSnapshotWriterFactory 快照读写器工厂委托 null = 默认 RingSnapshotReader / RingSnapshotWriter 自定义经构造参数 snapshotReaderFactory / snapshotWriterFactory 注入
IRingSnapshotReader 快照读取器——pull 模式,页级流式 Length / Read(Span) / ReadAsync(Memory, ct)IDisposable + IAsyncDisposable
IRingSnapshotWriter 快照写入器——push 模式,Complete 收口 Write(ReadOnlySpan) / WriteAsync(ReadOnlyMemory, ct) / Complete() / CompleteAsync(ct)
OverflowPolicy(enum) 溢出策略 Disabled / Enabled Disabled Enabled = WiscKey 式 value 分离,溢出引擎与主引擎同 fs
RingMetaPayload(struct) 持久化层 6 指针 + LastCommittedSeq + LastPreparedSeq + OverflowTailAddress + KeySize + CommittedTailAddress [BinaryLayout] 源生成器产出 codec;旧块短 payload 零扩展降级
RingMetaHeader(struct) meta header 规范字段(MagicValue / Version / Flags / PayloadLength / PaddingLength,12B) Magic="RMHD"CurrentVersion=major 1/minor 0
AddressInfo(internal struct) 溢出指针(Address + Size,24B) 旧版 8B 位压缩在 LogicalAddress(16B) 模型下不适用
OverflowRecordHeader(internal struct) 溢出帧头 18B(Magic+Version+Flags+PayloadLen+PadLen+Crc32C) Magic "OVRF"Alignment=4
IReadOnlyRecordHandler<in TKey> 批量读回调接口 Handle(LogicalAddress address, TKey key, int valueLength, ushort flags)——flagsRecordFlags 内部位
MetaPolicyFactory<RingMetaHeader, RingMetaPayload> meta 策略工厂委托 kind => policy CreateMetaPolicyDefault Managed / Transport(未注入 transport 回落 MetaHost)/ Disabled 三模式
IMetaTransport Transport 模式的外部 meta 传输 MetaHost(嵌套,回落:meta record 嵌入 ring 流) 自定义经构造参数 metaTransport 注入
IRecovery<RingRecoveryHints> 恢复策略 DefaultRingRecovery(四级回退) 自定义经构造参数 recovery 注入;裸写 IRecoveryLifecycleBase 闸门不同步——继承 RecoveryBase<RingRecoveryHints>
LightEpoch epoch 保护 自建 自定义共享经构造参数 epoch 注入(ResourceOwnership.Referenced

6. 持久化机制

6.1 页池状态机(4 态)

mutable → readonly → flushed → evicted
   ↑          │             │            │
   │          │             │            │
   └──────────┴─────────────┴────────────┘
            (slot 循环复用:pageSeq & PageCountMask)
  • mutable:当前写者活跃区,TryAllocate 推进 TailAddress
  • readonly:页面冻结,等待 flush。ReadOnlyAddress 推进经 PageAlignedShiftReadOnlyAddress(页边界对齐)
  • flushed:已写引擎(AsyncFlushPages 把页内容 Write 回它对应的引擎地址——同地址 100% 确定)。FlushedUntilAddress 推进
  • evicted:旧页回收(head 推进 FreePage)。HeadAddress / SafeHeadAddress 推进

页池固定 N 槽(PageCount = MemorySize/PageSize),slot = pageSeq & PageCountMask 循环复用。地址单调递增永不回退,写满一圈淘汰旧页(FASTER hybrid log 模型)。_emptyPageCount 默认 PageCount-1(最大 mutable 区);_headOffsetLagBytes = (PageCount-1) × PageSize 决定 mutable 区大小 + 自动驱逐触发点;_readOnlyLagBytes = MutableFraction × (PageCount-1) × PageSize 决定 mutable→readonly 转换点。

6.2 FlushedUntilAddress 推进路径

  • FlushUntil(untilAddress) / FlushUntilAsync:持久化契约面(写穿 + fsync)。_flushGate 串行门保证并发 flush 区间不重叠——「读水位快照 → 迭代刷页 → 推进水位」三步原子,避免两个并发 flush 的区间重叠触发关页链 Shift→OnPagesClosed→FreePage 把对方迭代中的页置 null
  • FlushUntilTail() / FlushUntilTailAsync:tail 原子快照(tail 在 _tailLock 内原子读取,读与用之间不被并发推进混入;快照之后的并发写入由后续 flush 覆盖,水位 CAS128 单调)
  • WriteThroughUntil(untilAddress):容量写穿(驱逐链专用,零 fsync)——页复用前数据已在设备,进程崩溃安全,断电窗口归上层 checkpoint/Committed 档承担

flush 路径必调 InvalidateColdPageCache(页内容随追加演进,flush 后缓存快照即陈旧——W6 缺陷根因修复:不失效则同页后续冷读永久供陈旧快照,resolver 回读 key 不匹配 → Find=Empty)。

在途 flush 登记与关页 worker 互斥(_ongoingCloseLock 内登记区间,worker 走到与在途区间相交的页即停止本轮回收,由下一次关页触发续走)。

6.3 meta 持久化(IRingMetaPolicy

  • WriteMeta / WriteMetaAsync:更新 meta 缓冲(水位 + payload)+ Commit 落盘。flushedUntilOverride 参数:Transport 回落 MetaHost 时的 1-fsync Prepare 优化——先记 dataTail 再随数据同页 flush,避免 Managed 那种"先 flush 再记水位"的 2 次 fsync
  • WriteMetaRecord(private protected):把内层 meta block 作为 FLAG_ENTRY_IS_META record 追加到 TailAddress(纯内存写:分配 tail + 写 header + 写 payload + CRC + Seal,零 device IO;落盘靠 Prepare 末尾 FlushUntil)。flags 顺序约定:写 header 时就设全 IS_META|VALID|SEALED,再 FillCrc(CRC 覆盖含 flags 字段),最后 Seal 幂等
  • BuildMetaPayload:6 持久化指针 + LastCommittedSeq + LastPreparedSeq + OverflowTailAddress + KeySize + CommittedTailAddress
  • MetaLayout(sealed 嵌套):实现 IMetaLayout<RingMetaHeader, RingMetaPayload>——12B 规范 header + 结构化 payload + opaque 插槽;旧块短 payload 零扩展降级(新字段读默认值,优雅不抛)
  • MetaHost(sealed 嵌套):Transport 未注入传输时的回落——meta record 作为 FLAG_ENTRY_IS_META 追加到 ring 流末尾;读回 = 倒序扫 ring 流找最后一条有效 IS_META record 的 payload

详见 meta.md

6.4 与 StorageEngine 关系

  • 主引擎构造期 Create(纯装配零 IO),OnInitializeBeginInitialize(非阻塞)
  • 溢出引擎(OverflowPolicy=Enabled 时构造,引擎名 .overflow 后缀,与主引擎同 fs、同几何)
  • meta 引擎(MetaPolicyKind=Managed 时构造,引擎名 .meta 后缀,段大小 ≥ 1MB,不分段)
  • 三引擎在 OnInitializeBegin 并行启动,WaitForDependenciesAsync 异步 join;引擎就绪由 DefaultRingRecovery.RecoverAsync 开头 WaitForReadyAsync 保证
  • 水位线归结构层:引擎自恢复 MinAddress/AllocatedTail,结构水位走 meta/扫盘,不下传
  • _dataStart 构造时由首次 Allocate 确定(GetDistance 锚点,100% 确定);pageSeq = GetDistance(_dataStart, addr) >> PageSizeBitspageIntra = GetDistance(_dataStart, addr) & PageSizeMask
  • 热路径快路径:同段(Ring 单段是常态)时 DistanceFromDataStart = 纯 long 减法,零引擎调用;跨段才 fallback 到 engine.GetDistance

7. 恢复协议

7.1 四级回退(DefaultRingRecovery.OnRecoveryCoreAsync

① RingRecoveryHints.RecoveredTail(外部主动注入最高优先级)
   → hints.FlushedUntilAddress(次选)
② meta(O(1) 正路):MetaPolicy.LoadAsync → RingMetaPayload
   + KeySize 锚点校验(防拿错特化开同一卷,fail-fast)
   + 2PC 水位还原(LastCommittedSeq/LastPreparedSeq/CommittedTailAddress)
③ 引擎 CommittedTail(已落盘区域)
④ 扫盘找 torn write 边界:
   MagicLocator 倒序找最后含 magic 的页(Monotone 快速档)
   → ForwardScanRecordsAsync 前向逐页扫 record,CRC 校验求精
   → 同页撕裂重同步(坏帧按对齐粒度步进续扫,不连带丢同页后续有效帧)

7.2 RingRecoveryHints 注入

public readonly struct RingRecoveryHints
{
    public LogicalAddress? BeginAddress { get; init; }
    public LogicalAddress? HeadAddress { get; init; }
    public LogicalAddress? FlushedUntilAddress { get; init; }
    public LogicalAddress? RecoveredTail { get; init; }            // 上层快照场景注入,优先于扫盘
    public LogicalAddress? OverflowTailAddress { get; init; }      // 溢出引擎尾
}

7.3 自愈读(read-protection-tiering v2)

不变式(读安全性由不变式承担,不依赖调用方编排):

  • I1 写穿先行addr < FlushedUntilAddress → 数据已在引擎
  • I2 水位内完整addr < SafeSnapshotTail → 页池必有完整 record(写者写完才推进水位)
  • I4 内容指纹:magic + CRC32C 校验

读分档判据 = IsHotRecord(dist)head ≤ addr < SafeSnapshotTail → 页池直读;addr < head → 页已回收(强制设备读,防同形记录复用别名假数据);addr ≥ SafeSnapshotTail → 设备读。热直读经 magic+CRC 校验失败自动回退设备重读——页池与设备互为自愈。

7.4 锚点 W 与 Index 协议

① Ring.CreateAsync → 恢复水位;锚点 W = ring.ReadOpaqueMeta()
   (无锚点 / 损坏 / W 越过当前尾 → 回退 BeginAddress——宁可旧多重放)
② index 拉流重放:ScanAsync(W, TailAddress) 逐条 Insert 自建
   (Hash/SortedIndex 有主存储帧时优先载帧,见 structures.md §4)

锚点 W = 索引上次主存储 dump/重放完成的水位,经 Ring 的 opaque 随水位线原子落盘;每次 dump/重放后 ring.SetOpaqueMeta(W) 即可。详见 meta.md

7.5 溢出引擎尾恢复

RecoverOverflowTail 三级回退(同主流程):

  1. hints.OverflowTailAddress 注入
  2. meta 的 OverflowTailAddress 字段
  3. OpenSequentialReader 扫描溢出帧(逐帧 magic + Skip payload,恢复容忍不验 CRC,上层读时再校验)
  4. 引擎 AllocatedTail 近似

恢复后引擎尾对齐:_overflowTailAddress 落后于引擎物理尾时(撕裂写预留/Wasted 区间),ReclaimTail 退齐——否则新溢出帧从撕裂区中间分配,与遗留 Wasted 区间相交被读门拦截。


8. 反模式

# 反模式 后果 / 正解
1 写编排反序:先 index.Insertring.Write 索引指向不存在的数据。正序:先 ring.Write 得地址 → 再 index.Insert
2 零拷贝 span 跨 awaitGetValueSpan 返回值跨 await 持有 span 仅同步栈内合法;跨 await/跨线程消费必须走拷贝 API(TryGetValue/GetValue)——API 形态强制,不依赖约定
3 绕过 [RingKey] 直接继承开放泛型new BlittableRing<TKey>(...) CS0122 编译错(protected internal 闸门);封闭薄类是唯一消费面
4 跨实例组合 DeleteOnClose=true 跨重启组合必须 DeleteOnClose=false(否则索引重建时真相源被清)
5 给地址直达读加会话/索引GetValue(addr) 前先 EnterScope 地址即句柄——GetValue(addr) 永久零税。读路径已退役 epoch 保护,调用方编排不必要
6 TruncatePrefix 越过 FlushedUntilAddress InvalidOperationException——未落盘数据会丢。截断点必须 ≤ FlushedUntilAddress
7 TruncateSuffix 落入已驱逐区< SafeHeadAddress 抛——事务窗口横跨已驱逐页,无安全回退边界。Abort 前置窗口内不应发生大规模驱逐
8 WriteBatch 跨 await 持有:ref struct 不跨 await,但 using 范围跨 await 写锁语义——批窗口持 epoch,跨 await 持有 = epoch 嵌套警告。批在同步栈内开闭
9 Overflow=Enabled 时分段写超阈值 Write(key, prefix, value)InvalidOperationException——用连续 value 的 Write/WriteAsync
10 UpdateValue overflow→inline 回退时超原 inline 槽容量 抛——newValue.Length > originalInlineCapacity。原 inline 槽容量 = allocated - HeaderSize - keyLen
11 MetaPolicyKind=Disabled 时 SetOpaqueMeta 抛——未开启 meta 持久化,opaque 登记被拒。配置 Managed/Transport 或移除 opaque 写入
12 跨特化开同一卷RingOfLongRingOfGuid 的卷) ValidateKeySizeAnchor fail-fast——盘上 KeySize 与实例 TKey 不符,避免静默 key 损坏

9. 想深入?指路

想懂什么 去哪
总览(七结构选型 / 组合 KV / 恢复模型) structures.md
横切协议(opaque 搭车 / 块格式 / 自定义传输 / 锚点 W) meta.md
Session 协调协议(写/读/检查点三 op、悬挂裁决、故障模型) session.md
引擎使用(读写/水位/恢复/Compact/段表) storage-engine.md / segment-table.md
Lease 协议(raft 共识 lease) lease-protocol.md
同族其他结构 log.md / index.md / versioned-metadata.md / mirror.md / snapshot.md
性能基线(点查/写/恢复/并发扩展) perf/structures-perf-baseline.md
源码(base/Settings/Contracts/各 partial) src/TC.Tier.Runtime/Structures/Ring/

机制级细节(CAS128 水位基座、帧字节布局、溢出分块骨架、2PC 内部状态机、撕裂重同步扫描器)按需从源码注释与设计稿(docs/design/)查阅——使用面不需要这些细节;能力全集以类型 XML 注释为准。