土法炼钢 · 系统与基础设施

【Ceph RADOS】EC 路径:ECBackend、整条带写与 RMW 的代价模型

文章导航

分类入口
storagedistributed
标签入口
#ceph#erasure-coding#ecbackend#rmw#full-stripe-write#jerasure#isa-l#ec-overwrite#squid#v19.2.5

目录

纠删码(erasure coding,EC)允许 Ceph 以低于三副本的存储开销换取同等的数据持久性,但它为写路径引入了新的复杂度:当写请求不能填满整个条带时,必须先读出旧数据再计算校验,这就是读改写(Read-Modify-Write,RMW)代价。本篇拆解 ECBackend 的实现路径、整条带写与 RMW 的边界,以及 EC 修复路径;编码理论的基础(GF 域、MDS 码、XOR 纠错等)见 纠删码(storage/49),本篇不重写。

本文是「Ceph / RADOS 存储内核」系列第 11 篇(共 18 篇)。→ 系列目录

篇目 核心内容
第 10 篇 · Recovery / Backfill / Scrub reservation、async recovery、mClock 边界
第 11 篇 · EC 路径 ECBackend、整条带 vs RMW、EC 修复路径
第 12 篇 · RBD image→对象、krbd vs librbd、快照/克隆边界

上一篇Recovery / Backfill / Scrub · 下一篇RBD

版本锚定:Ceph Squid v19.2.5docs.ceph.com/en/squid/rados/operations/erasure-code/docs.ceph.com/en/squid/dev/osd_internals/erasure_coding/;源码 src/osd/ECBackend.cc @ v19.2.5。编码理论基础见 纠删码(storage/49)


一、ECBackend 与 ReplicatedBackend 的分工

Ceph 的 PG 层通过 PGBackend 接口屏蔽了副本(replicated)和纠删码(erasure coding)两种存储策略的差异。PrimaryLogPG 调用 pgbackend->submit_transaction() 时,并不关心底层是三副本还是 4+2 EC。

两个实现的核心差异:

维度 ReplicatedBackend ECBackend
对象粒度 整个对象发给每个副本 OSD 对象被切成 \(k\) 个数据分片 + \(m\) 个校验分片,分片散布在 \(k+m\) 个 OSD 上
写语义 直接发 MOSDRepOp 给所有 Replica 全条带写(direct)或 RMW(读→编码→写)
读路径 从任意一个 acting OSD 读 至少读 \(k\) 个分片,解码后拼接
修复(recovery) 从好的副本拷贝整个对象 从任意 \(k\) 个存活分片重建缺失分片
存储开销 \(n\) 副本:\(n \times\) 原始大小 \(k+m\) 分片:\(\frac{k+m}{k} \times\) 原始大小

k=4, m=2(4+2 EC)为例:总存储开销为原始大小的 1.5 倍,而三副本的开销是 3 倍。空间效率相差一倍,代价是写路径的 RMW 开销和读路径的解码开销。


二、条带(Stripe)的概念

EC 把对象数据按 条带(stripe) 组织。一个条带的大小是 \(k \times \text{stripe\_unit}\),其中 stripe_unit(chunk 大小)由创建 EC pool 时的 stripe_unit 参数指定(默认 4 KiB,可配置)。

对于 4+2 EC,若 stripe_unit = 4 KiB: - 条带大小 = \(4 \times 4 = 16\) KiB; - 对象数据按 16 KiB 一组切分为条带; - 每个条带的 4 个 4 KiB 数据 chunk 分别存到 4 个数据 OSD; - 对应的 2 个 4 KiB 校验 chunk 存到 2 个校验 OSD。

RADOS 对象在 EC 池中的实际存储:

object "foo" (logical 100 KiB)
  → stripe 0 : shard 0..3 (data) + shard 4..5 (parity)  → 6 个 OSD
  → stripe 1 : shard 0..3 + 4..5                         → 同 6 个 OSD
  → ...
  → stripe 6 : shard 0..3(最后一个条带,可能不足 16 KiB)

每个 OSD 存储对象的一个 shard(包含所有条带的该 OSD 部分),shard 命名为 <pgid>/<oid>#<shard_id>


三、整条带写(Whole Stripe Write)

开发者文档 ECBackenddev/osd_internals/erasure_coding/ecbackend/)把单条带写分成数据流选择;WHOLE STRIPE WRITE 是其中最简单的一支:Primary 已持有该条带全部 (k) 个数据块所需字节,本地算 (m) 个校验块,再发给各 shard。

flowchart LR
  A["write 覆盖整条带\nW = K"] --> B["Primary EC encode"]
  B --> C["MOSDECSubOpWrite\n→ 各 shard OSD"]
  C --> D["各 OSD BlueStore 落盘"]
  D --> E["Reply 收齐 → commit 路径"]

要点:

  1. 应用写可能跨多条带;ECBackend 先 tessellate 成「若干整条带 + 至多两个部分条带」。
  2. 整条带:无读旧分片;网络放大约为 ()(相对逻辑用户字节),低于三副本的 (3)。
  3. 编码库(Jerasure / ISA-L 等)只在 Primary(或文档指定角色)上算校验。

适用:对齐的大块顺序写、全新对象填充——RGW 大对象 PUT 常接近这条路径。编码理论(MDS、RS 距离)见 storage/49;本篇只钉 Ceph 数据流


四、读改写(Read-Modify-Write)

当写只覆盖条带内 (W < k) 个数据块(不对齐、部分覆盖),文档 READ-MODIFY-WRITE 路径生效:

flowchart LR
  A["部分条带写 W < K"] --> B["读未改动的数据 shard"]
  B --> C["与新数据合并\n重算 parity"]
  C --> D["写回被改动的 data/parity shard"]

步骤(与 ecbackend 文档一致):

  1. 识别本条带内哪些数据块不变;
  2. 从对应 shard 读出那些块;
  3. 与新写入合并,重算校验块;
  4. 把修改过的块写回各 OSD;
  5. 进入与整条带写相同的 commit / rollforward 一致性协议(见下)。

代价模型(符号,非实测 IOPS):记一次小写逻辑长度为 1 个条带内的部分更新,则粗略放大可写成「约读 (k-W)(或实现上读齐所需)+ 写涉及的数据/校验 shard」。文档用 (W K) 描述被写数据块数。对随机小写,额外读 RTT 往往主导延迟——这是 EC 不宜作为延迟敏感随机写主路径 的机制原因,而不是某张未复核的 IOPS 表。

4.1 Commit 与 rollforward

不论整条带还是 RMW,落盘是两阶段:commit(可能留下 rollback 旁路信息)与 rollforward(acting 成员都 commit 后丢掉回滚信息)。覆盖写时,rollback 信息曾以 sparse clone 等形式存在;BlueStore 提供更高效原语后,实现仍遵循「先可回滚提交,再前滚」的不变量(见 ecbackend OSD Object Write and Consistency)。ExtentCache / Pipeline 防止同一对象上 RMW 与不可缓存 op(如 clone) 危险并发——细节见 ExtentCache.hECBackend::waiting_*

4.2 与「伪造性能」的边界

可以说:RMW 比整条带多至少一轮读依赖。不可说:某 (k,m) 在「所有 NVMe 集群」上小写延迟等于副本的 (N) 倍——除非给出本机实验台账。本站无该台账。


五、EC overwrite 功能

Ceph Luminous(12.x)为 RBD 引入了 allow_ec_overwrites 功能,允许 EC 池支持覆盖写。在此之前,EC 池不支持对已存在对象的覆盖写(只支持全新对象写入),因为没有 RMW 机制。

allow_ec_overwrites 需要满足的前提:

  1. BlueStore 后端(FileStore 无法支持 EC overwrite 所需的原子性保证);
  2. 条带/shard 写入与本地分配语义兼容:官方要求与 BlueStore 能力对齐,避免不安全的中间状态;具体约束以 Squid 操作文档与当时源码检查为准(常与 min_alloc_size、checksum 路径相关)。

启用后,EC 池可作 RBD 后端并允许覆盖写,但 RMW 税仍在:小于条带对齐的随机写不会因为开关打开而变成整条带写。顺序大写 / 冷数据仍是 EC 主场;延迟敏感随机写仍宜副本池。


六、EC 修复路径

EC 池的修复(recovery)与副本池不同:不需要从别处拷贝整个 shard,而是利用编码冗余\(k\) 个存活分片重建缺失的分片。

6.1 修复触发

当一个 OSD 宕机,其上存储的所有对象的某个 shard 变为不可访问。PG 进入 active+recovering 状态,ECBackend 识别缺失的 shard,发起修复。

6.2 修复流程

flowchart LR
  A["OSD down\nshard j 不可用"] --> B["Primary 收集\n任意 k 个存活 shard\n(MOSDECSubOpRead)"]
  B --> C["解码:EC decode\n重建 shard j 数据"]
  C --> D["将重建的 shard j 写到\n新 OSD 或恢复的 OSD\n(MOSDECSubOpWrite)"]
  D --> E["PG 修复完成"]

修复只需读 \(k\) 个分片(而非 \(k+m\) 个),因此即使有 \(m\) 个 OSD 同时不可用,修复仍可进行。修复的网络 I/O:读 \(k\) 个分片 + 写 1 个分片(仅重建缺失的那个),比副本池修复(读整个对象 + 写整个对象)网络效率更好——特别是当 \(k\) 大、\(m\) 小时,每个分片只有原始对象大小的 \(\frac{1}{k}\)

6.3 EC 降级读

当有分片不可用但数量不超过 \(m\) 时(即至少 \(k\) 个分片存活),EC 池仍可响应读请求,但需要执行降级读(degraded read):从 \(k\) 个存活分片读出数据,解码后返回客户端。降级读比正常读(只读 1 个分片不需解码)延迟更高,且对剩余 \(k\) 个 OSD 产生额外 I/O 负担。


七、编码插件:Jerasure、ISA-L 与 LRC

插件 说明
jerasure Jerasure 常见默认;RS/Cauchy 等;评测见 Plank et al. FAST 2009
isa Intel ISA-L 利用 SIMD 加速 GF 运算;相对 Jerasure 的加速比依赖 CPU 与块大小,本篇不引用未复核的「快 N 倍」营销句
lrc Ceph LRC plugin 局部可修复码工程化;文档 erasure-code-lrc
shec SHEC 另一类局部修复思路

生产选择:在支持 AVX2 等的 x86 上常优先评估 isa;最终以本集群 encode/decode CPU 剖析为准。

7.1 LRC 文献必须对齐的两点

  1. 形式化 locality:Gopalan, Huang, Simitci, Yekhanin,《On the Locality of Codeword Symbols》,IEEE Transactions on Information Theory, 2012——定义码字符号的局部可修复性,是理论锚点。
  2. 工业 LRC:Huang et al.,《Erasure Coding in Windows Azure Storage》,USENIX ATC 2012(Best Paper)——引入 Local Reconstruction Codes,强调单故障重建少读分片;文中 LRC(12,2,2) 等参数对应 Azure 存储开销与重建代价的设计点。不是 FAST 2012

Facebook f4 等系统有各自的局部修复/EC 变体;与 Azure LRC、与 Ceph lrc 插件不是同一实现。Ceph 插件提供「局部修复」方向的可配置编码,生产采用率仍低于 jerasure/isa——这是部署观察,不是论文结论。

可证伪命题:「Ceph lrc 插件 ≡ Azure ATC’12 生产码」→ 假;「LRC 类码可降低单盘故障重建的分片读取数」→ 在 Huang/Gopalan 假设下真。


八、副本池 vs EC 池:边界与选择

维度 副本池 EC 池
空间效率 \(1 / n\)(3 副本时 33%) \(k / (k+m)\)(4+2 时 67%)
随机小写延迟 低(直写到所有副本,无 RMW) 高(有 RMW:额外读 RTT + 编码计算)
顺序大写延迟 中(\(n\) 份网络 I/O) 低(\(\frac{k+m}{k}\) 份网络 I/O,少于 3 副本)
读路径 从任意一副本读,无需解码 需读 \(k\) 分片解码(降级时)或 1 分片(正常时)
快照/克隆 支持(COW 语义) 支持(需 allow_ec_overwrites + BlueStore)
对象覆盖 直接支持 需 allow_ec_overwrites(Luminous+);RMW 代价
适合场景 RBD(随机 I/O)、CephFS(POSIX 工作负载)、对延迟敏感 冷数据归档、对象存储大文件(RGW)、顺序读写为主

选择边界的核心问题

  1. 工作负载是否以顺序大写为主?如果是,EC 的整条带写路径高效,空间节省显著。
  2. 是否有频繁随机小写?如果有,RMW 代价使 EC 的写延迟不可接受。对于 RBD/CephFS,建议走副本池。
  3. 空间成本是否是主要约束?3 副本需要 3 倍存储,4+2 只需 1.5 倍;在 PB 级对象存储中,空间成本差距是主要决策因素。

RGW(Ceph 对象网关)是 EC 最常见的生产场景:S3/Swift 对象通常是一次性大文件写入(PUT 整个对象),读以完整读为主,几乎不做随机覆盖写,是 EC 整条带写路径的理想工作负载。


九、学术谱系与争论

9.1 谱系

RS / RAID-6 实践
  → 分布式 RS(GFS/Colossus、HDFS-RAID 等)
  → 局部可修复:Gopalan et al. TIT'12;Huang et al. ATC'12(Azure LRC)
  → Ceph:Jerasure/ISA-L 插件 + ECBackend 整条带/RMW(dev 文档)
  → 理论菜谱:[storage/49];本篇只钉 OSD 路径

Jerasure 生态的系统评测锚点:Plank et al.,《A Performance Evaluation and Examination of Open-Source Erasure Coding Libraries for Storage》,USENIX FAST 2009。Ceph 开发者文档中的 ECBackend 策略(whole stripe / RMW / commit-rollforward)是工程规范锚点,与编码数学分工明确。

9.2 争论(可证伪表述)

副本 vs EC

检验:固定 (k,m,),对比对齐大写 vs 4 KiB 随机覆盖的延迟分布;本站无统一排行榜。

LRC vs RS:Azure/ATC’12 显示局部重建读更少;Ceph 默认生态仍以 RS 插件为主,因参数与工具链更简单。检验:同存储开销下单 OSD 故障重建的网络字节是否下降——需在具体 plugin 配置下测,不能用 Azure 论文数字直接标到 Ceph lrc


十、开放问题

1. EC 池上 RMW 的 I/O 放大定量分析。对于给定的 \((k, m, \text{stripe\_unit})\) 配置,RMW 场景下单次小写产生的实际 I/O 放大倍数(读 \(k\) 分片 + 写 \(k+m\) 分片,共 \((2k+m) / 1\) 倍逻辑写量)在 HDD vs NVMe 上的实际延迟差异,是选择 EC 还是副本池的关键依据,但需要在特定集群上实测。本文不编造未实测的具体数字。

2. EC 与 mClock 的交互。EC 修复(recovery)也使用 mClock 调度(第 10 篇),但 EC 修复的一次操作实际上涉及 \(k\) 个 OSD 的读和 1 个 OSD 的写,比副本修复(1 个 OSD 读 + 1 个 OSD 写)的 I/O 放大更大。mClock 的 recovery_op 带宽配额是按”ops 数量”限速,而非按”实际字节 I/O”限速,因此 EC 修复在相同 recovery_op 配额下会消耗更多实际带宽。这个不对等性是否需要在 mClock 配置中手动补偿,目前官方文档没有明确建议。

3. EC 元数据一致性与 split brain。EC 池的 PG 在 min_size(通常等于 \(k\))个 OSD 存活时可以响应读,但写需要 \(k+m\) 全部存活(否则无法保证校验一致性)。当网络分区导致 Primary 与部分校验 OSD 断开时,EC 池的写可用性低于副本池(副本池只需超过半数副本存活即可写),这是 EC 在可用性上的固有劣势,设计分布策略时需要考虑故障域覆盖 \(m+1\) 个不同机架。


十一、参考资料

规范 / 官方文档(A 级)

源码(A 级)

论文(A 级)

站内


系列目录 · 上一篇:Recovery / Backfill / Scrub · 下一篇:RBD

读完这篇,下一步读什么

优先读同系列或同问题的下一篇,把单篇消费变成主题集群。


By .