Files
wiki/存储/Ceph/知识库/02-RADOS-数据寻址与存储引擎.md
T
2026-09-18 13:49:11 +08:00

238 KiB
Raw Blame History

基于 Ceph 官方文档整理 | 文档版本:Tentacle v20.2.4 | 抓取日期:2026-09-17 | 源文:ceph/ceph@v20.2.4 doc/rados/ 官网入口:https://docs.ceph.com/en/tentacle/rados/


第 02 册 · RADOS 核心篇(上):数据寻址、CRUSH、PG 与存储引擎

本册导读

本册覆盖 Ceph 官方文档 doc/rados/ 目录下与「数据是怎么落盘的」直接相关的 25 个官方页面(全部为 Tentacle v20.2.4 的 rst 源文):

# 官方页面(逻辑页名) 本册章节
1 rados/index 导言
2 rados/operations/index 导言
3 rados/operations/data-placement 一
4 rados/operations/crush-map 二
5 rados/operations/crush-map-edits 二、三
6 rados/operations/placement-groups 四
7 rados/operations/pg-concepts 五
8 rados/operations/pg-states 五
9 rados/operations/pools 六
10 rados/operations/upmap 十
11 rados/operations/erasure-code 七
12 rados/operations/erasure-code-profile 七
13 rados/operations/erasure-code-jerasure 七
14 rados/operations/erasure-code-isa 七
15 rados/operations/erasure-code-lrc 七
16 rados/operations/erasure-code-shec 七
17 rados/operations/erasure-code-clay 七
18 rados/operations/pgcalc/index 四
19 rados/configuration/index 导言
20 rados/configuration/bluestore-config-ref 八
21 rados/configuration/filestore-config-ref 八
22 rados/configuration/journal-ref 八
23 rados/configuration/storage-devices 九
24 rados/configuration/pool-pg-config-ref 四、六
25 rados/configuration/mon-osd-interaction 十一

建议阅读顺序

  1. 第一遍(建立心智模型):一(数据放置)→ 二(CRUSH map)→ 四(PG)→ 六(Pool)。这四章构成「一个字节怎么找到它的家」的完整故事。
  2. 第二遍(稳态运维):五(PG 状态机,日常 ceph -s 看到的就是它)→ 十(upmap 均衡)→ 十一(MON/OSD 心跳与故障判定)。
  3. 第三遍(容量与介质选型):七(纠删码)→ 八(BlueStore)→ 九(设备选型)。
  4. 第四遍(动手改):三(CRUSH map 编辑实战)—— 这一章是唯一会「改坏集群」的章节,建议先在与生产同构的测试集群上跑一遍 crushtool --compare。

阅读前须知:本文档中「文档未明确」的含义

⚠️ 重要说明(关于配置项表格):Ceph 官方文档的配置参考页使用 Sphinx 的 .. confval:: <option_name> 指令,其 type / default / description 字段是在文档构建期从 Ceph 选项数据库(源码 src/common/options/*.yaml.in)拉取渲染的,rst 源文本身不包含这些字段的值。本手册严格基于 rst 源文撰写,因此:

  • 凡是源文散文中明确写出的默认值/数值(例如 mon_target_pg_per_osd default 100、pg_num default 32、心跳 6 秒 / 20 秒 grace),本手册照录并给出出处;
  • 凡是源文只有 .. confval:: 指令、散文未给出数值的,本手册在「类型」「默认值」列一律写 「文档未明确」,不做任何推测或填充;
  • 「含义」列取自该配置项所在章节的散文描述(这是官方文档对该选项的权威解释);
  • 「调参建议」列属于本手册的补充,均带 💡 补充 标记(若该行无标记,说明建议本身出自原文)。

所有确实无法从本册 25 个源文中得到答案的问题,统一汇总在文末的 ## 文档未明确项 一节。


一、数据放置(Data Placement Overview)

1.1 Ceph 存储集群的构成

Ceph Storage Cluster 是基于 RADOS(Reliable Autonomic Distributed Object Store) 的基础设施,由三类守护进程构成:

守护进程 职责(原文表述)
Ceph OSD Daemon(OSD) stores data as objects on a storage node — 在存储节点上以对象形式存储数据
Ceph Monitor(MON) maintains a master copy of the cluster map — 维护集群 map 的主副本
Ceph Manager(MGR) manager daemon

规模上:一个 Ceph Storage Cluster 可以包含数千个存储节点;最小系统至少需要一个 Ceph Monitor 和两个 Ceph OSD Daemon`(用于数据复制)。Ceph File System、Ceph Object Storage、Ceph Block Devices 都是从这个集群读写数据。

1.2 四个核心的数据放置规划概念

官方把 data-placement planning 归纳为四个概念(Pools / Placement Groups / CRUSH Maps / Balancer),逐条对照原文:

(1)Pools(存储池)

Ceph stores data within pools, which are logical groups used for storing objects.

Pools 是存储对象的逻辑分组。一个 Pool 负责管理三件事:

  • placement groups 的数量
  • replicas 的数量
  • 该 pool 使用的 CRUSH rule

此外:向 pool 写数据必须是对该 pool 有权限的已认证用户;Ceph 可以对 pool 做快照(snapshot)。

(2)Placement Groups(PG,归置组)

Ceph maps objects to placement groups. Placement groups (PGs) are shards or fragments of a logical object pool that place objects as a group into OSDs.

关键点:

  • PG 是逻辑 pool 的分片/片段,把对象成组地放进 OSD。
  • PG 存在的首要意义:减少 per-object metadata。Ceph 以 PG 粒度而非对象粒度管理数据("this scales better than would managing individual RADOS objects")。
  • PG 越多越均衡:原文明确举例 —— 每 OSD 100 个 PG 比每 OSD 50 个 PG 均衡性更好;另一处(pools.rst)给出典型配置目标是每 OSD 约 150 个 PG。
  • 每个 RADOS object 映射到唯一一个 PG,每个 PG 恰好属于一个 pool。

(3)CRUSH Maps

CRUSH(Controlled Replication Under Scalable Hashing)让 Ceph 在扩展时避免三类陷阱:performance bottlenecks(性能瓶颈)、limitations to scalability(可扩展性限制)、single points of failure(单点故障)。

CRUSH map 向 CRUSH 算法提供集群的物理拓扑,使算法能够决定:

  1. 一个对象及其副本的数据应该存在哪里;
  2. 如何跨 failure domains(故障域) 分布数据以提升数据安全性。

CRUSH map 可以通过编码 chassis / rack / 物理邻近性 / 共享电源 / 共享网络 / 故障域 等信息,来建模并应对相关性设备故障(correlated device failures)。举例:为了保证不出现并发故障,可能希望副本落在不同的 shelf、rack、power supply、controller 或物理位置。

(4)Balancer

The balancer is a feature that automatically optimizes the distribution of placement groups across devices...

Balancer 自动优化 PG 在设备间的分布,目的是:(a) 均衡数据分布;(b) 最大化可存储的数据量;(c) 在 OSD 之间均匀分摊负载。

1.3 使用默认值 vs 定制

原文态度很明确:

  • 测试集群初始搭建:可以用默认值("Default values are recommended for a test cluster's initial setup")。
  • 规划大型集群:必须定制("when planning a large Ceph cluster, values should be customized"),定制时要参考 pool、PG、CRUSH 各自扮演的不同角色。

1.4 完整寻址链路:file → object → pool → PG → OSD

下面把整条链路拆成 5 跳。第 0 跳(file → object)发生在 RADOS 之上的接口层(RBD / CephFS / RGW),不在本册 25 个源文覆盖范围内,因此只在下面做一句说明,不做展开。

跳 环节 由谁完成 依据(源文表述)
0 file / image / S3 object → RADOS object 上层接口(RBD / CephFS / RGW) ❗本册源文未涉及(见「文档未明确项」第 1 条)
1 RADOS object → pool 客户端显式指定(pool name / pool id) "objects are stored within pools";"each placement group belongs to exactly one Ceph pool"
2 object → PG 客户端计算(Ceph client calculates which PG a RADOS object should be in) "the client hashes the object ID and performs an operation involving both the number of PGs in the specified pool and the pool ID"
3 PG → OSD set( Acting Set / Up Set) CRUSH 算法(CRUSH computes storage locations) "CRUSH uses a map of the cluster ... to map data to OSDs";PG 的 OSD 集合由 CRUSH 按 rule + failure domain 算出
4 客户端 → Primary OSD 直连 I/O 客户端 "CRUSH allows Ceph clients to communicate with OSDs directly rather than through a centralized server or broker"

第 4 跳是 CRUSH 设计的核心价值:没有中心化的元数据服务器/代理。客户端拿到了 OSD map + CRUSH map 之后,自己就能算出数据在哪台 OSD 上,直接发起 I/O。这直接消除了单点故障、性能瓶颈和物理扩展上限。

第 2 跳(object → PG)的细节

源文给出的信息是:

The Ceph client calculates which PG a RADOS object should be in. As part of this calculation, the client hashes the object ID and performs an operation involving both the number of PGs in the specified pool and the pool ID.

即:参与运算的三个输入是 ① 对象 ID 的 hash 值 ② 该 pool 的 PG 数量(pg_num) ③ pool ID。

PG 的标识形式可以从官方文档的示例命令中看到(ceph pg map 1.6c 返回 osdmap e13 pg 1.6c (1.6c) -> up [1,0] acting [1,0]),即 <pool_id>.<pg_id_hex>。

💡 补充:下面给出一个用于建立直观理解的教学演算,其中的 hash 值是虚构的(真实实现使用 rjenkins1,参见 osd pool ls detail 输出中的 object_hash rjenkins),不要把它当作可复现的计算结果:

假定 pool rbd 的 pool_id = 2,pg_num = 256,对象名为 rb.0.1234.6b8b4567.000000000001。

  1. h = hash("rb.0.1234.6b8b4567.000000000001") → 假设得到 h = 0x9A3F1C07
  2. h mod pg_num = 0x9A3F1C07 mod 256 = 0x07 = 7
  3. 该对象所属 PG 标识为 2.7
  4. 客户端再用 CRUSH(以 2.7 作为输入,配合 pool 的 CRUSH rule)算出 OSD 集合,例如 up [1,0] acting [1,0]
  5. 客户端直连 Primary = OSD 1(Acting Set 的第一个成员)发起读写

由于 pg_num 只取 2 的幂(见第四章),mod pg_num 在二进制上等价于取 hash 的低 log2(pg_num) 位,这也是为什么把 pg_num 调成 2 的幂对 CRUSH 效率有"边际改善"(pgcalc 页面原文:"The nearest power of 2 provides a marginal improvement in efficiency of the CRUSH algorithm")。

第 3 跳(PG → OSD)的细节

PG 与 OSD 是多对多的关系:

  • 一个 PG 的副本/分片存放在一组 OSD 上。例:size=2 的副本池,每个 PG 把对象存在两个 OSD 上。
  • 一个 OSD 不被某个 PG 独占,它被同 pool 或其他 pool 的多个 PG 共享。
  • 若某个 OSD 故障,会给它承载的每个 PG 重新分配一个 OSD,并从存活副本回填全部对象。
  • 改变 pg_num 会改变 CRUSH 函数的输出:新增 PG 后,部分对象会从旧 PG 迁移到新 PG(这就是 split 引发数据迁移的原因)。

1.5 概念关系图(原文 ditaa 转述)

    obj    obj    obj    obj    obj
      \     /               \   /
       v   v                 v v
   +--------------+     +--------------+
   | Placement    |     | Placement    |
   | Group #1     |     | Group #2     |
   +--------------+     +--------------+
          \                   /
           --------  ---------
                   v
            +--------------+
            |     Pool     |
            +--------------+

引自:Data Placement Overview、Ceph Storage Cluster、Cluster Operations


二、CRUSH map 详解

2.1 CRUSH 是什么

CRUSH = Controlled Replication Under Scalable Hashing。它计算存储位置,从而决定如何存、取数据。CRUSH map 包含:

  1. 一份 OSD 列表;
  2. 一层 bucket 层级(host / rack 等);
  3. 一组 rules,规定数据在集群 pool 内如何复制。

深入论文:CRUSH - Controlled, Scalable, Decentralized Placement of Replicated Data(官方文档在多处引用其 Section 3.2 讲 rule、Section 3.4 讲 bucket 类型)。

一条重要的运维事实:

When OSDs are deployed, they are automatically added to the CRUSH map under a host bucket that is named for the node on which the OSDs run.

即:OSD 部署时会自动挂到以其所在节点命名的 host bucket 下。这个行为 + 配置的 CRUSH failure domain,共同保证了副本/EC 分片跨主机分布。更大的集群必须由管理员认真选择 failure domain(例如中型到大型集群典型地跨 rack 分布副本)。

2.2 CRUSH Location

OSD 在 CRUSH map 层级中的位置称为它的 CRUSH location,形式是若干 key-value 对:

root=default row=a rack=a2 chassis=a2a host=a2a1

规则:

  • key 的顺序不重要;
  • key 名(= 左边)必须是合法的 CRUSH type。默认合法 type 包括:root、datacenter、room、row、pod、pdu、rack、chassis、host。(注:此处列出的 9 个与 2.4 节的默认 type 全集略有出入,见「文档未明确项」第 2 条。)

在 ceph.conf 中设置:

crush_location = root=default row=a rack=a2 chassis=a2a host=a2a1

设置后,OSD 每次启动都会校验自己在 CRUSH map 中的位置是否正确,不正确就把自己挪过去。

关闭这个自动管理(在 [osd] 段):

osd_crush_update_on_start = false

原文评价:Note that this action is unnecessary in most cases.(大多数情况下不需要关)

默认值:如果 crush_location 没有显式设置,OSD 使用 root=default host=HOSTNAME,其中 hostname 取自 hostname -s 的输出。

⚠️ 原文警告:从默认值切换到显式设置 crush_location 时,不要忘了带上 root=default,因为已有的 CRUSH rule 会引用它。

自定义 location hook

当某些 location 字段在写 ceph.conf 时还不知道(例如同一份配置要部署到多个 datacenter,rack/datacenter 字段不同),可以用 hook:

crush_location_hook = /path/to/customized-ceph-crush-location
  • hook 是一个可执行文件(通常是脚本);
  • 传入参数形如 --id ID --type TYPE,type 为 osd / mds / mgr / mon;
  • hook 向 stdout 输出单行 CRUSH location 描述;
  • 执行并解析成功后,hook 的输出会替换掉之前设置的任何 CRUSH location。

示例脚本:

#!/bin/sh
echo "root=default rack=$(cat /etc/rack) host=$(hostname -s)"

2.3 CRUSH 结构:Devices / Types / Buckets / Rules

CRUSH map = ① 描述集群物理拓扑的层级(hierarchy) + ② 定义数据放置策略的一组规则(rules)。层级里叶子是 devices(OSD),内部节点对应物理特征或分组(host / rack / row / datacenter ...)。

2.3.1 Devices

  • Device 就是存数据的单个 OSD(通常一个存储盘对应一个 device);
  • 由 id(非负整数) 和 name(通常 osd.N) 标识;
  • Luminous 及以后,OSD 可以有 device class(如 hdd / ssd / nvme),从而被 CRUSH rule 定向选择。

2.3.2 Types and Buckets

"Bucket" 在 CRUSH 语境下指层级中的任意内部节点(host / rack / row ...)。CRUSH map 定义一系列 types 来标识这些节点。默认 type 全集(crush-map.rst):

# type 说明
1 osd(或 device) 叶子
2 host
3 chassis
4 rack
5 row
6 pdu
7 pod
8 room
9 datacenter
10 zone
11 region
12 root 根节点

Most clusters use only a handful of these types, and other types can be defined as needed.(大多数集群只用到其中几个;也可以按需自定义)

在反编译后的 CRUSH map 中,type 以数字 ID 声明(见 3.4 节)。

层级示例(原文 ditaa 转述):

                    +-----------------+
                    | {o}root default |
                    +--------+--------+
                             |
             +---------------+---------------+
             |                               |
      +------+------+                 +------+------+
      | {o}host foo |                 | {o}host bar |
      +------+------+                 +------+------+
             |                               |
        +----+----+                     +----+----+
        |         |                     |         |
   +--------+ +--------+           +--------+ +--------+
   | osd.0  | | osd.1  |           | osd.2  | | osd.3  |
   +--------+ +--------+           +--------+ +--------+

查看集群 CRUSH 层级(含权重):

ceph osd tree

2.3.3 权重(Weight)

  • 层级中每个节点(device 或 bucket)都有一个 weight,表示该设备/子树应存数据的相对比例;
  • 权重在叶子上设置,表示设备大小;
  • 权重会自动向上累加:root 的权重 = 其下所有 device 权重之和;
  • 权重通常以 tebibytes(TiB)为单位。

Weighting Bucket Items(权重惯例):

  • Ceph 用 double 表达 bucket 权重,支持细粒度;
  • 权重是设备容量之间的相对差异;
  • 推荐以 1.00 作为 1 TB 存储设备的相对权重:
    • 0.50 ≈ 500 GB
    • 3.00 ≈ 3 TB
  • 层级中越高的 bucket,其权重 = 它聚合的所有叶子项权重之和。

2.3.4 Rules

Rules 定义数据如何跨层级中的设备分布,既定义 placement,也定义 replication strategy / distribution policy。原文举了三种 rule:

  1. 选一对 target 做两路镜像;
  2. 在两个不同 datacenter 中选三个 target 做三副本;
  3. 跨六个存储设备做纠删码。

创建方式:CRUSH rule 通常可以通过命令行创建,只需指定 pool type(replicated / erasure coded)、failure domain、可选的 device class。只有在极少数情况下才需要手工编辑 CRUSH map 来创建 rule("In rare cases, CRUSH rules must be created by manually editing the CRUSH map")。

# 列出集群已定义的 rule
ceph osd crush rule ls

# 查看 rule 内容
ceph osd crush rule dump

2.4 Device classes(设备类)

  • 每个 device 可以可选地有一个 class;
  • 默认情况下,OSD 在启动时依据其底层设备类型自动把自己设为 hdd / ssd / nvme;
  • Luminous 引入,用于在同一 host 内混插不同设备类型时被 CRUSH rule 定向。
# 显式设置一个或多个 OSD 的 device class
ceph osd crush set-device-class <class> <osd-name> [...]

# 移除(unset)旧 class —— 必须先 unset 才能改设成另一个 class
ceph osd crush rm-device-class <osd-name> [...]

这个"必须先 unset"的限制意义:允许管理员设置的 class 不会被 OSD 重启或脚本改掉。

# 创建针对特定 device class 的放置规则
ceph osd crush rule create-replicated <rule-name> <root> <failure-domain> <class>

# 把新 rule 应用到指定 pool
ceph osd pool set <pool-name> crush_rule <rule-name>

也可以自定义 class 名以做灵活的约束,例如把 OSD 的 device class 设成 nvme 以区别于 SATA SSD,或设成像 ssd-testing / ssd-ethel 这样的任意名字,让 rule 和 pool 可以灵活地"只用"或"避开"某些 OSD 子集。

实现原理:shadow CRUSH hierarchy

Device classes are implemented by creating one or more "shadow" CRUSH hierarchies.

  • 每一个在用的 device class 都对应一棵 shadow hierarchy,其中只包含该 class 的设备;
  • CRUSH rule 随后在相应的 shadow hierarchy 上分布数据;
  • 这种方式对老版本 Ceph 客户端完全向后兼容。
# 查看带 shadow item 的 CRUSH 层级
ceph osd crush tree --show-shadow

Luminous 之前的老集群依靠手工编制的 CRUSH map 来维护 per-device-type 层级,对此有 reclassify 工具 可以帮它们迁移到 device class 而不触发不必要的数据移动(详见 3.7 节)。

2.5 Weight sets(权重集)

为什么需要 weight set

CRUSH 是一个概率性伪随机放置过程,因此实际分布总是对理想分布有偏差。原文比喻:

in the same way that rolling a die sixty times will likely not result in exactly ten ones and ten sixes

weight set 是一组"替代权重",让集群能基于本集群的具体情况(层级、pool 等)做数值优化,从而达成均衡分布。

两种 weight set

类型 特征 兼容性
compat 为每个 device 和每个节点提供单一的替代权重集 向后兼容老版本 Ceph。虽然 weight set 是 Luminous v12.2.z 引入的,但用 compat weight set 时连 Firefly 这样的老客户端也能连上集群
per-pool 更灵活:可以针对每个数据 pool 单独优化放置;还可以对放置的每个位置(position)分别调整权重,从而修正"数据向小权重设备倾斜"的细微偏斜(这种效应通常只在超大规模集群才明显,但会造成均衡问题) 要求所有服务器和守护进程都运行在 Luminous v12.2.z 或更高版本

局限:compat weight set 不能指望修正所有异常 —— 例如不同 pool 的 PG 大小不同、负载水平不同,但 balancer 大多一视同仁。

优先级

  1. 若该 pool 有自己的 per-pool weight set → 用它;
  2. 否则若存在 compat weight set → 用它;
  3. 都没有 → 用普通 CRUSH 权重。

查看

使用 weight set 时,层级中每个节点的权重会在下列命令输出的单独一列显示(列标题为 (compat) 或 pool 名):

ceph osd tree

官方建议:虽然可以手工建立和调整 weight set,但推荐启用 ceph-mgr 的 balancer 模块自动完成(Luminous 及以上)。

命令速查

# ---------- compat weight set ----------
ceph osd crush weight-set create-compat
ceph osd crush weight-set reweight-compat {name} {weight}
ceph osd crush weight-set rm-compat

# ---------- per-pool weight set ----------
ceph osd crush weight-set create {pool-name} {mode}   # mode: flat | positional
ceph osd crush weight-set reweight {pool-name} {item-name} {weight [...]}
ceph osd crush weight-set ls
ceph osd crush weight-set rm {pool-name}

mode 参数释义:

mode 含义
flat 给所有 device 或 bucket 分配同一个权重
positional 对结果放置映射中的每个位置都可能有不同的权重。例如 pool 副本数为 3,则 positional weight set 对每个 device 和 bucket 都有三个权重

2.6 用 CLI 修改 CRUSH map(不手工编辑)

注:以下三小节(add/move OSD、调权重、删 OSD)在正常情况下都不需要手工执行,因为 OSD 创建时会自动把自己加进 CRUSH map 并带上正确权重。

添加/移动一个 OSD

ceph osd crush set {name} {weight} root={root} [{bucket-type}={bucket-name} ...]
参数 说明 类型 必填 示例
name OSD 全名 String 是 osd.0
weight OSD 的 CRUSH 权重,通常是其大小,以 TB 计 Double 是 2.0
root OSD 所在 CRUSH 层级的根节点(通常 default) Key-value pair 是 root=default
bucket-type OSD 在 CRUSH 层级中的位置 Key-value pairs 否 datacenter=dc1 room=room1 row=foo rack=bar host=foo-bar-1

示例:

ceph osd crush set osd.0 1.0 root=default datacenter=dc1 room=room1 row=foo rack=bar host=foo-bar-1

调整 OSD 权重(reweight)

ceph osd crush reweight {name} {weight}
参数 说明 类型 必填 示例
name OSD 全名 String 是 osd.0
weight OSD 的 CRUSH 权重 Double 是 2.0

删除 OSD(从 CRUSH map)

ceph osd crush remove {name}

原文注:OSD 通常是作为 ceph osd purge 命令的结果被从 CRUSH map 移除的,本命令很少需要。

Bucket 相关命令

# 添加 bucket
ceph osd crush add-bucket {bucket-name} {bucket-type}

# 移动 bucket 到不同位置/位置(position)
ceph osd crush move {bucket-name} {bucket-type}={bucket-name}, [...]

# 重命名 bucket(保持在层级中的位置不变)
ceph osd crush rename-bucket {oldname} {newname}

# 删除 bucket
ceph osd crush remove {bucket-name}

参数说明:

命令 参数 说明 类型 必填 示例
add-bucket bucket-name bucket 全名 String 是 rack12
add-bucket bucket-type bucket 类型,该类型必须已存在于 CRUSH 层级中 String 是 rack
move bucket-name 要移动的 bucket 名 String 是 foo-bar-1
move bucket-type bucket 在 CRUSH 层级中的新位置 Key-value pairs 否 datacenter=dc1 room=room1 row=foo rack=bar host=foo-bar-1
remove bucket-name 要删除的 bucket 名 String 是 rack12

⚠️ bucket 必须为空才能被删除:其中不能有 OSD,也不能有其他 CRUSH bucket。

add-bucket 的典型用途(原文给出两个):

  1. 把一批 host 挪到一个新的 rack 级 bucket;
  2. 把新的 host bucket(OSD 节点)挂到一个 dummy root 下,这样这些 bucket 在准备好接收数据之前不会收到任何数据;等准备好了,再把它们移到 default root 或其他 root。

示例:

ceph osd crush add-bucket rack12 rack
ceph osd crush remove rack12

为副本池创建 rule

ceph osd crush rule create-replicated {name} {root} {failure-domain-type} [{class}]
参数 说明 类型 必填 示例
name rule 名 String 是 rbd-rule
root 数据放置所在的 CRUSH 层级节点名 String 是 default
failure-domain-type 用于副本故障域的 CRUSH 节点类型 String 是 rack
class 数据放置所在 device class String 否 ssd

选择 failure domain 是关键决策:

  • host → CRUSH 保证每个副本在不同的 host 上;
  • rack → 每个副本在不同的 rack 中;
  • 应根据集群规模及其 CRUSH 拓扑来选。

关于 root 的一个易错点:整个集群层级通常嵌套在名为 default 的 root 节点下。如果你自定义了层级,可能想让 rule 嵌套在其他节点下 —— 这是可以的:节点类型不重要,rule 不必嵌套在 root 节点下。

为纠删码池创建 rule

EC 池同样要决定 failure domain、放置层级节点(通常 default)、是否限制 device class。但 EC 池的创建方式不同:必须结合所用的 erasure code plugin 精心构造,所以这些决策要写进 erasure-code profile,然后由 profile 生成 CRUSH rule(显式生成,或在使用该 profile 创建 pool 时自动生成)。

# 列出 EC profile
ceph osd erasure-code-profile ls

# 查看某个 profile
ceph osd erasure-code-profile get {profile-name}

# 由 profile 显式创建 CRUSH rule
ceph osd crush rule create-erasure {name} {profile-name}

原文注:正常情况下永远不应该修改 profile;应该创建新 profile,并用它创建新 pool 或为已有 pool 创建新 rule。

EC profile 中与 CRUSH 相关的 key(crush- 前缀):

key 含义 默认值
crush-root 数据放置在哪个 CRUSH 节点下 default
crush-failure-domain 分布 EC 分片所用的 CRUSH bucket 类型 host
crush-osds-per-failure-domain 每个故障域中放置的最大 OSD 数 1(>1 会导致创建 CRUSH MSR rule)。若指定了 crush-num-failure-domains,则本项必须指定
crush-num-failure-domains 要映射的故障域数量。若指定了 crush-osds-per-failure-domain,则本项必须指定。结果是创建 CRUSH MSR rule 文档未明确
crush-device-class 数据放置在哪个 device class 上 无(意味着使用所有设备)
k、m(lrc plugin 还有 l) 决定 EC 分片数量,进而影响生成的 CRUSH rule 见第七章

原文注:创建新 pool 时不必显式创建 rule。如果只指定了 erasure-code profile 而省略 rule 参数,Ceph 会自动创建 CRUSH rule。

CRUSH MSR Rules(Multi-Step Retry)

背景:普通 CRUSH rule 在遇到 out OSD 时不能重试之前的步骤,只能靠 CHOOSELEAF 步骤把 OSD 换到新 host;而 CHOOSELEAF 不支持每个故障域多于一个 OSD。

MSR rule:

  • squid 引入;
  • 通过在遇到 out OSD 时重试所有先前的步骤,支持每个故障域多个 OSD;
  • 触发条件:crush-osds-per-failure-domain > 1 的 EC profile(会自动创建 MSR rule 而非普通 rule);
  • 要求:OSD 和客户端必须支持 CRUSH_MSR feature bit(squid 或更新)。
# 删除未被任何 pool 使用的 rule
ceph osd crush rule rm {rule-name}

2.7 CRUSH map 的编译 / 反编译 / 编辑(crushtool)

手工编辑 CRUSH map 的完整五步(详见第三章):

# 1. 取出(编译格式的)CRUSH map
ceph osd getcrushmap -o {compiled-crushmap-filename}

# 2. 反编译成文本
crushtool -d {compiled-crushmap-filename} -o {decompiled-crushmap-filename}

# 3. 用文本编辑器编辑 devices / buckets / rules

# 4. 重新编译
crushtool -c {decompiled-crushmap-filename} -o {compiled-crushmap-filename}

# 5. 注入集群
ceph osd setcrushmap -i {compiled-crushmap-filename}

2.8 CRUSH map 的六大 Section

# Section 内容
1 tunables map 顶部的 preamble,描述所有不属于 legacy CRUSH 行为的 tunables。这些 tunable 用于修正老 bug、优化、以及多年来为改进 CRUSH 行为所做的其它变更
2 devices 存数据的各个 OSD
3 types bucket types,定义 CRUSH 层级中使用的 bucket 类型
4 buckets 存储位置(row / rack / chassis / host ...)的层级聚合及其权重。定义完 types 后,CRUSH map 为层级中每个节点定义其 type,以及它包含哪些 device 或其他节点
5 rules 定义数据如何跨层级中的设备分布的策略
6 choose_args 与层级关联的、为优化数据放置而调整过的替代权重。可以是一个用于整个集群的 choose_args map,也可以是多个、每个 map 为特定 pool 定制

2.9 Bucket 算法(5 种 bucket type)

原文建议:If you are unsure of which bucket type to use, use the straw2 bucket.

Bucket 类型 原理 适用 / 不适用
uniform 聚合权重完全相同的设备 适用:硬件批量采购后整体上线/下线、设备权重完全一致的场景。优势:CRUSH 可以常数时间把副本映射进 uniform bucket。不适用:设备权重不一致
list 内容以链表聚合,行为由 RUSHP 算法决定。对象要么按适当概率重定位到最新设备,要么留在老设备上 优势:往 bucket 里加 item 时数据迁移最优。不适用:会经常缩容的场景 —— 从链表中间或尾部删除 item 会导致大量不必要的数据移动
tree 用二叉搜索树,行为由 RUSHR 算法决定 比 list 更擅长处理包含大量 item 的 bucket。放置时间降到 O(log n)。适合管理超大规模设备集或嵌套 bucket
straw 所有 item 通过"抽签"相互竞争来获得副本放置 与 list/tree 的分治策略不同(后者要么给某些 item 优先权、要么可以不必考虑整个子树)。提升放置过程性能,但 bucket 内容因增删改权重而变化时,重组织行为可能次优
straw2 改进 straw:邻居权重变化时完全避免任何数据移动。若某 item 权重变化(含加入/移除集群),只会有数据移入或移出该 item,不考虑邻居权重 新创建的 bucket 的默认类型(Hammer 引入)

2.10 Hash 算法

Each bucket uses a hash algorithm. As of Reef, Ceph supports the rjenkins1 algorithm. To select rjenkins1 as the hash algorithm, enter 0 as your hash setting.

即:hash 0 = rjenkins1(这也是反编译 CRUSH map 中 hash 0 # rjenkins1 注释的来源)。

2.11 CRUSH rule 语法:逐字段讲解

普通 CRUSH rule

rule <rulename> {

    id [a unique integer ID]
    type [replicated|erasure]
    step take <bucket-name> [class <device-class>]
    step [choose|chooseleaf] [firstn|indep] <N> type <bucket-type>
    step emit
}

CRUSH MSR rule

rule <rulename> {

    id [a unique integer ID]
    type [msr_indep|msr_firstn]
    step take <bucket-name> [class <device-class>]
    step choosemsr <N> type <bucket-type>
    step emit
}

字段表

字段 Description 类型 必填 默认值 / 合法值 备注
id 标识 rule 的唯一整数;构成 rule mask 的一部分 Integer 是 0
type rule 强制执行的复制策略类型。msr_firstn / msr_indep 是一种不同的 descent 算法,支持在 rule 内重试步骤,从而支持每个故障域多个 OSD String 是 默认 replicated;合法值 replicated、erasure、msr_firstn、msr_indep 构成 rule mask 的一部分
step take <bucket-name> [class <device-class>] 取一个 bucket 名并沿树向下迭代。若指定 device-class,该参数必须匹配集群中 OSD 已分配的某个 class,且只有属于该 class 的设备会被纳入 — 是 示例 step take data 构成 rule 的一部分
step choose firstn {num} type {bucket-type} 从当前 bucket 中选出 num 个给定类型的 bucket。{num} 通常是 pool 的副本数(pool size) — — 示例 step choose firstn 1 type row Prerequisite: 跟在 step take 或 step choose 之后
step chooseleaf firstn {num} type {bucket-type} 选出一组给定类型的 bucket,并从该集合中每个 bucket 的子树里各选一个叶子节点(即一个 OSD)。集合中 bucket 的数量通常是 pool 的副本数 — — 示例 step chooseleaf firstn 0 type row 用 chooseleaf 就不必再单独用一步去选 device。Prerequisite: 跟在 step take 或 step choose 之后
step emit 输出栈顶的当前值并清空栈。通常用在 rule 末尾,但也可以用来在同一个 rule 中从不同的树中进行选择 — — 示例 step emit Prerequisite: 跟在 step choose 之后
step choosemsr {num} type {bucket-type} 选出 num 个 bucket-type 类型的 bucket。msr_firstn 和 msr_indep 必须用 choosemsr,不能用 choose 或 chooseleaf — — 示例 step choosemsr 3 type host Prerequisite: 跟在 step take 之后、step emit 之前

{num} 的三种取值语义(choose / chooseleaf / choosemsr 通用)

{num} 取值 语义
{num} == 0 选 pool-num-replicas 个 bucket(有多少可用就选多少)
pool-num-replicas > {num} > 0 就选那么多(选 num 个)
{num} < 0 选 pool-num-replicas - {num} 个 bucket(注:choosemsr 的条目中未列出负值分支)

firstn vs indep:降级时的替换策略

决定当 CRUSH map 中 item(OSD)被标记 down 时,CRUSH 使用哪种替换策略:

  • 副本池用 firstn;
  • 纠删码池用 indep。

原文给出的例子:假设一个 PG 存放在 OSD 1,2,3,4,5 上,然后 OSD 3 挂了。

模式 行为 最终映射变换
firstn CRUSH 调整计算:选 1 和 2 → 选到 3 时发现 3 down → 重试选 4 和 5 → 最后再选一个新 OSD:OSD 6 1, 2, 3, 4, 5 → 1, 2, 4, 5, 6
indep CRUSH 选到 3,发现 down,重试选 6 1, 2, 3, 4, 5 → 1, 2, 6, 4, 5

为什么 EC 必须用 indep:上面的 firstn 序列会改变映射到 OSD 4、5、6 上的数据(位置发生了平移)。对纠删码池来说,分片位置与数据内容一一对应,位置平移等于数据错乱。indep 模式正是为了避免这个后果 —— 它保证"3 号位"仍然落在 3 号位(由 6 顶替),其余位置不变。

⚠️ 一条重要约束

A single CRUSH rule can be assigned to multiple pools, but a single pool cannot have multiple CRUSH rules.

(一条 CRUSH rule 可以分配给多个 pool;但一个 pool 不能有多条 CRUSH rule。)

默认 rule 的编号

In most cases, there is no need to modify the default rule. When a new pool is created, by default the rule will be set to the value 0 (which indicates the default CRUSH rule, which has the numeric ID 0).

2.12 自定义 rule 示例(含 SSD / HDD 分层)

示例 A:混合 SSD + HDD,SSD 做 primary(同一批 host 内混插)

有些集群为了平衡成本与性能,在同一个副本池里混用 SSD 和 HDD。做法一:把 HDD OSD 的 primary affinity 设为 0,让操作被导向每个 acting set 中的 SSD OSD(见 2.14 节)。做法二:定义一条 CRUSH rule,永远选一个 SSD OSD 作为 primary,其余 OSD 用 HDD。

rule mixed_replicated_rule {
        id 11
        type replicated
        step take default class ssd
        step chooseleaf firstn 1 type host
        step emit
        step take default class hdd
        step chooseleaf firstn 0 type host
        step emit
}

关键代价(原文明确):

For an N-times replicated pool, this rule selects N+1 OSDs in order to guarantee that N copies are on different hosts, because the first SSD OSD might be colocated with any of the N HDD OSDs.

即:对 N 副本池,这条 rule 会选 N+1 个 OSD。因为先选出的那个 SSD OSD 有可能和后面 N 个 HDD OSD 中的某一个在同一台 host 上,为了保证 N 个副本确实分布在不同 host,只能多选一个。→ 存储开销增加。

这正是 step emit "也可以用来在同一个 rule 中从不同的树中进行选择" 的实际用例:先 emit 一次 SSD 的选择结果,再 take 一次 hdd 树、选完再 emit。

示例 B:SSD 与 HDD 分属不同 host(避免 N+1 开销)

rule mixed_replicated_rule_two {
       id 1
       type replicated
       step take ssd_hosts class ssd
       step chooseleaf firstn 1 type host
       step emit
       step take hdd_hosts class hdd
       step chooseleaf firstn -1 type host
       step emit
}

要点:

  • CRUSH root ssd_hosts 和 hdd_hosts 有严格要求:不得包含任何相同的服务器;
  • firstn -1 的含义(按 2.11 的 {num} < 0 规则):选 pool-num-replicas - 1 个 —— 因为已经选了 1 个 SSD primary;
  • 硬件配套建议(原文):因为所有客户端请求都会打到有 SSD 的那批 host,所以建议给 SSD OSD 配更快的 CPU,给 HDD OSD 配更一般 CPU(后者正常情况下只做 recovery);
  • ⚠️ 故障降级:如果 primary SSD OSD 挂了,该 PG 的请求会临时由较慢的 HDD OSD 提供服务,直到数据被复制到替换的 primary SSD OSD 上。

2.13 Tunables(可调参数)

为什么有 tunables

计算数据放置的 CRUSH 算法随时间被不断改进。为了让用户能选择"legacy 还是 optimal 版本的 CRUSH",Ceph 提供了成组的 tunables。

  • 要用更新的 tunables,所有 Ceph 客户端和守护进程都必须支持新的 CRUSH 主版本;
  • 因此 tunables 以引入它的 Ceph 版本命名成 profiles。例:firefly tunables 最早由 Firefly 支持,且不能与更老的客户端(如 Dumpling)一起工作;
  • 集群的 tunables profile 从 legacy 切到 newer/optimal 后,ceph-mon 和 ceph-osd 选项会阻止不支持新 CRUSH 特性的老客户端接入集群。

各 profile 详解

argonaut(legacy)

Argonaut 及更早版本的 legacy CRUSH 行为。前提是"没有太多 OSD 被标记为 out",此时对大多数集群工作良好。

bobtail(CRUSH_TUNABLES2)

解决的问题:

  1. 对于叶子 bucket 中设备数较少的层级,某些 PG 可能映射到少于期望副本数的 OSD,产生 undersized PG。已知发生在 host 节点下 OSD 数很少(1~3 个)的层级。
  2. 对于大型集群,一小部分 PG 可能映射到的 OSD 数少于期望。已知发生在使用了多层层级(如 row、rack、host、osd)时。
  3. 当一个或多个 OSD 被标记 out 时,数据倾向于重分布到邻近的 OSD,而不是跨整个层级分布。

引入的 tunable:

tunable 含义 legacy 值 optimal 值
choose_local_tries 本地重试次数 2 0
choose_local_fallback_tries 5 0
choose_total_tries 选择某个 item 的总尝试次数 19 50(原文:subsequent testing indicates that a value of 50 is more appropriate for typical clusters。对于极大规模集群可能需要更大的值)
chooseleaf_descend_once 递归 chooseleaf 尝试是否重试(还是只试一次、让原始放置去重试) 0 1

迁移影响:从 argonaut 迁到 bobtail 会触发中等量的数据移动。对已存有大量数据的集群要谨慎。

firefly(CRUSH_TUNABLES3)

chooseleaf_vary_r

修复 chooseleaf CRUSH step 的一个问题:当很大比例的 OSD 被标记 out 时,会产生 OSD 数过少的 PG 映射。

tunable 含义 legacy 值 optimal 值
chooseleaf_vary_r 递归 chooseleaf 尝试是否以非零的 r 值开始(该值由父级已尝试的次数决定) 0(此值下 CRUSH 有时无法找到映射) 1(在计算开销与正确性上最优)

迁移影响:对存有大量数据的已有集群,把这个 tunable 从 0 改成 1 会触发大量数据迁移;取 4 或 5 既能让 CRUSH 找到合法映射,又能减少数据移动量。

straw_calc_version

问题:straw 算法 bucket 在 CRUSH map 中计算并存储的内部权重有问题 —— 当存在 CRUSH 权重为 0 的 bucket、或权重互不相同且唯一的混合 bucket 时,CRUSH 会不正确地分布数据(即不按权重成比例)。

tunable 含义
straw_calc_version 0 = 保留旧的、有问题的内部权重计算;1 = 修复该问题

迁移影响:改成 1 之后再调整 straw bucket(增删改 item 的权重,或用 reweight-all 命令),如果集群正好命中了上述有问题的条件,会触发小到中等量的数据移动。

这个 tunable 的特别之处:对客户端侧的内核版本要求完全没有影响。

hammer(CRUSH_V4)
  • 仅切换 profile 本身不会改变已有 CRUSH map 的映射;
  • 新增一种 bucket 算法:straw2。它修复了原始 straw 的若干限制。具体来说:旧的 straw bucket 在权重被调整时,会改变一些本不应该改变的映射;而 straw2 达成了最初的目标 —— 只向/从权重发生变化的那个 bucket item 改变映射;
  • straw2 是所有新建 bucket 的默认类型。

迁移影响:把 bucket 类型从 straw 改成 straw2 会触发少量数据移动,移动量取决于 bucket item 的权重差异有多大 —— 权重全一样则无数据移动,权重差异越大移动越多。

jewel(CRUSH_TUNABLES5)

整体改善 CRUSH 行为:当一个 OSD 被标记 out 时,显著减少发生变化的映射数量 → 显著减少数据移动量。

tunable 含义 legacy 值 new 值
chooseleaf_stable 递归 chooseleaf 尝试是否为内层循环使用更好的值,从而大幅减少 OSD 被标记 out 时的映射变更数 0 1(使用新方法)

迁移影响:在已有集群上改这个值会导致极大量的数据移动,因为几乎每个 PG 映射都可能改变。

各 tunables 所需的客户端版本

Tunables Ceph 版本 Linux 内核版本(CephFS / RBD 内核客户端)
CRUSH_TUNABLES2 v0.55 及以后,含 Bobtail(v0.56.x) v3.9 及以后
CRUSH_TUNABLES3 v0.78(Firefly)及以后 v3.15 及以后
CRUSH_V4 v0.94(Hammer)及以后 v4.1 及以后
CRUSH_TUNABLES5 v10.0.2(Jewel)及以后 v4.5 及以后

"Non-optimal tunables" 告警

v0.74 及以后,若当前任何 CRUSH tunable 处于非最优值(即不等于 default profile 中的 optimal 值),Ceph 会抛出健康检查:

HEALTH_WARN crush map has non-optimal tunables

两种消除告警的方式:

方式 1:把 tunables 调整成最优(推荐)

ceph osd crush tunables optimal

会触发一些数据移动(可能高达 10%)。一般优于方式 2,但在数据移动会影响性能的场景(如生产集群)要特别小心。

可能出现的三类问题(此时应该回退):(a) 新值产生的负载集群扛不住;(b) 新值让集群运行慢到不可接受;(c) 客户端兼容性问题(用老内核 CephFS/RBD 客户端,或 pre-Bobtail 的 librados 客户端时)。

回退:

ceph osd crush tunables legacy

方式 2:不改 CRUSH,仅关闭告警

在 ceph.conf 的 [mon] 段加入:

mon_warn_on_legacy_crush_tunables = false

生效方式:重启 mon,或在 mon 运行时执行:

ceph tell mon.\* config set mon_warn_on_legacy_crush_tunables false

调整 tunables 的注意事项(原文三条)

  1. 调整 CRUSH tunables 的值会导致一个或多个 PG 从某个存储节点迁移到另一个。若集群已存有大量数据,准备好迎接显著的数据移动。
  2. 当 ceph-osd 和 ceph-mon 守护进程拿到更新后的 map,它们会立即开始拒绝不支持新特性的客户端的新连接。但已连接的客户端实际上被" grandfathered in( grandfathered,既往不咎)",其中任何不支持新特性的客户端会发生故障(malfunction)。
  3. 若 CRUSH tunables 被设为 newer(非 legacy)值后又回退到 legacy 值,ceph-osd 守护进程不再被要求支持与 newer 值关联的任何新 CRUSH 特性。但是,OSD peering 过程需要检查和理解旧的 map。因此,如果集群曾经使用过非 legacy 的 CRUSH 值,就不要运行老版本的 ceph-osd 守护进程 —— 即使最新的 map 已经回退为 legacy 默认值。

可用的 profile(Octopus 起)

profile 含义
legacy argonaut 及更早的 legacy 行为
argonaut argonaut release 支持的 legacy 值
bobtail bobtail release 支持的值
firefly firefly release 支持的值
hammer hammer release 支持的值
jewel jewel release 支持的值
optimal 当前 Ceph 版本的最佳值
default 全新安装集群的默认值。这些值取决于当前 Ceph 版本,是硬编码的,通常是最优值与 legacy 值的混合。它们通常对应于上一个 LTS 版本的 optimal profile,或者"大多数用户被预期已升级到最新客户端"的最近版本的 optimal profile

应用 profile:

ceph osd crush tunables {PROFILE}

⚠️ 该操作可能触发大量数据移动。变更前请查阅 release notes 和文档,并考虑限制 recovery / backfill 参数以限制由此产生的 backfill。

2.14 Primary OSD 选择的调优

背景

客户端读写时,首先联系该 PG acting set 中的 primary OSD。默认 acting set 的第一个 OSD 就是 primary OSD(也称 "lead OSD")。例:[2, 3, 4] 中 osd.2 排第一,因此是 primary。

如果某个 OSD 明显不适合当 lead(例如盘慢、控制器慢),为了避免性能瓶颈(尤其是读操作)并最大化硬件利用率,有两种手段:

  1. 调整 primary affinity 值;
  2. 编制 CRUSH rule,让它选出更适合当 lead 的 OSD。

对副本池:这种调优特别有用,因为默认读操作由每个 PG 的 primary OSD 提供。 对纠删码池:读速度可以通过启用 fast read 来提升(见第六章 fast_read)。

Primary Affinity

  • primary affinity 是 OSD 的一个属性,决定该 OSD 在给定 acting set 中被选为 primary(lead OSD)的可能性;
  • 取值:0 到 1(含端点)之间的任意实数;
  • 默认每个 Ceph OSD 的 primary affinity = 1,此时所有 OSD 当 primary 的可能性相等;
  • 0 = 该 OSD 不可被用作 primary;1 = 最大可能被用作 primary;中间值 ≈ 大致的被选概率。
ceph osd primary-affinity <osd-id> <weight>

原文给出的真实案例:集群里混插了 1.9 TB 和 3.84 TB 两种 SATA SSD。后者平均被分配到的 PG 数是前者两倍,因此服务的读写也是两倍 —— 更忙。把 primary affinity 粗略地设成与 OSD 大小成反比,不是 100% 最优,但通过更均匀地利用 SATA 接口带宽和 CPU 周期, readily 获得了 15% 的整体读吞吐提升。原文强调这不是思想实验 —— 这 15% 是在一个真实 Ceph 集群上取得的。

另一个说明:CRUSH 选 lead OSD 的过程不是"由相对 affinity 值决定的简单概率"的函数。但即使只用 desirable primary affinity 值的一阶近似,也能取得可测量的效果。

引自:CRUSH Maps、Manually editing the CRUSH Map


三、CRUSH map 编辑实战

⚠️ 原文开宗明义: Manually editing the CRUSH map is an advanced administrator operation. For the majority of installations, CRUSH changes can be implemented via the Ceph CLI and do not require manual CRUSH map edits. If you have identified a use case where manual edits are necessary with a recent Ceph release, consider contacting the Ceph developers at dev@ceph.io so that future versions of Ceph do not have this problem.

3.1 标准五步流程

步骤 命令 说明
1. Get ceph osd getcrushmap -o {compiled-crushmap-filename} Ceph 输出(-o)一个编译格式的 CRUSH map
2. Decompile crushtool -d {compiled-crushmap-filename} -o {decompiled-crushmap-filename} 因为取出的 map 是编译过的,编辑前必须先反编译
3. Edit 用文本编辑器编辑 Devices / Buckets / Rules 中的至少一个
4. Recompile crushtool -c {decompiled-crushmap-filename} -o {compiled-crushmap-filename}
5. Set ceph osd setcrushmap -i {compiled-crushmap-filename} Ceph 从指定文件加载(-i)编译后的 CRUSH map

完整示例:

# 1. 取
ceph osd getcrushmap -o /tmp/crush-compiled

# 2. 反编译
crushtool -d /tmp/crush-compiled -o /tmp/crush.txt

# 3. 编辑
vi /tmp/crush.txt

# 4. 编译
crushtool -c /tmp/crush.txt -o /tmp/crush-new

# 5. 注入
ceph osd setcrushmap -i /tmp/crush-new

3.2 场景一:Devices 段

Device 定义通常每个 OSD 守护进程一条,由 id(非负整数)和 name(通常 osd.N)标识。

查看 device 列表:

ceph device ls

输出格式:

device {num} {osd.name} [class {class}]

示例输出:

device 0 osd.0 class ssd
device 1 osd.1 class hdd
device 2 osd.2
device 3 osd.3

注意 osd.2 / osd.3 没有 class —— device class 是可选的。

device 与守护进程的对应关系(原文):大多数情况下每个 device 对应一个 ceph-osd 守护进程。这个守护进程可能映射到:

  • 单个存储设备;
  • 一对设备(例如一个放数据、一个放 journal 或 metadata);
  • 某些情况下是一个小型 RAID 设备,或更大存储设备的一个分区。

手工编辑时的 device 语法(反编译后形如):

device {num} {osd.name} [class {class}]

3.3 场景二:Types 段(增加/修改 bucket 类型)

在 bucket 类型列表下新增一行:type + 唯一数字 ID + bucket 名。

约定:有且仅有一个叶子 bucket 类型,且它是 type 0;但叶子 bucket 的名字可以随便起(如 osd、disk、drive、storage)。

# types
type {num} {bucket-name}

官方给出的完整默认声明示例:

# types
type 0 osd
type 1 host
type 2 chassis
type 3 rack
type 4 row
type 5 pdu
type 6 pod
type 7 room
type 8 datacenter
type 9 zone
type 10 region
type 11 root

注意:这里的数字 ID 分配(osd=0, host=1, chassis=2, rack=3, row=4, pdu=5, pod=6, room=7, datacenter=8, zone=9, region=10, root=11)与 2.3.2 节 crush-map.rst 中列出的顺序(osd, host, chassis, rack, row, pdu, pod, room, datacenter, zone, region, root)是一致的。

3.4 场景三:Buckets 段(层级编辑)

声明一个 bucket 实例的六要素

  1. 指定其 type;
  2. 给它一个唯一名字(alphanumeric string);
  3. 给它一个唯一 ID,用负整数表示(此项可选);
  4. 给它一个相对权重(相对于 bucket 中 item 的总容量/能力);
  5. 给它一个 bucket 算法(通常 straw2);
  6. 指定 bucket 算法的 hash(通常 0,即 rjenkins1)。

语法

[bucket-type] [bucket-name] {
    id [a unique negative numeric ID]
    weight [the relative capacity/capability of the item(s)]
    alg [the bucket type: uniform | list | tree | straw | straw2 ]
    hash [the hash type: 0 by default]
    item [item-name] weight [weight]
}

官方示例(对应 2.3.2 的 host1/host2/rack1 图)

host node1 {
    id -1
    alg straw2
    hash 0
    item osd.0 weight 1.00
    item osd.1 weight 1.00
}

host node2 {
    id -2
    alg straw2
    hash 0
    item osd.2 weight 1.00
    item osd.3 weight 1.00
}

rack rack1 {
    id -3
    alg straw2
    hash 0
    item node1 weight 2.00
    item node2 weight 2.00
}

注意:rack bucket 不含任何 OSD。它包含的是更低层的 host bucket,并且在 item 条目中计入它们权重的和(2.00 = node1 的 1.00 + 1.00)。

一条重要规则:叶子不需要声明为 bucket 实例

Because leaf nodes reflect storage devices that have already been declared under the #devices list at the beginning of the CRUSH map, there is no need to declare them as bucket instances.

层级中第二低的 bucket 类型通常用来聚合 device —— 通常就是装这些盘的服务器。

高密度环境的故障域提示

原文特意提示:高密度环境中,一个 chassis 里有多个 host/node 很常见(blade 或 twin)。必须预见到 chassis 故障的后果:例如更换 chassis 时,该 chassis 下所有 host 及其 OSD 都会 down 而不可用。因此必须避免把多个副本或数据分片放在同一个 chassis 内 —— 此时 chassis 就是一个 failure domain。

命名建议(原文)

  • 让 CRUSH map 适配你偏好的硬件命名规范;
  • 用能清楚反映物理硬件的 bucket 名。清晰的命名在 OSD 或硬件故障、管理员需要接触物理硬件时,能显著降低管理集群和排查问题的难度。

3.5 场景四:Rules 段(自定义 rule)

见 2.11(语法逐字段)和 2.12(SSD/HDD 分层示例)。这里补充几条编辑时的注意:

  • 默认 CRUSH map 每个 pool 一条 rule;大集群可能有很多 pool,每个 pool 可能有自己的非默认 CRUSH rule;
  • 大多数情况下不需要修改默认 rule。新建 pool 时 rule 默认为 0;
  • ⚠️ 一条 rule 可以给多个 pool 用;一个 pool 不能有多条 rule。

3.6 场景五:调整权重(bucket item 权重)

见 2.3.3(权重惯例,1 TB = 1.00)与 2.6(ceph osd crush reweight)。手工编辑时直接在 bucket 声明里改 item ... weight ...,并同步更新上层 bucket 的 item weight(其为下层之和)。

💡 补充:手工编辑 bucket 时如果忘记同步上层 weight,会造成层级权重不一致。建议优先用 CLI 的 ceph osd crush reweight(它会自动处理),只在 CLI 无法表达的场景才手工编辑,并在注入前用 crushtool --compare(3.7 节)验证。

3.7 场景六:从 legacy SSD rule 迁移到 device classes(reclassify)

背景

Luminous 引入 device class 之前,要写针对特定设备类型(如 SSD)的 rule,必须手工编辑 CRUSH map 并为每种设备类型维护一套平行的层级。device class 提供了更透明的方式达成同样目的。

但是:如果把一个已手工定制过 per-device map 的集群迁移到新的 device class rule,系统中的所有数据都会被重新洗牌(reshuffled)。

crushtool 提供了几条命令,可以转换 legacy rule 和层级,从而开始使用新的 device class rule,同时避免数据移动。

三类转换

1. --reclassify-root <root-name> <device-class>
  • 检查层级中 root-name 下的所有内容;
  • 把所有引用该 root、形如 take <root-name> 的 rule 改写成 take <root-name> class <device-class>;
  • 重新编号 bucket,使得旧 ID 被用于指定 class 的 "shadow tree",从而不发生任何数据移动。

示例:

# 原 rule
rule replicated_rule {
   id 0
   type replicated
   step take default
   step chooseleaf firstn 0 type rack
   step emit
}

把 root default reclassify 为 class hdd 后:

rule replicated_rule {
   id 0
   type replicated
   step take default class hdd
   step chooseleaf firstn 0 type rack
   step emit
}
2. --set-subtree-class <bucket-name> <device-class>
  • 把以 bucket-name 为根的子树中的每个 device 都标记上指定的 device class;
  • 通常与 --reclassify-root 配合使用,以确保该 root 下所有 device 都被标上正确的 class;
  • 但:某些情况下,其中一些 device 正确地属于另一个 class,不能被重新标记。此时应省略 --set-subtree-class;
  • 代价:重映射过程不会是完美的(原 rule 会影响多个 class 的设备,而调整后的 rule 只映射到指定 class 的设备)。但当 outlier 设备不多时,产生的数据移动量通常在可容忍范围内。
3. --reclassify-bucket <match-pattern> <device-class> <default-parent>

用于把一套平行的、按类型区分的层级合并进正常层级。

典型的老式 map(原文示例):

host node1 {
   id -2           # do not change unnecessarily
   # weight 109.152
   alg straw2
   hash 0  # rjenkins1
   item osd.0 weight 9.096
   item osd.1 weight 9.096
   item osd.2 weight 9.096
   item osd.3 weight 9.096
   item osd.4 weight 9.096
   item osd.5 weight 9.096
   ...
}

host node1-ssd {
   id -10          # do not change unnecessarily
   # weight 2.000
   alg straw2
   hash 0  # rjenkins1
   item osd.80 weight 2.000
...
}

root default {
   id -1           # do not change unnecessarily
   alg straw2
   hash 0  # rjenkins1
   item node1 weight 110.967
   ...
}

root ssd {
   id -18          # do not change unnecessarily
   # weight 16.000
   alg straw2
   hash 0  # rjenkins1
   item node1-ssd weight 2.000
...
}

pattern 语法:%suffix 或 prefix%(% 是通配符)。上例用 %-ssd。

行为:

  • 对每个匹配的 bucket,% 通配符对应的名字剩余部分指定 base bucket;
  • 匹配 bucket 中的所有 device 都被打上指定 device class,然后被移到 base bucket;
  • 若 base bucket 不存在(例如 node12-ssd 存在但 node12 不存在),则创建它,并挂到指定的 default parent bucket 下;
  • 每种情况下都会小心地为新的 shadow bucket 保留旧的 bucket ID,以避免数据移动;
  • 任何带 take 步骤引用了旧 bucket 的 rule 都会相应调整。
4. --reclassify-bucket <bucket-name> <device-class> <base-bucket>(无通配符形式)

同一个命令不带通配符时用于映射单个 bucket。例如上例中要把 ssd bucket 映射到 default bucket:

ceph osd getcrushmap -o original
crushtool -i original --reclassify \
  --set-subtree-class default hdd \
  --reclassify-root default hdd \
  --reclassify-bucket %-ssd ssd default \
  --reclassify-bucket ssd ssd default \
  -o adjusted

--compare 校验

--compare 标志用于确认上述转换是否正确:它用大量输入样本测试 CRUSH map,并检查输出是否为预期结果。控制这些输入的选项与 --test 命令相同。

crushtool -i original --compare adjusted

输出示例:

rule 0 had 0/10240 mismatched mappings (0)
rule 1 had 0/10240 mismatched mappings (0)
maps appear equivalent

若发现差异,括号中会报告被重新映射的输入的比例。

确认无误后注入集群:

ceph osd setcrushmap -i adjusted

3.8 场景七:手工调整 CRUSH tunables

前提:已确认所有客户端都在运行较新的代码。

# 1. 取出最新 CRUSH map
ceph osd getcrushmap -o /tmp/crush

# 2. 调整 tunables(下面这组值是官方测试中"对大集群和小集群都表现最佳"的值)
#    注意:需要 --enable-unsafe-tunables,且必须 EXTREME CARE
crushtool -i /tmp/crush --set-choose-local-tries 0 --set-choose-local-fallback-tries 0 --set-choose-total-tries 50 -o /tmp/crush.new

# 3. 重新注入
ceph osd setcrushmap -i /tmp/crush.new

⚠️ 原文明确要求:该过程必须指定 --enable-unsafe-tunables 标志,Use this option with extreme care。

恢复 legacy 值:

crushtool -i /tmp/crush --set-choose-local-tries 2 --set-choose-local-fallback-tries 5 --set-choose-total-tries 19 --set-chooseleaf-descend-once 0 --set-chooseleaf-vary-r 0 -o /tmp/crush.legacy

同样需要 --enable-unsafe-tunables。⚠️ 回退到 legacy 值后运行老版本 ceph-osd 守护进程要小心,因为 feature bit 的执行并不完美(not perfectly enforced)。

💡 补充:注意 3.8 中的 legacy 值(choose_total_tries=19、choose_local_tries=2、choose_local_fallback_tries=5、chooseleaf_descend_once=0、chooseleaf_vary_r=0)与 2.13 节表格中的 legacy 值完全一致,可交叉验证。

引自:Manually editing the CRUSH Map


四、Placement Group(归置组)

4.1 PG 的三个基础事实

  1. PG 是 pool 的子集,负责把对象成组地放进 OSD;
  2. Ceph 以 PG 粒度管理内部数据,这比按单个 RADOS object 管理扩展性更好;
  3. PG 数量更多的集群更均衡(原文举例:每 OSD 150 个 PG 优于每 OSD 更少 PG 的同等集群)。

4.2 pg_num 与 pgp_num

概念 含义
pg_num pool 中 PG 的总数
pgp_num 用于计算数据放置的有效 PG 数量("the effective number of PGs to use when calculating data placement")

Nautilus 起的关键行为变化:

Since the Nautilus release, Ceph automatically and incrementally steps pgp_num for a pool whenever pg_num is changed, either by the PG autoscaler or manually. Admins generally do not need to touch pgp_num directly, but can monitor progress with watch ceph osd pool ls detail.

  • 改 pg_num 时,pgp_num 会被缓慢地步进(stepped slowly),这样分裂或合并 PG 的开销会随时间摊销,从而最小化性能影响;
  • 增大 pg_num 会分裂该 pool 中的一些 PG,但数据不会通过 backfill 迁移到新 PG,直到该 pool 的 pgp_num 被增大;
  • pgp_num 应该等于 pg_num;
  • 手动设置 pgp_num 是可能的,但很少合适("It is possible but rarely appropriate to manually set the pgp_num parameter");
  • 当 pg_autoscaler 未被使用时(Nautilus 起),pgp_num 会自动步进以匹配 pg_num。这个过程表现为一段时间的 PG remapping 和 backfill,这是预期行为。
ceph osd pool set {pool-name} pg_num {pg_num}
ceph osd pool set {pool-name} pgp_num {pgp_num}   # 很少需要

4.3 自动扩缩容(pg-autoscaling)

是什么

PG 是 Ceph 分发数据的内部实现细节。Autoscaling 提供了一种管理 PG(尤其是各 pool 中 PG 数量)的方式。启用后,集群会依据观测到和预期的 pool 利用率,对每个 pool 的 PG 数(pgp_num)给出建议或自动调整。

三种模式

pg_autoscale_mode 含义
off 关闭该 pool 的自动扩缩容。由管理员为该 pool 选择适当的 pg_num
on 启用对该 pool PG 数量的自动调整
warn 当 PG 数量需要调整时抛出健康检查(health checks)
# 设置已有 pool 的模式
ceph osd pool set <pool-name> pg_autoscale_mode <mode>

# 例
ceph osd pool set foo pg_autoscale_mode on

central config 选项:还有一个中心配置 pg_autoscale_mode 选项,控制集群初始搭建之后新创建 pool 的 autoscale 模式:

ceph config set global osd_pool_default_pg_autoscale_mode <mode>

全局开关 noautoscale

ceph osd pool set noautoscale    # 对所有 pool 关闭 autoscaler
ceph osd pool unset noautoscale  # 恢复
ceph osd pool get noautoscale    # 查看当前值

默认 noautoscale flag 是 off(即 autoscaler 全局可用)。

查看扩缩容建议

ceph osd pool autoscale-status

输出示例:

POOL    SIZE  TARGET SIZE  RATE  RAW CAPACITY   RATIO  TARGET RATIO  EFFECTIVE RATIO BIAS PG_NUM  NEW PG_NUM  AUTOSCALE BULK
a     12900M                3.0        82431M  0.4695                                          8         128  warn      True
c         0                 3.0        82431M  0.0000        0.2000           0.9884  1.0      1          64  warn      True
b         0        953.6M   3.0        82431M  0.0347                                          8              warn      False

各列含义(逐列对照原文):

列 含义
POOL pool 名
SIZE 该 pool 中已存的数据量
TARGET SIZE (若存在)管理员指定的预期存入该 pool 的数据量。系统计算时取 SIZE 与 TARGET SIZE 中较大者
RATE 该 pool 的空间放大因子,表示给定用户数据量会消耗多少原始存储容量。例:三副本 pool 显示 3.0;k=4 m=2 的纠删码 pool 为 1.5
RAW CAPACITY 该 pool 可用 OSD 上的原始存储总容量。注意多数情况下该容量被多个 pool 共享
RATIO 该 pool 消耗的存储 / 总原始存储容量。即 (SIZE * RATE) / RAW CAPACITY,可理解为满度百分比
TARGET RATIO (若存在)该 pool 的预期存储相对于其它所有设置了 target ratio 的 pool 的预期存储之比。若 target_size_bytes 与 target_size_ratio 都指定了,target_size_ratio 优先。⚠️ 当 BIAS 值不等于 1 时(尤其 CephFS metadata 和 RGW index pool),最好不要动 target ratio,因为同时调整两者会因 double-dipping 导致不恰当的 pg_num
EFFECTIVE RATIO 对 target ratio 做两处调整后的结果:① 减去预期会被设置了 target size 的 pool 使用的容量;② 在设置了 target ratio 的 pool 之间归一化 target ratio,使它们 collectively 对准集群容量(例:4 个 target_ratio=1.0 的 pool,每个 effective ratio = 0.25)。系统使用 target ratio 与 effective ratio 中较大者
BIAS 作为乘数手工调整 pool 的 PG,依据是"该 pool 预期应该有多少 PG"的先验信息。对主要把数据存在 omap 而非 RADOS object 中的 pool(尤其 RGW index 和 CephFS / RBD EC metadata pool)很重要。当 pool 设了非 1.0 的 bias 时,建议不要再设 target ratio
PG_NUM 该 pool 当前的 PG 数;或若正在变更,则为目标值
NEW PG_NUM (若存在)系统建议的 pg_num。始终是 2 的幂,且仅在建议值与当前值相差超过扩缩容阈值时出现
AUTOSCALE 该 pool 的 pg_autoscale_mode(on / off / warn)
BULK 该 pool 是否为 bulk(True / False)

扩缩容阈值(threshold)

  • 默认阈值是配置因子 3;
  • 缩容时只用这个配置因子;
  • 扩容时阈值会动态降低:
    • 建议 NEW PG_NUM 为 512 或 1024 → 阈值设为 1.0
    • 建议 NEW PG_NUM 为 2048 → 阈值设为 2.0
ceph osd pool set threshold 2.0   # 修改该倍数(示例改为 2)
ceph osd pool get threshold       # 查看当前值

每 OSD 目标 PG 数

ceph config set global mon_target_pg_per_osd 100
参数 默认值 官方建议
mon_target_pg_per_osd 100 除极小型部署外,推荐 200。超过 500 可能导致过多的 peering 流量和 RAM 占用

autoscaler 的按子树分析

  • autoscaler 按 per-subtree 分析 pool 并调整;
  • 因为每个 pool 可能映射到不同的 CRUSH rule,而每条 rule 又可能把数据分布到不同且可能重叠的设备集上,Ceph 会独立地考虑 CRUSH 层级中每个子树的利用率;
  • 例:一个映射到 ssd class OSD 的 pool 和一个映射到 hdd class OSD 的 pool,会各自根据这两种设备类型的 OSD 数量算出各自的 PG 数。

⚠️ 跨 root 的 pool(spanning pool):

  • 若一个 pool 使用了两个及以上 CRUSH root 下的 OSD(例如同时含 ssd 和 hdd 设备的 shadow tree),autoscaler 会在 manager log 中向用户发出 warning,指出 pool 名和互相重叠的 root 集合;
  • autoscaler 不会扩缩任何 root 重叠的 pool(该条件会导致扩缩过程出问题);
  • 建议把每个 pool 约束到只属于一个 root(即一个设备 OSD class),以消除告警并确保扩缩成功。

⚠️ 排障提示(原文):如果 ceph osd pool autoscale-status 完全没有输出,很可能是至少有一个 pool 跨了多个 CRUSH root。典型场景:新部署时在 default CRUSH root 上自动创建了 .mgr pool,之后创建的 pool 又用了约束到特定 shadow CRUSH tree 的 rule。例如:RBD metadata pool 约束到 deviceclass = ssd、RBD data pool 约束到 deviceclass = hdd,就会遇到这个问题。

修法:把跨 root 的 pool 也约束到单一 device class。上例中大概率存在一条 replicated-ssd CRUSH rule,可以把 .mgr pool 约束到 ssd 设备:

ceph osd pool set .mgr crush_rule replicated-ssd

这会产生少量 backfill,但通常不具破坏性且很快完成。

bulk flag

pool 是否 bulk autoscaler 的起始行为
是(bulk) 以完整配额的 PG 数启动,之后仅当该 pool 的用量比率不均时才缩减 PG 数
否 以最小 PG 数启动,之后仅当该 pool 用量增加时才创建更多 PG
  • bulk pool 的语义:预期会很大,因此初始就应有大量 PG,以免性能受损;
  • 非 bulk pool:预期很小(例如 .mgr pool 或 meta pool);
  • ⚠️ 原文警告:This flag should be used with care, as it may not have the results one would think.(该 flag 的结果可能与直觉不符,谨慎使用)
# 创建时打上 bulk
ceph osd pool create <pool-name> --bulk

# 设置/取消已有 pool 的 bulk
ceph osd pool set <pool-name> bulk <true/false/1/0>

# 查看
ceph osd pool get <pool-name> bulk

指定 pool 的预期大小

动机:集群或 pool 刚创建时只消耗总容量的很小一部分,系统据此认为只需要很少 PG。但管理员往往知道哪些 pool 长期会吃掉大部分容量。把这一信息告诉 Ceph,就能从一开始使用更合适的 PG 数,避免后续改 pg_num 及随之而来的数据搬迁开销;同时由于 PG 会被放置到所有可用 OSD 上,性能和数据均匀性也受益。

两种指定方式:

# 方式 1:绝对大小(字节)
ceph osd pool set mypool target_size_bytes 100T

# 方式 2:相对于其他设置了 target_size_ratio 的 pool 的权重
ceph osd pool set mypool target_size_ratio 1.0
场景 结果
mypool 是集群中唯一 pool,ratio=1.0 预期使用 100% 集群总容量
集群中还有第二个 pool 也设了 ratio=1.0 两个 pool 各预期使用 50%

创建时指定:

ceph osd pool create ... --target-size-bytes <bytes>
ceph osd pool create ... --target-size-ratio <ratio>

相关健康检查:

条件 健康检查
指定的 target-size 值不可能成立(例如容量大于集群总量) POOL_TARGET_SIZE_BYTES_OVERCOMMITTED
同时指定了 target_size_ratio 和 target_size_bytes POOL_HAS_TARGET_SIZE_BYTES_AND_RATIO(此时 target_size_bytes 会被忽略,系统计算使用 target_size_ratio)

官方建议:大多数情况下不要在同一个 pool 上同时设置非 1.0 的 bias 和 target ratio。metadata / omap 密集型 pool 用更高的 bias;RADOS 数据密集型 pool 用 target ratio。

限定 PG 数量的上下界

# 最小值:Ceph 不会(也不会建议你)把 pg_num 降到该值以下。
#   作用:即使 pool 基本为空,也保证客户端 I/O 的并行度下界。
ceph osd pool set <pool-name> pg_num_min <num>

# 最大值:Ceph 不会(也不会建议你)把 pg_num 增到该值以上。
ceph osd pool set <pool-name> pg_num_max <num>

创建时指定:

ceph osd pool create ... --pg-num-min <num>
ceph osd pool create ... --pg-num-max <num>

预选 pg_num

ceph osd pool create {pool-name} [pg_num]
  • 若不指定 pg_num,集群会用 PG autoscaler 依据数据量自动配置;
  • ⚠️ 但"是否在创建时指定 pg_num"并不影响该参数之后是否会被自动调优 —— 自动调优由 ceph osd pool set {pool-name} pg_autoscale_mode (on|off|warn) 单独控制。

关于每 OSD 的 PG 副本数目标(preselection 段落原文):

条件 每 OSD 的 PG 副本数
没有 balancer 时,对大多数集群**建议(但非默认)**的目标 200
开启 balancer 且用默认值 初始结果大致 50 ~ 70

这是 ceph df 输出中 PGS 列报告的数值,注意它不是"集群总 PG 数 / OSD 数"。

autoscaler 试图满足两个条件:

  1. 每 OSD 的 PG 副本数应与该 pool 中的数据量成比例;
  2. 默认情况下每个 pool 应有 50~100 个 PG(这已把每个 PG 副本跨 OSD 的复制开销或纠删码 fan-out 考虑在内)。

4.4 影响 pg_num 取舍的三大因素

(1)Data durability(数据持久性)

PG 数对 durability 几乎无影响:

In such a cluster, the number of PGs has almost no effect on data durability. Whether there are 128 PGs per OSD or 8192 PGs per OSD, the recovery will be no slower or faster.

但 OSD 数量会显著影响恢复速度(原文用 10 OSD / 20 OSD / 40 OSD / 200 OSD 的递进例子说明):

集群规模 每 OSD 承载的 PG 数 恢复特征
10 OSD,512 PG,三副本 (512 * 3) / 10 ≈ 150 一个 OSD 故障 → 约 150 个 PG 触发恢复,均摊到 9 个剩余 OSD
20 OSD ≈ 75 19 个 OSD 参与,每个只需复制 50 GB(原 100 GB)→ 若网络原本是瓶颈,恢复快一倍
40 OSD ≈ 38 恢复更快(除非遇到别的瓶颈)
200 OSD ≈ 7 ⚠️ 恢复只能在最多 ≈ 21 = (7 × 3) 个相关 OSD 间进行,恢复反而比 40 OSD 时更慢 → 此时应该增加 PG 数

级联故障导致数据丢失的推演(原文完整场景):

  1. OSD 故障,它承载的所有对象副本丢失。该 PG 中每个对象的副本数从 3 骤降到 2;
  2. Ceph 启动恢复,选一个新 OSD 重建每对象的第三份副本;
  3. 同一 PG 中的另一个 OSD 在新 OSD 完全填满前又故障 → 部分对象只剩一份;
  4. Ceph 再选一个 OSD 继续复制;
  5. 同一 PG 中第三个 OSD 在恢复完成前又故障。如果这个 OSD 恰好持有某对象仅存的那份副本 → 该对象永久丢失。

这正是 size=2 的副本池和 m=1 的 EC 池"有风险、通常不推荐"的一个微妙原因。

概率量化(原文,假设恢复期间丢失一个 OSD 的概率为 0.0001%):

集群 坏 1 个 OSD 后只剩一份副本的 PG 数 坏 3 个 OSD 时数据丢失的概率
10 OSD ≈ 17(≈ 150/9) ≈ 17 × 10 × 0.0001%
20 OSD ≈ 4(75/19) ≈ 4 × 20 × 0.0001%

结论(原文):

In summary, the greater the number of OSDs, the faster the recovery and the lower the risk of permanently losing a PG due to cascading failures. As far as data durability is concerned, in a cluster with fewer than 50 OSDs, it doesn't much matter whether there are 512 or 4096 PGs.

补充注:新加入集群的 OSD 可能需要很长时间才被填满分配给它的 PG。但这不会导致对象降级或影响数据持久性 —— 因为 Ceph 会先往新 PG 里填数据,再从旧 PG 里删除。

(2)Object distribution within a pool(池内对象分布)

  • 理想条件下对象在 PG 间均匀分布;
  • 但 CRUSH 只计算每个对象的 PG,它并不知道该 PG 关联的每个 OSD 上已存了多少数据 → PG 数与 OSD 数的比率对数据分布有显著影响;
  • 极端例子:三副本池、10 个 OSD、只有 1 个 PG → 只会用到 3 个 OSD(CRUSH 别无选择)。PG 越多,对象越可能均匀分布;
  • 经验法则(原文):只要 PG 数比 OSD 数多一到两个数量级,分布就可能是均匀的。举例:3 OSD → 256 PG;10 OSD → 512 PG;10 OSD → 1024 PG;
  • 分布不均的其它来源:CRUSH 不考虑 RADOS object 的大小。少数超大对象就会造成倾斜。原文例子:100 万个 4 KB 对象共 4 GB,均匀分布在 10 OSD 的 1024 个 PG 上 → 每个 OSD 400 MB。此时再往里加一个 400 MB 的对象,承载它的那个 PG 所对应的 3 个 OSD 会变成 400+400=800 MB,而其余 7 个 OSD 仍是 400 MB。

(3)Memory, CPU and network usage(资源占用)

  • 集群中每个 PG 都会给 OSD 和 Monitor 带来内存、网络和 CPU 开销;
  • 这些需求必须随时被满足,并且在恢复期间会增加;
  • PG 被发明出来的主要原因之一就是通过把 RADOS 对象聚合成可管理大小的集合来降低这类开销;
  • → 限制 PG 数量能节省大量资源。

综合建议(原文)

Performance and even data distribution across OSDs weigh in favor of a higher number of PGs. Conserving CPU resources and minimizing memory usage weigh in favor of a lower number of PGs. The latter was more of a concern before Filestore OSDs were deprecated, so most modern clusters with BlueStore OSDs can favor the former by configuring a value of 200-250 for mon_target_pg_per_osd and 500 for mon_max_pg_per_osd.

⚠️ 注意:mon_max_pg_per_osd 只是一个 failsafe(保险丝),它本身不参与 pg_num 的计算。

4.5 手工计算 PG 数量

原文注:It is rarely necessary to do the math in this section by hand. 建议改用 ceph osd pool autoscale-status 配合 target_size_bytes / target_size_ratio。

推荐值:

条件 建议
OSD > 50 每 OSD 约 100~250 个 PG 副本,以平衡资源占用、数据持久性和数据分布
OSD < 50 遵循 preselection 段落(4.3 末)的指导

单 pool 基线公式:

Total PGs = (OSDs × 100) / pool size

其中 pool size:

  • 副本池 = 副本数;
  • 纠删码池 = K+M 之和(可用 ceph osd erasure-code-profile get 取到)。

三条后续规则:

  1. 结果应向上取整到最近的 2 的幂;
  2. 每个 pool 的 pg_num 都应是 2 的幂 —— 其它值很可能造成跨 OSD 的数据分布不均;
  3. 只在可行且可取时才把 pg_num 提到下一个更高的 2 的幂。 ⚠️ 2 的幂规则是 per-pool 的:既没有必要、也不容易让所有 pool 的 pg_num 之和对齐到 2 的幂。

官方算例:

200 OSD,单 pool,3 副本:
  (200 × 100) / 3 = 6667  →  向上取到最近的 2 的幂:8192

多 pool 时要平衡"每 pool 的 PG 数"与"每 OSD 的 PG 数":

例子 结果
10 个 pool,每 pool 512 PG,10 个 OSD 5120 PG / 10 OSD = 每 OSD 512 个 PG → 不会消耗过多资源
1000 个 pool,每 pool 512 PG,10 个 OSD 每 OSD 要处理 ≈ 50,000 个 PG → 需要显著更多的资源,peering 也会显著更慢

4.6 PG Calc 工具(pgcalc)用法与公式

PG Calc 是官方提供的交互式 PG 计算器(rados/operations/pgcalc,页面上是一个 JS 小工具)。

使用步骤(原文 Instructions):

  1. 通过阅读下方的 Key 确认你理解各个字段;
  2. 从下拉菜单选择一个 "Ceph Use Case";
  3. 调整下方绿色底纹字段的值(提示:表头可点击,用于整列改值);
  4. 你会看到 Suggested PG Count 随输入更新;
  5. 点 "Add Pool" 按钮新增一行新 pool;
  6. 点垃圾桶图标删除某个特定 pool;
  7. 关于所用逻辑和重要细节,见表格下方区域;
  8. 所有值调整完毕后,点 "Generate Commands" 按钮获取 pool 创建命令。

核心公式(原文 "Logic behind Suggested PG Count"):

        ( Target PGs per OSD ) × ( OSD # ) × ( %Data )
        ─────────────────────────────────────────────
                            ( Size )

后处理三步:

  1. 下限保护:若上式结果 小于 ( OSD# ) / ( Size ),则把结果提升到 ( OSD# ) / ( Size )。

    目的:确保每个 OSD 对每个 pool 至少分到一个 Primary 或 Secondary PG,从而实现均匀的负载/数据分布。

  2. 取 2 的幂:输出值四舍五入到最近的 2 的幂。

    Tip:最近的 2 的幂为 CRUSH 算法的效率带来边际改善。

  3. 上调保护:若"最近的 2 的幂"比原值低了超过 25%,则改用再高一级的 2 的幂。

目标(Objective):这套计算和 Key 中的目标区间的目的是 —— 确保有足够的 PG 让数据在整个集群中均匀分布,同时又不让 PG-per-OSD 比率高到在 Recovery / Backfill 时造成问题。

空 pool / 非活跃 pool 的影响(原文):

  • 空的或非活跃的 pool 不应被视为对集群均匀分布有益;
  • 但是,这些空/非活跃 pool 关联的 PG 仍然消耗内存和 CPU 开销。

4.7 PG 相关运维命令

# 查看某 pool 的 PG 数
ceph osd pool get {pool-name} pg_num

# 查看集群 PG 明细(格式:plain(默认)/ json)
ceph pg dump [--format {format}]

# 查看处于指定状态的"卡住"的 PG 的统计
ceph pg dump_stuck inactive|unclean|stale|undersized|degraded [--format <format>] [-t|--threshold <seconds>]

dump_stuck 的四类:

类别 含义
Inactive PG 无法处理读写,因为它们在等待足够多的持有最新数据的 OSD 变为 up 且 in
Undersized PG 中包含的对象尚未被复制期望的次数。正常情况下可认为这些 PG 正在恢复
Stale PG 处于未知状态 —— 承载它们的 OSD 已经有一段时间没向 monitor 集群上报了(该时长由 mon_osd_report_timeout 决定)
unclean ❗原文在 dump_stuck 的参数列表中列出了 unclean,但未给出其定义(见「文档未明确项」第 5 条)
  • 合法格式:plain(默认)和 json;
  • threshold:定义 PG 卡住多少秒后才被纳入返回的统计,默认 300。
# 取某个 PG 的 PG map
ceph pg map {pg-id}
# 例
ceph pg map 1.6c

输出形如:

osdmap e13 pg 1.6c (1.6c) -> up [1,0] acting [1,0]
# 查看某个 PG 的统计
ceph pg {pg-id} query

Scrub

# 高优先级、立即执行(不受星期/时间限制,不受 osd_max_scrubs 限制,不必等待副本的 scrub 资源)
ceph tell {pg-id} scrub
ceph tell {pg-id} deep-scrub

# 按"常规 scrub"的方式调度(受调度限制)
ceph tell {pg-id} schedule-scrub
ceph tell {pg-id} schedule-deep-scrub

# scrub 某个 pool 的所有 PG
ceph osd pool scrub {pool-name}

Scrub 做了什么(原文):Ceph 检查 primary 和 replica OSD,并生成该 PG 中所有对象的目录。对每个对象,Ceph 比较该对象在所有实例(primary 和 replica OSD)中的副本以确保一致。

  • shallow scrub(第一种命令格式):只比较对象元数据;
  • deep scrub(第二种命令格式):还比较对象内容;
  • 若所有副本一致,还会做一次 final semantic sweep,确保所有与快照相关的对象元数据一致;
  • 错误报告在日志中。

优先化 PG 的 backfill / recovery

# 指定 PG 优先 recovery / backfill
ceph pg force-recovery {pg-id} [{pg-id #2}] [{pg-id #3} ...]
ceph pg force-backfill {pg-id} [{pg-id #2}] [{pg-id #3} ...]

# 取消
ceph pg cancel-force-recovery {pg-id} [{pg-id #2}] [{pg-id #3} ...]
ceph pg cancel-force-backfill {pg-id} [{pg-id #2}] [{pg-id #3} ...]

# 让某个 pool 的所有 PG 优先
ceph osd pool force-recovery {pool-name}
ceph osd pool force-backfill {pool-name}

# 取消
ceph osd pool cancel-force-recovery {pool-name}
ceph osd pool cancel-force-backfill {pool-name}

语义要点(原文):

  • 这些命令让 Ceph 先于其他 PG 处理指定 PG 的 recovery / backfill;
  • Prioritization 不会打断当前正在进行的 backfill 或 recovery,而是把指定 PG 放到队列最前面,使它们下一个被处理;
  • 取消操作只影响仍在排队中的 PG,不影响正在恢复的 PG;
  • force flag 在 recovery 或 backfill 完成后会自动清除。

⚠️ 官方警告:这些命令可能破坏 Ceph 内部优先级计算的顺序,谨慎使用!

更推荐的替代方案:如果多个 pool 共享同一批底层 OSD,且某些 pool 的数据更重要,官方推荐使用:

ceph osd pool set {pool-name} recovery_priority {value}
用法 说明
20 个 pool 时 最重要的 pool 设 20,次重要设 19,依此类推
只关心 3 个重要 pool 三个都设 1,其他 pool 不设
三个重要 pool 排序 分别设 3、2、1

⚠️ 重要:用 recovery_priority 时,数值越大优先级越高。例:优先级 30 的 pool 高于优先级 15 的 pool。

💡 补充:注意这与 ceph osd pool set {pool-name} recovery_priority {value} 在 pools.rst 中描述的取值范围 -10 到 10(默认 0)存在表述差异 —— placement-groups.rst 的例子用了 20/19/30/15 这类超出该范围的值。以 pools.rst 的范围说明为准,placement-groups.rst 的示例值应理解为示意。

恢复丢失的 RADOS 对象

如果集群丢失了一个或多个 RADOS 对象,且你已决定放弃寻找:

ceph pg {pg-id} mark_unfound_lost revert|delete

语义(原文):

  • 该命令只有一个受支持的选项:revert;
  • revert 会:若该 RADOS 对象旧到有前一个版本,则回滚到前一个版本;若太新而没有前一版本,则彻底遗忘它;
  • 但命令签名中同时列出了 delete。

⚠️ 官方重要提示:Use this feature with caution. It might confuse applications that expect the object(s) to exist.

4.8 Pool, PG and CRUSH 配置参考(pool-pg-config-ref)

概述

决定 CRUSH 算法给每个 pool 分配多少 PG 的变量值,存放在 monitor 集群的 central configuration database 中。容器化部署(cephadm 或 Rook)和非容器化部署都依赖 monitor 集群中这个中心配置数据库的值来为 pool 分配 PG。

# 查看
ceph config get osd osd_pool_default_pg_num

# 设置
ceph config set osd osd_pool_default_pg_num

⚠️ 原文给出的 ceph config set osd osd_pool_default_pg_num 后面没有写值(源文本身如此,见「文档未明确项」第 6 条)。

全部配置项(逐项列出)

⚠️ 本节 24 个配置项在源文中均为 .. confval:: 指令,rst 源文不包含 type / default / description 字段(详见「阅读前须知」)。下表"含义"列取自源文散文;"💡"开头的行为本手册补充。

配置项 类型 默认值 含义 / 上下文 调参建议
mon_max_pool_pg_num 文档未明确 64_K(= 65536 字节 = 64 KiB)(默认值取自 src/common/options/mon.yaml.in @ v20.2.4) 与 pool 的 pg_num 取值上界相关(pg_num 的 Valid Range 为 0 到 mon_max_pool_pg_num) 💡 新建 pool 时若报 pg_num 超限,检查此项。Placement Groups 页另提到 BlueStore 集群可配 mon_max_pg_per_osd 到 500,注意这是另一个参数(见 4.4 节)
mon_pg_stuck_threshold 文档未明确 1_min(= 60 秒)(默认值取自 src/common/options/mgr.yaml.in @ v20.2.4) 与"卡住的 PG"(stuck PG)判定阈值相关 💡 ceph pg dump_stuck 有自己的 --threshold(默认 300 秒),两者不同,勿混淆
mon_pg_warn_min_per_osd 文档未明确 0(默认值取自 src/common/options/mgr.yaml.in @ v20.2.4) 每 OSD 的 PG 数低于此值时可能触发告警 💡 与 mon_target_pg_per_osd(目标值)是一对:一个管下限告警,一个管目标
mon_pg_warn_min_objects 文档未明确 10000(默认值取自 src/common/options/mgr.yaml.in @ v20.2.4) PG 中对象数相关的告警下限 💡 只在对象数极少的小集群调优时关注
mon_pg_warn_min_pool_objects 文档未明确 1000(默认值取自 src/common/options/mgr.yaml.in @ v20.2.4) pool 中对象数相关的告警下限 💡 同上
mon_pg_check_down_all_threshold 文档未明确 0.5(默认值取自 src/common/options/mgr.yaml.in @ v20.2.4) 检查"全部 OSD down"场景的阈值 💡 大规模集群整机断电类场景相关
mon_pg_warn_max_object_skew 文档未明确 10(默认值取自 src/common/options/mgr.yaml.in @ v20.2.4) 对象数偏斜(skew)的告警上限 💡 与 4.4 节"少数超大对象造成分布倾斜"的现象对应
mon_delta_reset_interval 文档未明确 10(默认值取自 src/common/options/mgr.yaml.in @ v20.2.4) monitor 侧 delta 统计的重置间隔 💡 一般不动
osd_crush_chooseleaf_type 文档未明确 1(默认值取自 src/common/options/global.yaml.in @ v20.2.4) CRUSH chooseleaf 默认使用的 bucket 类型(即默认故障域) 💡 与 ceph osd crush rule create-replicated 的 failure-domain-type 语义对应;现代集群建议显式建 rule 而不是依赖此项
osd_crush_initial_weight 文档未明确 -1(默认值取自 src/common/options/osd.yaml.in @ v20.2.4) OSD 加入 CRUSH map 时的初始权重 💡 若用 ceph-volume 部署,权重会按设备容量自动计算;自定义容量映射时才需要
osd_pool_default_crush_rule 文档未明确 -1(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 新建 replicated pool 默认使用的 CRUSH rule(pools.rst 明确:该 rule 必须存在) 💡 若建好了 replicated-ssd 之类的 rule 并希望成为默认,改此项
osd_pool_erasure_code_stripe_unit 文档未明确 4_K(= 4096 字节 = 4 KiB)(默认值取自 src/common/options/mon.yaml.in @ v20.2.4) EC profile 未显式指定 stripe_unit 时,创建 pool 所采用的默认 stripe unit 💡 Tentacle EC optimizations 强烈建议提升到至少 16K(见 7.8 节);此项是全局默认,stripe_unit 是 per-profile
osd_pool_default_size 文档未明确 3(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 新建副本 pool 的默认副本数 💡 pools.rst 提到 Ceph 默认维护 3 个副本(`ceph osd dump \
osd_pool_default_min_size 文档未明确 0(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 新建 pool 的默认 min_size 💡 EC 池的 min_size 应 > K(pools.rst 明确,见 6.6 节)
osd_pool_default_pg_num 文档未明确 32(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 新建 pool 未显式指定时使用的 PG 数。pools.rst 另给出 ceph osd pool create 的 {pg-num} 参数 Default: 32,并强调 "The default value is NOT suitable for most systems" 💡 强烈建议不要依赖默认值。要么建 pool 时显式给 pg_num,要么启用 autoscaler
osd_pool_default_pgp_num 文档未明确 0(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 新建 pool 未显式指定时使用的 pgp_num。pools.rst 给出 {pgp-num} 参数 Default: 32 💡 Nautilus 起通常不需要关心(会自动步进匹配 pg_num)
osd_pool_default_pg_autoscale_mode 文档未明确 on(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 控制集群初始搭建之后新建 pool 的 autoscale 模式(on/off/warn) 💡 新集群建议设为 on
osd_pool_default_flags 文档未明确 0(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 新建 pool 默认带上的 flag 集合 💡 一般不动
osd_max_pgls 文档未明确 1_K(= 1024 字节 = 1 KiB)(默认值取自 src/common/options/osd.yaml.in @ v20.2.4) 单次 pg ls 类命令返回的最大 PG 条数 💡 PG 数很多(数万)且 ceph pg ls 被截断时调大
osd_min_pg_log_entries 文档未明确 250(默认值取自 src/common/options/global.yaml.in @ v20.2.4) PG log 保留的最小条目数 💡 与 osd_max_pg_log_entries 共同决定 PG log 长度;影响 peering 能否走 log 而非 backfill
osd_max_pg_log_entries 文档未明确 10000(默认值取自 src/common/options/global.yaml.in @ v20.2.4) PG log 保留的最大条目数 💡 调大可让更多场景走 log-based recovery 而非 full backfill,代价是内存
osd_default_data_pool_replay_window 文档未明确 45(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 与 data pool(cache tier 场景下)的 replay 窗口相关 💡 cache tiering 已在 Reef 废弃,新部署无需关注
osd_max_pg_per_osd_hard_ratio 文档未明确 3(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 每 OSD 的 PG 数硬上限比率(failsafe 性质) 💡 与 mon_max_pg_per_osd 同属保险丝类参数
osd_pool_default_flag_ec_optimizations 文档未明确 false(默认值取自 src/common/options/global.yaml.in @ v20.2.4) Tentacle 新增。让 EC optimizations 成为新建 pool 的默认行为(per-pool 开关是 allow_ec_optimizations) 💡 见 7.8 节;存量 pool 需显式 ceph osd pool set <pool> allow_ec_optimizations true

引自:Placement Groups、PG Calc、Pool, PG and CRUSH Config Reference


五、PG 状态机(pg-states、pg-concepts)

5.1 PG 核心术语(pg-concepts)

执行 ceph -w、ceph osd dump 等命令时会看到这些术语。官方定义逐条如下:

术语 官方定义
Peering 让存储某个 PG 的所有 OSD 就该 PG 中所有对象(及其元数据)的状态达成一致的过程。⚠️ 注意:对状态达成一致并不意味着它们都拥有最新内容
Acting Set 负责某个特定 PG 的 OSD 有序列表(是"当前"或"截至某个 epoch 曾经"负责的那些 OSD)
Up Set 依据 CRUSH,在某个特定 epoch 负责某个 PG 的 OSD 有序列表。通常与 Acting Set 相同,除非 Acting Set 被 OSD Map 中的 pg_temp 显式覆盖
Current Interval / Past Interval Acting Set 和 Up Set 对某个特定 PG 都不发生变化的一段连续的 OSD map epoch 序列
Primary Acting Set 的成员(按约定是第一个),负责协调 peering,并且是唯一会接受客户端发起的、对该 PG 中对象写操作的 OSD
Replica Acting Set 中的非 primary OSD(且已被 primary 认可并 activate)
Stray 不是当前 Acting Set 成员的 OSD,但尚未被告知可以删除它对某个 PG 的副本
Recovery 确保 PG 中所有对象的副本存在于 Acting Set 的所有 OSD 上。一旦 Peering 完成,Primary 就可以开始接受写操作,而 Recovery 在后台进行
PG Info 关于 PG 的基础元数据:创建 epoch、该 PG 最近一次写入的 version、last epoch started、last epoch clean,以及 current interval 的起点。任何 OSD 间关于 PG 的通信都包含 PG Info,因此任何知道某 PG 存在(或曾存在)的 OSD,也都拥有 last epoch clean 或 last epoch started 的下界
PG Log 对某个 PG 中对象所做近期更新的列表。⚠️ 当 Acting Set 中所有 OSD 都已确认到某一点之后,这些 log 可以被截断
Missing Set 每个 OSD 记录更新日志条目,若某条目意味着对象内容发生了更新,就把该对象加入一个"需要的更新"列表。这个列表称为该 <OSD,PG> 的 Missing Set
Authoritative History 一个完整且完全有序的操作集合 —— 若执行这些操作,就能把某 OSD 的 PG 副本更新到最新
Epoch (单调递增的)OSD map 版本号
Last Epoch Start Acting Set 中所有节点就某个 Authoritative History 达成一致的最后一个 epoch。到这一点,Peering 被视为成功
up_thru 在 Primary 能成功完成 Peering 之前,它必须通过一个存活的 monitor 把自己在 osd map 中的 up_thru 设置到当前 OSD map Epoch。这有助于 Peering 忽略那些"Peering 从未完成"的先前 Acting Set
Last Epoch Clean Acting Set 中所有节点**完全最新(PG log 和对象内容都最新)**的最后一个 Epoch。到这一点,recovery 被视为完成

up_thru 为什么必要(原文给的失败序列)

- acting set = [A,B]
- acting set = [A]
- acting set = []   很短时间之后(例如同时故障但检测有先后)
- acting set = [B]  (B 重启了,A 没有)

如果没有 up_thru,B 重启后可能会错误地认为可以和 A 完成 peering。up_thru 机制让 Peering 能忽略这类"Peering 从未真正完成"的旧 Acting Set。

5.2 PG 状态全表(pg-states)

前置知识:检查集群状态时(ceph -w / ceph -s),Ceph 会报告 PG 的状态。一个 PG 有一个或多个状态("A placement group has one or more states")。PG map 中 PG 的最佳状态是 active + clean。

⚠️ 阅读约定:下表"含义"列是官方文档原文定义的中文转述(权威);"典型触发原因"和"处理方向"两列官方文档在本页未给出,为本手册基于本册其它章节(pg-concepts、placement-groups、crush-map、pools)的补充,均标注 💡。

状态 含义(官方文档) 典型触发原因 💡 处理方向 💡
creating Ceph 仍在创建该 PG 新建 pool、增大 pg_num、PG 分裂 通常瞬时,无需干预。若长期停留,检查 ceph -s 是否有 OSD 未 up
activating PG 已 peered 但尚未 active peering 刚完成,等待激活 瞬时状态,一般自动转到 active
active Ceph 会处理对该 PG 的请求 正常 无需干预
clean Ceph 已把该 PG 中所有对象复制了正确的次数 正常 无需干预。active + clean 是最佳状态
down 一个持有必要数据的副本 down 了,因此该 PG 离线 承载该 PG 且持有唯一/必要数据的 OSD 掉线 尽快恢复对应 OSD;见第十一章心跳与 mon_osd_down_out_interval
laggy 某个副本没有及时确认来自 primary 的新 lease;I/O 被临时暂停 副本 OSD 高负载 / 网络抖动 / 慢盘 排查该副本 OSD 的磁盘与网络;与 11.4 节的 laggy 相关参数(mon_osd_laggy_*)有关
wait 该 PG 的 OSD 集合刚刚发生变化,I/O 被临时暂停,直到前一个 interval 的 lease 过期 刚发生 OSD 增减 / PG 重映射 瞬时状态,等 lease 过期即可
scrubbing Ceph 正在检查该 PG 的元数据是否存在不一致 周期性 shallow scrub 无需干预(见 4.7 的 scrub 命令)
deep Ceph 正在对照存储的校验和检查该 PG 的数据 周期性 deep scrub 无需干预。可用 ceph tell {pg-id} deep-scrub 手动触发
degraded Ceph 尚未把该 PG 中的部分对象复制够正确次数 OSD 掉线 / 正在恢复 观察是否转为 recovering;长期 degraded 需检查 OSD 与容量
inconsistent Ceph 在该 PG 中一个或多个对象副本中检测到不一致(例如对象大小错误、recovery 完成后对象从某个副本中缺失等) scrub 发现不一致 查看日志定位对象;通常需要 ceph pg repair(对应下面的 repair 状态)
peering 该 PG 正在进行 peering 过程 OSD 重启 / OSD map 变更 短时正常。长期 peering 需查 OSD 间通信与 PG log 长度(osd_min/max_pg_log_entries)
repair Ceph 正在检查该 PG 并修复它发现的任何不一致(若可能) 由不一致触发的修复动作 等待完成;配合 scrub / deep-scrub 诊断
recovering Ceph 正在迁移/同步对象及其副本 OSD 恢复、PG 重映射 正常。可用 ceph pg force-recovery 提优先级(见 4.7)
forced_recovery 该 PG 的高 recovery 优先级由用户强制设定 执行了 ceph pg force-recovery 完成后 force flag 自动清除;也可 cancel-force-recovery
recovery_wait 该 PG 排队等待开始 recovery 并发恢复数受限 正常排队。可调 recovery 并发或提高该 PG 优先级
recovery_toofull 一个 recovery 操作正在等待,因为目标 OSD 超过了它的 full ratio 目标 OSD 已满 扩容或清理空间;检查 OSD full / backfillfull ratio
recovery_unfound Recovery 因 unfound 对象而停止 有对象在所有已知位置都找不到 见 4.7 的 ceph pg {pg-id} mark_unfound_lost revert
backfilling Ceph 正在扫描并同步整个 PG 的全部内容,而不是从近期操作日志推断需要同步哪些内容。Backfill 是 recovery 的一种特殊情况 新 OSD 加入、PG log 不足以做增量恢复 正常(但比增量恢复慢、更耗 IO)。可用 ceph pg force-backfill 提优先级
forced_backfill 该 PG 的高 backfill 优先级由用户强制设定 执行了 ceph pg force-backfill 同 forced_recovery
backfill_wait 该 PG 排队等待开始 backfill 并发 backfill 数受限 正常排队
backfill_toofull 一个 backfill 操作正在等待,因为目标 OSD 超过了 backfillfull ratio 目标 OSD 接近满 扩容或清理空间
backfill_unfound Backfill 因 unfound 对象而停止 同 recovery_unfound 同 recovery_unfound
incomplete Ceph 检测到某个 PG 缺失关于"可能已发生的写入"的信息,或者没有任何健康的副本 多个 OSD 同时故障且持有不同写入;可用副本不足 官方给的方向:尝试启动任何可能包含所需信息的失败 OSD。若是纠删码池,临时降低 min_size 可能允许恢复
stale 该 PG 处于未知状态 —— 自 PG mapping 发生变化以来,monitors 没有收到关于它的更新 承载 OSD 长时间未向 monitor 上报 检查对应 OSD 与 monitor 连通性;与 mon_osd_report_timeout 相关(见 4.7 与第十一章)
remapped 该 PG 被临时映射到一组与 CRUSH 指定的不同的 OSD OSD 故障后临时映射;pgp_num 步进过程中 若为 pgp_num 步进导致,属预期行为(见 4.2)。否则等待恢复完成
undersized 该 PG 的副本数少于配置的 pool 复制级别 部分副本不可用 通常伴随 degraded/recovering,观察恢复
peered 该 PG 已 peered,但因副本数不足以达到 pool 配置的 min_size 而无法服务客户端 I/O。Recovery 可能在此状态下发生,因此 PG 最终可能恢复到 min_size 存活副本 < min_size 恢复 OSD;或临时调整 min_size(有风险,见 6.6 节与 7.9 节)
snaptrim 正在修剪快照(Trimming snaps) 快照删除后的异步清理 正常
snaptrim_wait 排队等待修剪快照 snaptrim 并发受限 正常排队
snaptrim_error 修剪快照因错误停止 修剪过程出错 查日志定位具体错误
unknown ceph-mgr 自启动以来,尚未从任何 OSD 收到关于该 PG 状态的信息 mgr 刚重启 通常瞬时;若长期存在,检查 mgr 与 OSD

状态速查:几个容易混淆的对照

对比 区别
degraded vs undersized degraded:部分对象没复制够次数;undersized:副本数少于配置的复制级别。二者常同时出现
recovering vs backfilling backfill 是 recovery 的特例:backfill 是扫描并同步整个 PG 的全部内容;而普通 recovery 是从近期操作日志推断需要同步哪些内容
peered vs active peered = 已完成 peering 但副本数不够 min_size,不能服务 I/O;active = 能处理请求
stale vs down stale = monitor 侧长时间没收到该 PG 的更新(信息缺失);down = 持有必要数据的副本掉线(PG 离线)
recovery_toofull vs backfill_toofull 前者卡在 full ratio,后者卡在 backfillfull ratio(backfillfull 通常更严格)

引自:Placement Group States、Placement Group Concepts


六、Pool 管理(pools.rst)

6.1 Pool 是什么

Pools are logical partitions that are used to store RADOS objects.

Pool 提供四件事:

能力 说明
Resilience(韧性) 可以规划"多少个 OSD 并行故障而不导致数据不可用或丢失"。副本池:不丢数据的并行故障 OSD 数 = 副本数 - 1;不导致数据不可用的数量通常为 2。典型配置每对象存 3 个副本(size = 3),但副本数可按 pool 配置。纠删码池:韧性定义为 coding(parity)chunk 的数量(例如默认 EC profile 的 m = 2)
Placement Groups autoscaler 为 pool 设定 PG 数。典型配置下,每 OSD 约一百五十个 PG("the target number of PGs is approximately one-hundred and fifty PGs per OSD"),在合理均衡与不过度消耗计算资源之间取得平衡。设置多个 pool 时,要确保每 OSD 的 PG 副本之和落在期望的 PG-per-OSD 目标区间内。工具见 pgcalc
CRUSH Rules 数据存入 pool 时,PG 与对象副本(EC 池则为 chunk/shard)在集群中的放置由 CRUSH rule 决定。若默认 rule 不满足用例,可为 pool 创建自定义 CRUSH rule
Snapshots ceph osd pool mksnap 创建 pool 快照

6.2 Pool 命名

⚠️ 以 . 开头的 pool 名保留给 Ceph 内部操作使用。不要创建或操作这些名字的 pool。

(例如 .mgr、.rgw.root 等。)

6.3 列出 Pool

# 只列名(适合脚本)
ceph osd pool ls
.rgw.root
default.rgw.log
default.rgw.control
default.rgw.meta
# 带 pool 编号
ceph osd lspools
1 .rgw.root
2 default.rgw.log
3 default.rgw.control
4 default.rgw.meta
# 带附加信息
ceph osd pool ls detail
pool 1 '.rgw.root' replicated size 3 min_size 1 crush_rule 0 object_hash rjenkins pg_num 32 pgp_num 32 autoscale_mode on last_change 19 flags hashpspool stripe_width 0 application rgw read_balance_score 4.00
pool 2 'default.rgw.log' replicated size 3 min_size 1 crush_rule 0 object_hash rjenkins pg_num 32 pgp_num 32 autoscale_mode on last_change 21 flags hashpspool stripe_width 0 application rgw read_balance_score 4.00
pool 3 'default.rgw.control' replicated size 3 min_size 1 crush_rule 0 object_hash rjenkins pg_num 32 pgp_num 32 autoscale_mode on last_change 23 flags hashpspool stripe_width 0 application rgw read_balance_score 4.00
pool 4 'default.rgw.meta' replicated size 3 min_size 1 crush_rule 0 object_hash rjenkins pg_num 128 pgp_num 128 autoscale_mode on last_change 25 flags hashpspool stripe_width 0 pg_autoscale_bias 4 application rgw read_balance_score 4.00

更多信息可用 --format / -f 加 json、json-pretty、xml 或 xml-pretty。

💡 补充:从上面的真实输出可以读出几个常用默认值/常见值:object_hash rjenkins、pg_num 32、autoscale_mode on、flags hashpspool、application rgw。

6.4 创建 Pool

# 副本池
ceph osd pool create {pool-name} [{pg-num} [{pgp-num}]] [replicated] \
         [crush-rule-name] [expected-num-objects]

# 纠删码池
ceph osd pool create {pool-name} [{pg-num} [{pgp-num}]] erasure \
         [erasure-code-profile] [crush-rule-name] [expected_num_objects] [--autoscale-mode=<on,off,warn>]

参数详解

参数 说明 类型 必填 默认值
{pool-name} pool 名,必须唯一 String 是 —
{pg-num} pool 中 PG 总数。默认值对大多数系统是不合适的("The default value is NOT suitable for most systems") Integer 是 32
{pgp-num} 用于放置目的的 PG 总数。应等于 PG 总数,除非在 pg_num 增减的短暂窗口期。Nautilus 起通常不直接改 pgp_num:Ceph 会在 pg_num 调整时自动增量扩缩它 Integer 是(若命令中未指定则用默认值,除非 Ceph 配置中设了不同的值) 32
{replicated|erasure} pool 的数据保护策略 String 否 replicated
[crush-rule-name] 该 pool 使用的 CRUSH rule 名。指定的 rule 必须已存在,否则命令失败 String 否 副本池:默认用 osd_pool_default_crush_rule 配置选项指定的 rule,该 rule 必须存在。
EC 池:若用 default erasure code profile 则为 erasure-code rule;否则为 {pool-name} rule。若该 rule 不存在会被隐式创建
[erasure-code-profile=profile] 仅 EC pool。指定要用的 erasure code profile。该 profile 必须已存在(通过 dashboard 或 osd erasure-code-profile set 定义)。⚠️ pool 创建之后再改 EC profile 不生效;要给已有 pool 换 EC profile,必须修改该 pool 去使用一条用目标 profile 定义的不同 CRUSH rule String 否 —
--autoscale-mode=<on,off,warn> on = 依实际用量自动调优 PG 数;warn = 依实际用量建议调整;off = 关闭 String 否 由 osd_pool_default_pg_autoscale_mode 选项决定
[expected-num-objects] 该 pool 预期的 RADOS 对象数量。设置此值可让 PG 分裂在 pool 创建时发生,避免运行时分裂带来的延迟冲击 Integer 否 0(创建时不分裂)

replicated vs erasure 的取舍(原文表述)

replicated erasure
类比 像 RAID1 和 RAID10 一种通用化奇偶校验 RAID 策略,类似 RAID6 但更灵活
可用容量 给定原始存储量,可用容量较少 通常更多
适用面 适用于所有 Ceph 组件和用例 只适用于部分 Ceph 组件和用例
故障域要求 — 通常需要比复制更多的故障域
性能 — 性能下降
故障容忍 — 可能容忍更多的重叠盘/主机故障

⚠️ 另注(pools.rst 明确):Luminous 及以后,每个 pool 必须关联一个 application 才能被使用。

6.5 关联 Application

ceph osd pool application enable {pool-name} {application-name}
  • CephFS 用的 pool 和 RGW 自动创建的 pool 会自动关联;
  • 给 RBD 用的 pool 应通过 dashboard 或 rbd CLI 工具初始化;
  • 内置 application 名:CephFS 用 cephfs,RBD 用 rbd,RGW 用 rgw。

6.6 设置 Pool 值

ceph osd pool set {pool-name} {key} {value}

可设置的 key 全表

key 说明 类型 合法值 / 默认值 备注
compression_algorithm 设置底层 BlueStore 后端存储数据时使用的内联压缩算法。会覆盖全局设置 bluestore_compression_algorithm String lz4、snappy、zlib、zstd
compression_mode 设置底层 BlueStore 后端存储数据时的内联压缩策略。覆盖全局 bluestore_compression_mode String none、passive、aggressive、force
compression_min_blob_size 设置压缩 chunk 的最小尺寸:小于此值的 chunk 不压缩。覆盖全局 bluestore_compression_min_blob_size、..._hdd、..._ssd Unsigned Integer —
compression_max_blob_size 设置 chunk 的最大尺寸:大于此值的 chunk 会被拆成不大于此值的更小 blob 再进行压缩 Unsigned Integer —
size 设置 pool 中对象的副本数。只能用于 replicated pool。EC pool 会报告一个等于 K+M 的 size,但该值不能被直接设置 Integer — {num-replicas} 包含对象本身。例:想在原对象之外再有两份副本(共 3 个实例)→ 设 3
min_size 设置 PG 能 active、从而能进行 I/O 所需的最小活跃副本(或分片)数 Integer Version 0.54 及以上 对纠删码池,应设为大于 K 的值。若只以 K 个分片可用就允许 I/O,则没有任何冗余,一旦再有 OSD 永久故障就会丢数据
pg_num 指定该 pool 的 PG 总数。⚠️ 若该 pool 启用了 PG autoscaler,它可能覆盖手工设置的值 Integer 0 到 mon_max_pool_pg_num。若设为 0,则使用 osd_pool_default_pg_num 的值
pgp_num 设置计算数据放置时使用的有效 PG 数。Nautilus 起管理员通常不显式设置;Ceph 会自动增量扩缩它以匹配 pg_num Integer 1 到当前 pg_num 之间
crush_rule 设置 Ceph 用来把该 pool 的 RADOS 对象映射到合适 OSD 的 CRUSH rule String —
allow_ec_overwrites 决定是否允许对纠删码池的写入只更新 RADOS 对象的一部分。这使得 CephFS 和 RBD 能用 EC 池存用户数据(但不能存元数据) Boolean — .. versionadded:: 12.2.0
allow_ec_optimizations 为纠删码池启用性能和容量优化。这些优化为 CephFS 和 RBD 工作负载设计;有大量的小对象或对象小随机读的 RGW 负载也会受益;大对象顺序读写的 RGW 负载几乎无收益 Boolean — .. versionadded:: 20.2.0(Tentacle)
hashpspool 设置或取消给定 pool 上的 HASHPSPOOL flag Integer 1 置位,0 取消
nodelete 设置或取消 NODELETE flag Integer 1 置位,0 取消 Version: FIXME(源文如此)
nopgchange 设置或取消 NOPGCHANGE flag Integer 1 置位,0 取消 Version: FIXME
nosizechange 设置或取消 NOSIZECHANGE flag Integer 1 置位,0 取消 Version: FIXME
bulk 设置或取消 BULK flag Boolean true/1 置位,false/0 取消 见 4.3 节
write_fadvise_dontneed 设置或取消 WRITE_FADVISE_DONTNEED flag Integer 1 置位,0 取消
noscrub 设置或取消 NOSCRUB flag Integer 1 置位,0 取消
nodeep-scrub 设置或取消 NODEEP_SCRUB flag Integer 1 置位,0 取消
target_max_bytes 当 max_bytes 阈值被触发且已废弃的 cache tier 功能在使用中时,Ceph 会开始 flush 或 evict 对象 Integer 例:1000000000000(#1-TB) ⚠️ cache tiering 已废弃,见 6.11
target_max_objects 当 max_objects 阈值被触发且已废弃的 cache tier 功能在使用中时,Ceph 会开始 flush 或 evict 对象 Integer 例:1000000(#1M objects) ⚠️ 同上
fast_read 仅纠删码池。若开启,读请求会向所有 shard 发出 "sub reads",然后等待收到足够解码所需的 shard 后再响应客户端。若使用 jerasure 或 isa 插件,则前 K 个响应返回后,客户端请求会立即用这些数据解码出来的内容被服务。这种做法牺牲资源换取更好的性能 Boolean 默认 0 仅 EC 池支持
scrub_min_interval 设置该 pool 的 PG 两次浅(light)scrub 之间的最小间隔(秒)。若保持默认 0,则改用 central config 中的 osd_scrub_min_interval Double 默认 0
scrub_max_interval 设置该 pool 的 PG 两次浅 scrub 之间的最大间隔(秒)。影响 scrub scheduler dump 中出现的 'overdue' 属性。若保持默认 0,则改用 osd_scrub_max_interval Double 默认 0
deep_scrub_interval 设置该 pool 的 PG 两次 deep scrub 之间的间隔(秒)。若保持默认 0,则改用 osd_deep_scrub_interval Double 默认 0
recovery_priority 调整一个 pool 的计算预留优先级(computed reservation priority)。取值范围 -10 到 10。⚠️ 任何被赋予负值的 pool 都会比任何新建 pool 优先级更低,因此建议把负值给低优先级 pool Integer 默认 0 ⚠️ 与 4.7 节 placement-groups.rst 的用法示例(用了 20/19/30/15)取值范围不一致,见 4.7 的 💡 补充注
recovery_op_priority 设置某个 pool 的 PG 的 recovery 操作优先级。覆盖由 osd_recovery_op_priority 决定的通用优先级 Integer 默认 0

读取 Pool 值

ceph osd pool get {pool-name} {key}

可读取的 key:size、min_size、pg_num、pgp_num、crush_rule、target_max_bytes、target_max_objects、fast_read、scrub_min_interval、scrub_max_interval、deep_scrub_interval、allow_ec_overwrites、recovery_priority、recovery_op_priority。

(pgp_num 的读取 Valid Range:等于或小于 pg_num。)

6.7 副本数

# 设置副本数(num-replicas 包含对象本身)
ceph osd pool set data size 3

# 设置 I/O 所需的最小副本数
ceph osd pool set data min_size 2

# 查看
ceph osd dump | grep 'replicated size'

⚠️ 官方重要提示:生产环境把 size 设为 2 或把 min_size 设为 1 有数据丢失风险,只应在某些紧急情况下、且只是临时地这么做。

6.8 配额

ceph osd pool set-quota {pool-name} [max_objects {obj-count}] [max_bytes {bytes}]

例:

ceph osd pool set-quota data max_objects 10000
  • 把值设为 0 即移除该配额;
  • 可以只设 bytes、只设对象数,或两者都设。

6.9 快照

# 创建 pool 快照
ceph osd pool mksnap {pool-name} {snap-name}

# 删除 pool 快照
ceph osd pool rmsnap {pool-name} {snap-name}

6.10 重命名与删除

# 重命名
ceph osd pool rename {current-pool-name} {new-pool-name}

⚠️ 若被重命名的 pool 上有已认证用户的 per-pool capabilities,必须更新该用户的 caps 以引用新的 pool 名。

# 删除
ceph osd pool delete {pool-name} [{pool-name} --yes-i-really-really-mean-it]

前置条件:必须在 central configuration 中把 mon_allow_pool_delete flag 设为 true,否则 Ceph monitors 会拒绝删除 pool。

善后建议(原文):

  1. 清理不再使用的自定义 CRUSH rule:
# 查看该 pool 用的 rule
ceph osd pool get {pool-name} crush_rule

# 例:若自定义 rule 是 "123",检查所有 pool 是否还在用它
ceph osd dump | grep "^pool" | grep "crush_rule 123"

# 若没有 pool 用,可安全删除
ceph osd crush rule rm {rule-name}
  1. 清理权限限定在已删除 pool 上的用户:
ceph auth ls | grep -C 5 {pool-name}
ceph auth del {user}

6.11 Cache tier 相关(已废弃)

target_max_bytes / target_max_objects 两个 key 的描述里出现了 "the deprecated cache tier functionality";erasure-code.rst 中更明确:

⚠️ Cache tiering was deprecated in Reef. We strongly advise not deploying new cache tiers, and working to remove them from existing deployments.

历史用法(仅供理解存量集群,新部署不要用):

ceph osd tier add ecpool hot-storage
ceph osd tier cache-mode hot-storage writeback
ceph osd tier set-overlay ecpool hot-storage

引自:Pools

6.12 Stretch pool(延伸集群 pool)

# 设置
ceph osd pool stretch set {pool-name} {peering_crush_bucket_count} {peering_crush_bucket_target} {peering_crush_bucket_barrier} {crush_rule} {size} {min_size} [--yes-i-really-mean-it]

# 取消(回到非 stretch)
ceph osd pool stretch unset {pool-name} {crush_rule} {size} {min_size}

# 查看
ceph osd pool stretch show {pool-name}

stretch set 参数

参数 说明 类型 必填
{pool-name} pool 名。必须是已存在的 pool(本命令不创建新 pool) String 是
{peering_crush_bucket_count} 与 peering_crush_bucket_barrier 一起用于判断所选 acting set 中的 OSD 能否彼此 peering,依据是 acting set 中不同 bucket 的数量 Integer 是
{peering_crush_bucket_target} 与 peering_crush_bucket_barrier 和 size 一起用于计算 bucket_max,限制同一 bucket 中被选入某 PG acting set 的 OSD 数量 Integer 是
{peering_crush_bucket_barrier} pool 的 PG 分布在什么类型的 CRUSH bucket 之间,例如 rack、row 或 datacenter String 是
{crush_rule} pool 使用的 CRUSH rule。pool 类型必须与 CRUSH rule 类型匹配(replicated 或 erasure) String 是
{size} pool 中 RADOS 对象(以及 PG)的副本数 Integer 是
{min_size} 能服务 I/O 所必须活跃的最小副本数 Integer 是
--yes-i-really-mean-it 确认你确实想绕过安全检查来设置这些值。例:试图把 peering_crush_bucket_count 或 peering_crush_bucket_target 设成大于 CRUSH map 中 bucket 数量时 Flag 否

stretch unset / show 参数

  • unset:{pool-name} 必须是已 stretch 的 pool;{crush_rule} 为退出 stretch 后使用的 rule(类型必须与 pool 匹配);{size} / {min_size} 为退出后的副本数与最小副本数。
  • show:{pool-name} 必须是已 stretch 的 pool。

💡 补充:Stretch mode 的完整说明在 rados/operations/stretch-mode,不在本册 25 个源文范围内,此处仅收录 pools.rst 中出现的命令与参数定义。

6.13 查看 Pool 统计

# 利用率统计
rados df

# 某 pool(或所有 pool)的 I/O 信息
ceph osd pool stats [{pool-name}]

引自:Pools


七、纠删码(Erasure Code)

7.1 基本原理

默认情况下 Ceph pool 以 replicated 类型创建 —— 每个对象被复制到多个磁盘,这就是"复制(replication)"这种数据保护方法。

纠删码(erasure-coded)pool 用不同的方法:

  • 数据被拆成两类片段:data blocks(数据块) 和 parity blocks(奇偶校验块);
  • 若某个驱动器故障或损坏,用 parity blocks 重建数据;
  • 在规模上,纠删码相比复制节省空间("At scale, erasure coding saves space relative to replication")。

术语约定(本册统一):

本册/官方术语 含义
data chunks(数据块) data blocks
coding chunks(编码块) parity blocks

纠删码也叫 "forward error correction codes(前向纠错码)"。第一个前向纠错码由 Richard Hamming 于 1950 年在贝尔实验室开发。

7.2 快速上手

最简单的纠删码池类似 RAID5,至少需要三台 host:

ceph osd pool create ecpool erasure
pool 'ecpool' created
echo ABCDEFGHI | rados --pool ecpool put NYAN -
rados --pool ecpool get NYAN -
ABCDEFGHI

7.3 Profile 基础:K、M 与默认 profile

ceph osd erasure-code-profile get default
k=2
m=2
plugin=isa
crush-failure-domain=host
technique=reed_sol_van

⚠️ 两个易混淆的概念(原文专门澄清):

K / M 说明
默认的(default)纠删码池 k=2 m=2 有 2 个 data chunk + 2 个 coding chunk
最简单的(simplest)纠删码池 k=2 m=1 有 2 个 data chunk + 1 个 coding chunk

默认 profile 的能力:可承受两个 OSD 的重叠丢失而不丢数据。等价于一个三副本的副本池,但存储需求不同:不是用 3TB 存 1TB,而是只需 2TB 存 1TB。

⚠️ Profile 选错无法补救(原文重点强调): Choosing the right profile is important because the profile cannot be modified after the pool is created. 若发现需要不同 profile 的 EC 池,必须用不同的 profile 创建新 pool,然后把配错的 pool 里的所有对象迁移过去。没有任何办法在 pool 创建之后修改它的 profile。

最重要的三个参数:K、M 和 crush-failure-domain,因为它们定义了存储开销和数据持久性。

示例:若目标架构必须承受两个 rack 的丢失、且存储开销为 67%:

ceph osd erasure-code-profile set myprofile \
    k=3 \
    m=2 \
    crush-failure-domain=rack
ceph osd pool create ecpool erasure myprofile
echo ABCDEFGHI | rados --pool ecpool put NYAN -
rados --pool ecpool get NYAN -
ABCDEFGHI

对象 NYAN 会被分成 3 份(K=3),并额外创建 2 个 chunk(M=2)。

  • M 的值定义了可以同时丢失多少个 OSD 而不丢数据;
  • crush-failure-domain=rack 会创建一条 CRUSH rule,确保没有两个 chunk 存在同一个 rack 中。

编码过程示意(原文 ditaa 转述):

              name |       NYAN        |
           content |     ABCDEFGHI     |
                          |
                          v
                   +------+------+
   +---------------+ encode(3,2) +-----------+
   |               +--+--+---+---+           |
   |                  |  |   |               |
   |          +-------+  |   +-----+         |
   |          |          |         |         |
+--v---+   +--v---+   +--v---+  +--v---+  +--v---+
| NYAN |   | NYAN |   | NYAN |  | NYAN |  | NYAN |
+------+   +------+   +------+  +------+  +------+
shard      |  1   |   |  2   |   |  3   |  |  4   |  |  5   |
+------+   +------+   +------+  +------+  +------+
content    | ABC  |   | DEF  |   | GHI  |  | YXY  |  | QGC  |
+--+---+   +--+---+   +--+---+  +--+---+  +--+---+
   |          |          |         |         |
   v          v          v         v         v
 OSD1       OSD2       OSD3      OSD4      OSD5   (各在不同 rack)

7.4 Erasure code profile 命令与通用参数

命令

# 创建
ceph osd erasure-code-profile set {name} \
         [{directory=directory}] \
         [{plugin=plugin}] \
         [{stripe_unit=stripe_unit}] \
         [{key=value} ...] \
         [--force]

# 删除
ceph osd erasure-code-profile rm {name}

# 查看
ceph osd erasure-code-profile get {name}

# 列出所有 profile 名
ceph osd erasure-code-profile ls

通用(与 plugin 无关的)参数

参数 说明 类型 必填 默认值
{directory=directory} 加载 erasure code plugin 的目录名 String 否 /usr/lib/ceph/erasure-code
{plugin=plugin} 用哪个 erasure code plugin 来计算 coding chunk 并恢复缺失 chunk String 否 isa
{stripe_unit=stripe_unit} 每个 stripe 中,一个 data chunk 里放多少数据。例:一个 2 data chunk 且 stripe_unit=4K 的 profile 会把 0-4K 放进 chunk 0,4K-8K 放进 chunk 1,然后 8K-12K 又回到 chunk 0。为获得最佳性能,该值应为 4K 的倍数。默认值在创建 pool 时取自 monitor 配置选项 osd_pool_erasure_code_stripe_unit。使用该 profile 的 pool 的 stripe_width = data chunk 数 × 本 stripe_unit String 否 取自 osd_pool_erasure_code_stripe_unit
{key=value} 其余 key/value 对的语义由具体的 erasure code plugin 定义 String 否 —
--force 覆盖同名的已有 profile,并允许设置非 4K 对齐的 stripe_unit。⚠️ 覆盖已有 profile 是危险的,因此必须同时用 --yes-i-really-mean-it String 否 —

⚠️ 删除 profile 的一个坑

Removing an erasure code profile using osd erasure-code-profile rm does not automatically delete the associated CRUSH rule associated with the erasure code profile. It is recommended to manually remove the associated CRUSH rule using ceph osd crush rule remove {rule-name} to avoid unexpected behavior.

(若 profile 被某个 pool 引用,删除会失败。)

7.5 各 Plugin 逐项参数

7.5.1 jerasure

官方评价:The jerasure plugin is a generic and flexible plugin. However, the jerasure library is no longer maintained and has not been updated to support modern CPU instructions that can improve performance when encoding and decoding data.

(封装 Jerasure 库。建议阅读 jerasure 文档以理解参数。⚠️ 原文注:jerasure.org 网站截至 2023 年可能已不再与原始项目相关联,也不再是正当的。)

ceph osd erasure-code-profile set {name} \
  plugin=jerasure \
  k={data-chunks} \
  m={coding-chunks} \
  technique={reed_sol_van|reed_sol_r6_op|cauchy_orig|cauchy_good|liberation|blaum_roth|liber8tion} \
  [crush-root={root}] \
  [crush-failure-domain={bucket-type}] \
  [crush-device-class={device-class}] \
  [directory={directory}] \
  [--force]
参数 说明 类型 必填 默认值 / 示例
k 每个对象被拆成 data-chunks 份,每份存在不同 OSD 上 Integer 是 例 4
m 为每个对象计算 coding chunks 并存在不同 OSD 上。coding chunk 的数量 = 可以有多少个 OSD 挂掉而不丢数据 Integer 是 例 2
technique 见下方详述 String 否 reed_sol_van
packetsize 编码以 bytes 大小的包为单位进行。选对包大小很困难,jerasure 文档中有大量相关信息 Integer 否 2048
crush-root CRUSH rule 第一步使用的 crush bucket 名。例如 step take default String 否 default
crush-failure-domain 确保没有两个 chunk 落在具有相同故障域的 bucket 中。例:故障域为 host 时,不会有两个 chunk 存在同一 host。用于生成形如 step chooseleaf host 的 CRUSH rule step String 否 host
crush-device-class 把放置限制到特定 class 的设备(如 ssd 或 hdd),使用 CRUSH map 中的 crush device class 名 String 否 文档未明确(表格中未填)
directory 加载 plugin 的目录名 String 否 /usr/lib/ceph/erasure-code
--force 覆盖同名的已有 profile String 否 —

technique 的取值与特性:

technique 特性
reed_sol_van 最灵活:只需设置 k 和 m
cauchy_good 可能更快,但需要仔细选择 packetsize
reed_sol_r6_op、liberation、blaum_roth、liber8tion 都是 RAID6 等价物,含义是只能用 m=2 配置

⚠️ blaum_roth 的 w 坑(官方 note,重要): 使用 blaum_roth 编码时,默认字长 w=7 是次优的,因为 blaum_roth 在 w+1 为素数时工作最好。创建 technique=blaum_roth 的新 EC profile 时,应把 w 设为"比某个素数小 1 的数"(例如 6)。 背景:w 的默认值在源码中不容易改(见 Loic Dachary 的 commit f51d21b);限制本身见 Plank and Greenan 的 "Jerasure: A Library in C Facilitating Erasure Coding for Storage Applications" 第 29 页第二个要点。该信息由 Benjamin Mare 于 2024 年 9 月提供给 Ceph 上游。

7.5.2 isa(默认 plugin)

The isa plugin is the default for Ceph erasure coded pools. 封装 ISA(Intel Storage Acceleration Library)。

ceph osd erasure-code-profile set {name} \
  plugin=isa \
  technique={reed_sol_van|cauchy} \
  [k={data-chunks}] \
  [m={coding-chunks}] \
  [crush-root={root}] \
  [crush-failure-domain={bucket-type}] \
  [crush-device-class={device-class}] \
  [directory={directory}] \
  [--force]
参数 说明 类型 必填 默认值
k 每个对象被拆成 data-chunks 份,每份存在不同 OSD 上 Integer 否 7
m 计算 coding chunks 并存在不同 OSD 上。数量 = 可挂掉的 OSD 数而不丢数据 Integer 否 3
technique ISA plugin 有两种 Reed Solomon 形式:reed_sol_van = Vandermonde(范德蒙德);cauchy = Cauchy(柯西) String 否 reed_sol_van
crush-root CRUSH rule 第一步使用的 crush bucket 名。例 step take default String 否 default
crush-failure-domain 确保没有两个 chunk 落在具有相同故障域的 bucket 中。用于生成形如 step chooseleaf host 的 CRUSH rule step String 否 host
crush-device-class 把放置限制到特定 class 的设备(如 ssd / hdd) String 否 (源文 :Default: 后为空)
directory 加载 plugin 的目录名 String 否 /usr/lib/ceph/erasure-code
--force 覆盖同名的已有 profile String 否 —

💡 补充:注意 isa 的默认 k=7 m=3(空间开销 (7+3)/7 ≈ 1.43),而集群初始化时创建的 default profile 是 k=2 m=2(见 7.3)。"isa plugin 的默认 k/m" 与 "default profile 的 k/m" 是两个不同的东西,不要混淆。

7.5.3 lrc(Locally Repairable Code,本地可修复码)

要解决的问题(原文):

With the isa plugin, when an erasure coded object is stored on multiple OSDs, recovering from the loss of one OSD requires reading from k others. For instance if isa is configured with k=8 and m=4, recovering from the loss of one OSD requires reading from eight others.

lrc 的做法:创建本地 parity chunk,使得恢复只需更少的存活 OSD。例:k=8, m=4, l=4 时,会每 4 个 OSD 额外创建一个 parity chunk。当单个 OSD 丢失时,只需 4 个 OSD 就能恢复(而不是 8 个)。

ceph osd erasure-code-profile set {name} \
    plugin=lrc \
    k={data-chunks} \
    m={coding-chunks} \
    l={locality} \
    [crush-root={root}] \
    [crush-locality={bucket-type}] \
    [crush-failure-domain={bucket-type}] \
    [crush-device-class={device-class}] \
    [directory={directory}] \
    [--force]
参数 说明 类型 必填 默认值 / 示例
k 每个对象拆成 data-chunks 份,各存不同 OSD Integer 是 例 4
m 计算 coding chunks,各存不同 OSD。数量 = 可挂掉的 OSD 数而不丢数据 Integer 是 例 2
l(locality) 把 coding chunk 和 data chunk 分组,每组大小为 locality。例:k=4, m=2, locality=3 时会创建两个大小为 3 的组。每一组都可以在不读取其他组 chunk 的情况下被恢复 Integer 是 例 3
crush-root CRUSH rule 第一步使用的 crush bucket 名 String 否 default
crush-locality 由 l 定义的每一组 chunk 存放在什么类型的 CRUSH bucket 中。例:设为 rack 时,每组 l 个 chunk 会被放在不同的 rack 中。用于生成形如 step choose rack 的 CRUSH rule step。若不设置,则不进行此类分组 String 否 文档未明确(表格中未填)
crush-failure-domain 确保没有两个 chunk 落在具有相同故障域的 bucket 中。用于生成 step chooseleaf host 之类 String 否 host
crush-device-class 限制到特定 class 的设备 String 否 (源文 :Default: 后为空)
directory 加载 plugin 的目录名 String 否 /usr/lib/ceph/erasure-code
--force 覆盖同名的已有 profile String 否 —

约束:k 与 m 之和必须是 l 参数的整数倍("The sum of k and m must be a multiple of the l parameter")。

三个 profile 示例

① 减少 host 间的恢复带宽

ceph osd erasure-code-profile set LRCprofile \
   plugin=lrc \
   k=4 m=2 l=3 \
   crush-failure-domain=host
ceph osd pool create lrcpool erasure LRCprofile

原文评价:虽然"所有 host 都接在同一个交换机上"时这大概不是个有意思的用例,但带宽用量的降低确实是可观测的。

② 减少 rack 间的恢复带宽

ceph osd erasure-code-profile set LRCprofile \
   plugin=lrc \
   k=4 m=2 l=3 \
   crush-locality=rack \
   crush-failure-domain=host
ceph osd pool create lrcpool erasure LRCprofile

⚠️ In Firefly the bandwidth reduction will only be observed if the primary OSD is in the same rack as the lost chunk.

底层(low level)配置

底层配置参数不强制 "k+m 必须是 l 的倍数" 这个限制,在特定用途下反而有优势:

  • 可以定义两个组,一个 4 个 chunk、另一个 3 个 chunk;
  • 也可以递归地定义 locality 集合,例如 datacenter、以及 datacenter 内的 rack。

k/m/l 是通过生成一份底层配置来实现的。lrc plugin 递归地应用纠删码技术,使得大多数情况下从某些 chunk 的丢失中恢复只需要可用 chunk 的一个子集。

三层编码示例:

chunk nr    01234567
step 1      _cDD_cDD
step 2      cDDD____
step 3      ____cDDD

(c = 由 data chunk D 计算出的 coding chunk)

  • 丢失 chunk 7 → 可用最后四个 chunk 恢复;
  • 丢失 chunk 2 → 可用前四个 chunk 恢复。

底层 profile 示例:

# 最小测试:等价于 K=2 M=1(DD 意味着 K=2,c 意味着 M=1,默认用 isa plugin)
ceph osd erasure-code-profile set LRCprofile \
   plugin=lrc \
   mapping=DD_ \
   layers='[ [ "DDc", "" ] ]'
ceph osd pool create lrcpool erasure LRCprofile
# 减少 host 间恢复带宽:等价于 k=4, m=2, l=3,但 chunk 布局不同
$ ceph osd erasure-code-profile set LRCprofile \
     plugin=lrc \
     mapping=__DD__DD \
     layers='[
               [ "_cDD_cDD", "" ],
               [ "cDDD____", "" ],
               [ "____cDDD", "" ],
             ]'
$ ceph osd pool create lrcpool erasure LRCprofile
# 减少 rack 间恢复带宽
$ ceph osd erasure-code-profile set LRCprofile \
    plugin=lrc \
    mapping=__DD__DD \
    layers='[
              [ "_cDD_cDD", "" ],
              [ "cDDD____", "" ],
              [ "____cDDD", "" ],
            ]' \
    crush-steps='[
                    [ "choose", "rack", 2 ],
                    [ "chooseleaf", "host", 4 ],
                   ]'

$ ceph osd pool create lrcpool erasure LRCprofile

⚠️ In Firefly the reduced bandwidth will only be observed if the primary OSD is in the same rack as the lost chunk.

指定每层的 EC 后端:layers='[ [ "DDc", "" ] ]' 中的第二个参数实际上就是用于该层的 erasure code profile。

# LRC 默认使用 ISA 作为 EC 后端;可逐层指定
ceph osd erasure-code-profile set LRCprofile \
   plugin=lrc \
   mapping=DD_ \
   layers='[ [ "DDc", "plugin=jerasure technique=cauchy" ] ]'
ceph osd pool create lrcpool erasure LRCprofile
# 每层用不同 profile
$ ceph osd erasure-code-profile set LRCprofile \
     plugin=lrc \
     mapping=__DD__DD \
     layers='[
               [ "_cDD_cDD", "plugin=isa technique=cauchy" ],
               [ "cDDD____", "plugin=isa" ],
               [ "____cDDD", "plugin=jerasure" ],
             ]'
$ ceph osd pool create lrcpool erasure LRCprofile

原文明确:LRC now uses ISA as the default EC backend.

编解码算法(原文逐步推演)

以 4K 对象、上述三层为例:

编码:

  1. step 1:对象先被分成四个 1K chunk(四个大写 D),按序存在 chunk 2、3、6、7。由此计算出两个 coding chunk(两个小写 c),分别存在 chunk 1 和 5。
  2. step 2:以类似方式复用 step 1 创建的内容,在位置 0 存一个 coding chunk c。最后四个 chunk(用 _ 标记便于阅读)被忽略。
  3. step 3:在位置 4 存一个 coding chunk c。这个 coding chunk 是用 step 1 创建的三个 chunk 算出来的 —— 即 step 1 的 coding chunk 在 step 3 中变成了 data chunk。

解码(以丢失 chunk 2 为例):

chunk nr    01234567

step 1      _c D_cDD
step 2      cD D____
step 3      __ _cDDD

解码会按相反顺序遍历各 step:step 3 → step 2 → step 1。

  1. step 3 对 chunk 2 一无所知(是下划线),被跳过;
  2. step 2 的 coding chunk(存在 chunk 0)允许恢复 chunk 2 的内容。没有更多 chunk 需要恢复,过程停止,不再考虑 step 1。
  3. 恢复 chunk 2 需要读取 chunk 0、1、3,并写回 chunk 2。

解码(丢失 chunk 2、3、6):

chunk nr    01234567

step 1      _c  _c D
step 2      cD  __ _
step 3      __  cD D
  1. step 3 可以恢复 chunk 6 的内容 → 状态变为:
chunk nr    01234567

step 1      _c  _cDD
step 2      cD  ____
step 3      __  cDDD
  1. step 2 恢复失败并被跳过,因为缺两个 chunk(2、3),而它只能从一个缺失 chunk 中恢复;
  2. step 1 的 coding chunk(存在 chunk 1、5)允许恢复 chunk 2、3 的内容 → 回到完整状态。
控制 CRUSH 放置

默认 CRUSH rule 提供位于不同 host 上的 OSD。例如上例需要恰好 8 个 OSD(每个 chunk 一个)。若这些 host 位于两个相邻 rack,可以把前四个 chunk 放在第一个 rack、后四个放在第二个 rack,这样恢复单个 OSD 就不需要跨 rack 带宽。

crush-steps='[ [ "choose", "rack", 2 ], [ "chooseleaf", "host", 4 ] ]'

→ 这条规则会选两个 rack 类型的 crush bucket,并为其中每个选四个 OSD,且这些 OSD 各自位于不同的 host 类型 bucket 中。

原文:The CRUSH rule can also be manually crafted for finer control.(也可以手工编制 CRUSH rule 以获得更精细的控制。)

7.5.4 shec(Shingled Erasure Code)

The shec plugin encapsulates the multiple SHEC library. It allows ceph to recover data more efficiently than Reed Solomon codes.

ceph osd erasure-code-profile set {name} \
     plugin=shec \
     [k={data-chunks}] \
     [m={coding-chunks}] \
     [c={durability-estimator}] \
     [crush-root={root}] \
     [crush-failure-domain={bucket-type}] \
     [crush-device-class={device-class}] \
     [directory={directory}] \
     [--force]
参数 说明 类型 必填 默认值
k 每个对象拆成 data-chunks 份,各存不同 OSD Integer 否 4
m 计算 coding-chunks 并存在不同 OSD 上。⚠️ coding-chunk 的数量不一定等于"可以挂掉的 OSD 数而不丢数据" Integer 否 3
c 每个 data chunk 被包含在多少个 parity chunk 的计算范围内。这个数字被用作 durability estimator(持久性估计量)。例:c=2 时可以有 2 个 OSD 挂掉而不丢数据 Integer 否 2
crush-root CRUSH rule 第一步使用的 crush bucket 名 String 否 default
crush-failure-domain 确保没有两个 chunk 落在具有相同故障域的 bucket 中。生成 step chooseleaf host 之类 String 否 host
crush-device-class 限制到特定 class 的设备 String 否 (源文 :Default: 后为空)
directory 加载 plugin 的目录名 String 否 /usr/lib/ceph/erasure-code
--force 覆盖同名的已有 profile String 否 —

SHEC 的三个设计维度:

维度 定义 调节方式
Space Efficiency(空间效率) data chunk 与对象中所有 chunk 之比,表示为 k / (k + m) 提高 k 或降低 m 可改善。例:SHEC(4,3,2) 的空间效率 = 4/(4+3) = 0.57;SHEC(5,3,2) 或 SHEC(4,2,2) 都能改善它
Durability(持久性) 第三个参数 c 是 durability estimator,近似等于"可挂掉的 OSD 数而不丢数据" 例:SHEC(4,3,2) 的 durability estimator = 2
Recovery Efficiency(恢复效率) 原文明确:描述其计算方法超出本文档范围,但至少**"增加 m 而不增加 c"可以改善恢复效率**(代价是牺牲空间效率) 例:SHEC(4,2,2) → SHEC(4,3,2) 即改善恢复效率

示例:

ceph osd erasure-code-profile set SHECprofile \
   plugin=shec \
   k=8 m=4 c=3 \
   crush-failure-domain=host
ceph osd pool create shecpool erasure SHECprofile

7.5.5 clay(Coupled-Layer,耦合层码)

设计目标:在一个故障 node / OSD / rack 被修复时,显著节省网络带宽和磁盘 IO。

定义 d = 修复期间联系到的 OSD 数量。

  • 若 jerasure 配置为 k=8, m=4,丢失一个 OSD 需要从 d=8 个其他 OSD 读取来修复。恢复 1 GiB 需要下载 8 × 1 GiB = 8 GiB。
  • 而 clay 的 d 是可配置的,范围是 k+1 <= d <= k+m-1。
  • 默认情况下 clay 取 d = k+m-1,因为它在网络带宽和磁盘 IO 上节省最多。
  • 例:k=8, m=4, d=11 时,单个 OSD 故障会联系 d=11 个 OSD,每个下载 250 MiB,总共下载 11 × 250 MiB = 2.75 GiB。

磁盘 IO 对比公式:

plugin 总磁盘 IO 量
jerasure、isa k S
clay d S / (d - k + 1) = (k + m - 1) S / m

(S = 正在修复的单个 OSD 上存储的数据量。表中用了 最大的 d,因为这会带来最少的下载量。)

ceph osd erasure-code-profile set {name} \
     plugin=clay \
     k={data-chunks} \
     m={coding-chunks} \
     [d={helper-chunks}] \
     [scalar_mds={plugin-name}] \
     [technique={technique-name}] \
     [crush-failure-domain={bucket-type}] \
     [crush-device-class={device-class}] \
     [directory={directory}] \
     [--force]
参数 说明 类型 必填 默认值 / 示例
k 每个对象拆成 data-chunks 份,各存不同 OSD Integer 是 例 4
m 计算 coding chunks,各存不同 OSD。数量 = 可挂掉的 OSD 数而不丢数据 Integer 是 例 2
d 恢复单个 chunk 时请求发送数据的 OSD 数量。d 必须满足 k+1 <= d <= k+m-1。d 越大,节省越多 Integer 否 k+m-1
scalar_mds 指定在分层构造中用作构建块的 plugin,可以是 jerasure、isa、shec String 否 jerasure
technique 指定在 scalar_mds plugin 内选择哪种 technique。支持的技术:jerasure 支持 reed_sol_van、reed_sol_r6_op、cauchy_orig、cauchy_good、liber8tion;isa 支持 reed_sol_van、cauchy;shec 支持 single、multiple String 否 reed_sol_van(对 jerasure、isa)/single(对 shec)
crush-root CRUSH rule 第一步使用的 crush bucket 名 String 否 default
crush-failure-domain 确保没有两个 chunk 落在具有相同故障域的 bucket 中 String 否 host
crush-device-class 限制到特定 class 的设备 String 否 (源文 :Default: 后为空)
directory 加载 plugin 的目录名 String 否 /usr/lib/ceph/erasure-code
--force 覆盖同名的已有 profile String 否 —

💡 补充:注意 clay 的命令模板里没有列出 crush-root,但参数表里列了 crush-root(默认 default)。以参数表为准。

示例:

ceph osd erasure-code-profile set CLAYprofile \
   plugin=clay \
   k=4 m=2 d=5 \
   crush-failure-domain=host
ceph osd pool create claypool erasure CLAYprofile
sub-chunk 概念

Clay 能节省磁盘 IO 和网络带宽,因为它是向量码(vector code),能以更细的粒度(称为 sub-chunk)查看和操作 chunk 内的数据。

  • 一个 chunk 内的 sub-chunk 数量 = q^((k+m)/q),其中 q = d - k + 1;
  • 修复 OSD 时,从可用 OSD 请求的 helper 信息只是 chunk 的一小部分。确切地说,修复期间被访问的 sub-chunk 数量 = sub-chunk count / q。

例子:

配置 sub-chunk count repair sub-chunk count 效果
k=4, m=2, d=5 8 4 修复时只读半个 chunk
k=8, m=4, d=11 64 16 修复时从每个可用 OSD 读四分之一个 chunk
如何按负载选配置

由于只读 chunk 中的少数 sub-chunk,且这些 sub-chunk 在 chunk 内不一定是连续存储的,而读连续数据对磁盘 IO 性能最有利 —— 因此建议选择使 sub-chunk size 足够大的 stripe-size。

对给定的 stripe-size(由负载决定),选择 k、m、d 使得:

sub-chunk size = stripe-size / (k × sub-chunk count) = 4KB, 8KB, 12KB ...
负载 建议
大尺寸负载(stripe size 大) 容易选 k、m、d。例:stripe-size = 64MB,选 k=16, m=4, d=19 → sub-chunk count = 1024,sub-chunk size = 4KB
小尺寸负载 k=4, m=2 是个好配置,同时提供网络和磁盘 IO 收益
与 LRC 的对比
LRC CLAY
设计重心 让修复时联系到的 OSD 数(d)最小 让总下载量/磁盘 IO 最小
存储开销 (m + (k+m)/d) / k(在 m 个 parity 之外还要存 (k+m)/d 个 parity) m / k
可恢复的故障数 任意 m 个 OSD 任意 m 个 OSD

对比表(原文):

参数 LRC:磁盘 IO, 存储开销 CLAY:磁盘 IO, 存储开销
(k=10, m=4) 7 * S, 0.6(d=7) 3.25 * S, 0.4(d=13)
(k=16, m=4) 4 * S, 0.5625(d=4) 4.75 * S, 0.25(d=19)

(S = 被恢复的单个 OSD 上存储的数据量。)

读法提示:LRC 在 (k=16,m=4) 时的磁盘 IO(4S) 比 CLAY(4.75S)更小,但存储开销(0.5625) 远大于 CLAY(0.25)。两者都能从任意 m 个 OSD 故障中恢复。 选哪个取决于你更在意磁盘 IO 还是容量。

7.6 EC 与副本的对比与取舍(原文观点汇总)

维度 副本(replicated) 纠删码(erasure)
数据保护方式 每个对象被复制到多个磁盘 数据拆成 data chunk + coding chunk,用 parity 重建
原始空间开销(overhead factor) size=3 → 3.0 (k+m)/k。例 k=4,m=2 → 1.5(1 GiB 用户数据占 1.5 GiB 底层存储)
可用容量 给定原始存储,可用容量较少 通常更多
适用组件 所有 Ceph 组件与用例 仅部分组件与用例(例如不支持 omap)
性能 — 有显著的性能折衷,尤其在使用 HDD 以及执行集群 recovery / backfill 时
故障域要求 — 通常需要比复制更多的故障域
重叠故障容忍 — 可能容忍更多的重叠驱动器/主机故障
资源消耗 — 比副本池需要更多资源
部分写(partial write) 支持 默认不支持(仅支持整对象写和 append,例如 RGW);见 7.7
omap 支持 不支持

⚠️ 原文警句:Do not mistake erasure coding for a free lunch(别把纠删码当成免费的午餐)—— 有显著的性能折衷,尤其在使用 HDD 和执行 recovery / backfill 时。

7.7 Erasure Coding with Overwrites(部分写)

默认行为:纠删码池只能配合执行"整对象写入和追加"的操作(例如 RGW)。

Luminous 起,可以为纠删码池通过 per-pool 设置启用部分写,这让 RBD 和 CephFS 能把数据存在纠删码池中:

ceph osd pool set ec_pool allow_ec_overwrites true

硬性前提与限制:

限制 说明
必须在 BlueStore OSD 上 只能在位于 BlueStore OSD 的 pool 上启用,因为 BlueStore 的 checksumming 会在 deep scrub 期间被用来检测 bitrot 或其它损坏
不要配 Filestore "Using Filestore with EC overwrites is not only unsafe, but it also results in lower performance compared to BlueStore." 且 Filestore 已废弃,集群中任何 Filestore OSD 都应迁移到 BlueStore
** omap 限制** 纠删码池不支持 omap,因此 RBD 和 CephFS 必须把数据存在 EC 池、元数据存在副本池

RBD 用法(把 EC 池作为 --data-pool):

rbd create --size 1G --data-pool ec_pool replicated_pool/image_name

CephFS 用法:可以在创建文件系统时把纠删码池设为默认 data pool,或通过 file-layouts 设置。

7.8 Erasure Coding Optimizations(Tentacle 新增)

Since Tentacle, an erasure-coded pool may have optimizations enabled with a per-pool setting. This improves performance for smaller I/Os and eliminates padding which can save capacity.

ceph osd pool set ec_pool allow_ec_optimizations true

收益与适用面

场景 收益
RBD / CephFS 优化会让 EC 池更适合与它们配合使用
RGW:大量极小对象,或对象的小随机读 会看到性能和容量收益
RGW:大对象、顺序读写 收益甚微

关键约束

约束 说明
可后加,但不可关闭 该 flag 可以为已有 pool 启用;但一旦启用就不能禁用,因为它改变了新数据的存储方式
全局默认值 可通过 central configuration 选项 osd_pool_default_flag_ec_optimizations 配置成新建 pool 的默认行为
升级要求 除非所有 Monitor 和 OSD 都已升级到 Tentacle 或更高版本,否则该 flag 不能被设置。⚠️ 但优化启用后,Gateway 和 Client 不升级也可以使用
plugin / technique 限制 目前仅支持 Jerasure 和 ISA-L plugin 配合 reed_sol_van technique(这是旧的和当前的默认,也是最广泛使用的 plugin 和 technique)。为 plugin/technique 组合不受支持的 pool 设置该 flag 会被报错阻止

stripe unit 调优(重要)

项 说明
默认 stripe unit 4K,对标准 EC 池工作良好
启用优化后推荐 对大多数 I/O 负载,建议把 stripe unit 提升到至少 16K。性能测试表明 16K 是通用 I/O 负载的最佳选择
提升的收益/代价 显著提升小读性能,但会略微降低小顺序写的性能
读密集型负载 可以取更大值,最大到 256KB,进一步改善读性能,但会进一步降低小顺序写性能
超过 256KB 不太可能有任何性能收益
设置方式 stripe unit 是 pool create-time 选项,可在 EC profile 中设置,或通过 central configuration 选项 osd_pool_erasure_code_stripe_unit 设置
⚠️ 不可事后更改 stripe unit 在 pool 创建之后不能更改 —— 因此若对已有 pool 启用优化,你无法获得优化的全部收益

优化对 k / m 选择的影响

条件 影响
未启用优化 k+m 的选择影响性能。k 和 m 越大,性能越低
启用优化后 随 k 增大只有非常轻微的性能下降 → 使用更高的 k 变得更可行
m 的影响(两种情况) 增大 m 仍然影响写性能,尤其是小写 → 对块和文件负载,建议 m 不大于 3

7.9 EC 池的开销与限制

开销公式与选型建议

开销因子(空间放大)= (k+m) / k。

  • k=4, m=2 → 开销 1.5(1 GiB 用户数据用 1.5 GiB 底层存储);
  • 对比默认 size=3 复制 → 开销 3.0。

选型建议(原文,重点):

  1. 随 k 增大超过 4,容量开销的边际收益很快递减,但性能影响成比例增长。 → 建议:除非你充分理解其后果(包括集群拓扑呈现的故障域数量),否则不要选择 k > 4 或 m > 2 的 profile。
  2. 若选 m=1,预期在维护期间会出现数据不可用,在组件故障重叠时会出现数据丢失。 → m=1 的 profile 强烈不建议用于生产数据。
  3. 必须在大量重叠组件故障中仍保持活跃且不丢数据的部署,可能倾向于 m > 2。 → 注意这类 profile 会降低空间效率、降低性能,尤其在 backfill 和 recovery 期间。
  4. 如果确定要用 EC 但不确定用哪个 profile,选 k=4 和 m=2。 → 可获得相比 size=3 复制翻倍的可用空间,且写和恢复性能影响相对可容忍。

EC 开销因子全表(原文 list-table 完整复现)

读法:行 = k,列 = m,单元格 = (k+m)/k 开销因子。

m=1 m=2 m=3 m=4 m=5 m=6 m=7 m=8 m=9 m=10 m=11
k=1 2.00 3.00 4.00 5.00 6.00 7.00 8.00 9.00 10.00 11.00 12.00
k=2 1.50 2.00 2.50 3.00 3.50 4.00 4.50 5.00 5.50 6.00 6.50
k=3 1.33 1.67 2.00 2.33 2.67 3.00 3.33 3.67 4.00 4.33 4.67
k=4 1.25 1.50 1.75 2.00 2.25 2.50 2.75 3.00 3.25 3.50 3.75
k=5 1.20 1.40 1.60 1.80 2.00 2.20 2.40 2.60 2.80 3.00 3.20
k=6 1.16 1.33 1.50 1.66 1.83 2.00 2.17 2.33 2.50 2.66 2.83
k=7 1.14 1.29 1.43 1.58 1.71 1.86 2.00 2.14 2.29 2.43 2.58
k=8 1.13 1.25 1.38 1.50 1.63 1.75 1.88 2.00 2.13 2.25 2.38
k=9 1.11 1.22 1.33 1.44 1.56 1.67 1.78 1.88 2.00 2.11 2.22
k=10 1.10 1.20 1.30 1.40 1.50 1.60 1.70 1.80 1.90 2.00 2.10
k=11 1.09 1.18 1.27 1.36 1.45 1.54 1.63 1.72 1.82 1.91 2.00
k=12 1.08 1.17 1.25 1.33 1.42 1.50 1.58 1.67 1.75 1.83 1.92
k=20 1.05 1.10 1.15 1.20 1.25 1.30 1.35 1.40 1.45 1.50 1.55

故障域数量要求(原文 note)

Most erasure-coded pool deployments require at least k+m CRUSH failure domains, which in most cases means racks or hosts.

  • 将 EC profile 和集群拓扑规划成至少有 k+m+1 个故障域有运维上的好处;
  • 大多数情况下不鼓励 k > 8。

小文件场景的空间放大警告(原文 note)

CephFS 和 RGW 部署中,若有相当比例是非常小的用户文件/对象,应仔细规划 —— 纠删码数据池会导致相当大的额外空间放大。

应对思路(原文给的 RGW 示例):CephFS 和 RGW 都支持多个具有不同介质、性能和数据保护策略的数据池。一个 RGW 部署可以:

  • 配置数量适中的 TLC SSD,由副本模式的 index 和默认 bucket 数据池使用;
  • 配置更大规模的纠删码 QLC SSD 或 HDD,通过 storage class、placement target 或 Lua 脚本把更大、更冷的对象导向它们。

与 cache tiering 的关系(已废弃)

纠删码池比副本池需要更多资源,且缺少副本池支持的部分功能(例如 omap)。为克服这些限制,过去可以在 EC 池前设置一个 cache tier。

⚠️ Cache tiering was deprecated in Reef. 官方强烈建议不要部署新的 cache tier,并努力从已有部署中移除。

7.10 EC 池的恢复(Recovery)

  • 若纠删码池丢失了任何 data shard,必须从其他 shard 恢复;
  • 恢复过程包括:从剩余 shard 读取 → 重建数据 → 写入新 shard。

版本行为差异:

版本 恢复门槛
Octopus 及以后 只要有至少 K 个 shard 可用,纠删码池就可以恢复(少于 K 个 shard 时,你实际上是丢数据了)
Octopus 之前 即使 min_size 大于 K,也要求至少 min_size 个 shard 可用。这是设计新 pool 模式时出于充分谨慎(abundance of caution)做出的保守决定。其后果是:丢失了 OSD 但数据并未完全丢失的 pool,无法恢复并进入 active,必须人工介入临时修改 min_size

官方建议:

We recommend that min_size be K+1 or greater to prevent loss of writes and loss of data.

7.11 EC 术语表(官方 Glossary)

术语 定义
chunk 调用编码函数时,它返回大小彼此相同的 chunk。有两类:(1) data chunk,拼接起来即可重建原始对象;(2) coding chunk,可用于重建丢失的 chunk
K 对象被分成的 data chunk 数量。例:K = 2 时,一个 10KB 对象被分成两个 5KB 的对象
M 编码函数计算出的 coding chunk 数量。M 等于集群中可以有几个 OSD 缺失而不遭受数据丢失。例:有两个 coding chunk 时,可以缺失两个 OSD 而不丢数据

引自:Erasure code、Erasure code profiles、Jerasure erasure code plugin、ISA erasure code plugin、Locally repairable erasure code plugin、SHEC erasure code plugin、CLAY code plugin


八、存储引擎:BlueStore、FileStore 与 Journal

8.1 OSD Back Ends 概览

后端 状态
BlueStore Luminous 12.2.z 起成为默认(且推荐)后端
FileStore Luminous 之前是默认(且唯一)后端。⚠️ Filestore has been deprecated in the Reef release and is no longer supported

8.2 BlueStore

定位

BlueStore 是专为 Ceph OSD 工作负载管理磁盘数据而设计的专用存储后端,其设计基于支持和运维 Filestore OSD 十年的经验。

六大关键特性(官方列举)

特性 说明
直接管理存储设备 BlueStore 消费裸块设备或分区。这避免了中间抽象层(例如 XFS 这样的本地文件系统)限制性能或增加复杂度
用 RocksDB 管理元数据 内嵌 RocksDB 的 key/value 数据库来管理内部元数据,包括对象名到磁盘上块位置的映射
全量数据和元数据校验 默认情况下,所有写入 BlueStore 的数据和元数据都被一个或多个校验和保护。任何数据或元数据从磁盘读出或返回给用户之前,都必须通过验证
内联压缩 数据在写入磁盘前可以选择性地被压缩
多设备元数据分层 BlueStore 允许把它的内部 journal(write-ahead log)写到独立的高速设备(如 SSD、NVMe 或 NVDIMM)以提升性能。若有相当数量的更快存储可用,内部元数据也可以存在更快的设备上
高效的 copy-on-write RBD 和 CephFS 的快照依赖 copy-on-write 的 clone 机制,BlueStore 中该机制被高效实现。这让常规快照和纠删码池(依赖 clone 来实现高效的 two-phase commit)都有高效的 I/O

8.3 BlueStore 的设备模型

三种设备

BlueStore 管理一个、两个、或在某些情况下三个存储设备。这里的 "device" 是 Linux/Unix 意义上的设备 —— 列在 /dev 或 /devices 下的资产。每个设备可以是整块存储盘、一个分区、或一个逻辑卷。

⚠️ BlueStore does not create or mount a conventional file system on devices that it uses; BlueStore 以 "raw" 方式直接读写这些设备。

设备 标识 说明
primary device(主设备) data 目录中的 block 符号链接 最简单的情况下,BlueStore 占用单个存储设备的全部空间
WAL device(write-ahead log 设备) block.wal 用于把 BlueStore 的**内部 journal(write-ahead log)**分离出来。只有在 WAL 设备比主设备更快时才有优势(例:WAL 是 SSD,主设备是 HDD)
DB device block.db 用于存储 BlueStore 的内部元数据。BlueStore(更准确地说是内嵌的 RocksDB)会尽可能把元数据放到 DB 设备上以提升性能。⚠️ 若 DB 设备写满,元数据会溢出(spill back)回主设备(即没有 DB 设备时它本该在的地方)。同样,只有在比主设备更快时配置 DB 设备才有优势

data directory(数据目录) 是一个 tmpfs 挂载点。当它被 ceph-volume 启动或激活时,会被填入元数据文件和链接,保存该 OSD 的信息:例如 OSD 的标识符、所属集群名、OSD 的 private keyring。

WAL vs DB 的选择策略(原文决策树)

可用高速存储量 建议
很少(例如不足 1 GB) 用作 WAL device
更多 配置 DB device 更有意义

为什么更多空间时应该选 DB 而不是 WAL:

Because the BlueStore journal is always placed on the fastest device available, using a DB device provides the same benefit that using a WAL device would, while also allowing additional metadata to be stored off the primary device (provided that it fits).

原理:当指定了 DB device 但没有显式指定 WAL device 时,WAL 会隐式地与 DB 一起共存(colocated)在更快的设备上。

部署命令

# 单设备(colocated)BlueStore OSD
ceph-volume lvm prepare --bluestore --data <device>

# 指定 WAL / DB 设备
ceph-volume lvm prepare --bluestore --data <device> --block.wal <wal-device> --block.db <db-device>

⚠️ --data 参数可以接受以下任一形式:以 vg/lv 记法指定的逻辑卷、已存在的逻辑卷、GPT 分区。

8.4 BlueStore 的两种常见部署形态

block (data) only(所有设备同类型)

适用场景:所有设备类型相同(例如都是 HDD),且没有可用于存元数据的快速设备。

做法:只指定 block 设备,把 block.db 和 block.wal 保持不分离。

# 单个 /dev/sda 设备
ceph-volume lvm create --bluestore --data /dev/sda

# 若用预创建的逻辑卷 ceph-vg/block-lv
ceph-volume lvm create --bluestore --data ceph-vg/block-lv

block 和 block.db(混合快慢设备)

适用场景:混合了快速与慢速设备(例如 SSD 或 HDD)。

做法:推荐把 block.db 放在更快的设备上,而 block(即数据)放在较慢的设备(即旋转盘)上。

⚠️ 必须手工创建这些 volume group 和 logical volume —— ceph-volume 工具目前无法自动创建它们。

完整示例(原文,4 个 HDD + 1 个 SSD):

# 1. 创建 volume groups(4 块旋转盘 sda/sdb/sdc/sdd)
vgcreate ceph-block-0 /dev/sda
vgcreate ceph-block-1 /dev/sdb
vgcreate ceph-block-2 /dev/sdc
vgcreate ceph-block-3 /dev/sdd

# 2. 为 block 创建 logical volumes
lvcreate -l 100%FREE -n block-0 ceph-block-0
lvcreate -l 100%FREE -n block-1 ceph-block-1
lvcreate -l 100%FREE -n block-2 ceph-block-2
lvcreate -l 100%FREE -n block-3 ceph-block-3

# 3. 假设 /dev/sdx 上有一块 200GB SSD,创建 4 个 50GB 的 LV
vgcreate ceph-db-0 /dev/sdx
lvcreate -L 50GB -n db-0 ceph-db-0
lvcreate -L 50GB -n db-1 ceph-db-0
lvcreate -L 50GB -n db-2 ceph-db-0
lvcreate -L 50GB -n db-3 ceph-db-0

# 4. 创建 4 个 OSD
ceph-volume lvm create --bluestore --data ceph-block-0/block-0 --block.db ceph-db-0/db-0
ceph-volume lvm create --bluestore --data ceph-block-1/block-1 --block.db ceph-db-0/db-1
ceph-volume lvm create --bluestore --data ceph-block-2/block-2 --block.db ceph-db-0/db-2
ceph-volume lvm create --bluestore --data ceph-block-3/block-3 --block.db ceph-db-0/db-3

结果:4 个 OSD,block 在 4 块 HDD 上,每块 HDD 在共享的 SSD 上有一个 50GB 的 logical volume(即 DB 设备)。

8.5 Sizing:block.db 该多大

总原则:使用混合旋转盘+固态盘配置时,为 BlueStore 创建足够大的 block.db logical volume 很重要。block.db 对应的 logical volume 应尽可能大。

场景 建议 block.db 大小
通用 block 大小的 1% ~ 4% 之间
RGW 负载 至少 block 大小的 4% —— 因为 RGW 大量使用 block.db 存元数据(尤其是 omap keys)。
例:block 为 1TB 时,block.db 至少 40GB
RBD 负载 通常不需要超过 block 大小的 1% ~ 2%

RocksDB level size 的历史包袱与现状

老版本:内部 level size 使得 DB 只能充分利用那些对应 L0、L0+L1、L1+L2 ... 之和的特定分区/逻辑卷大小 —— 即在默认设置下大约 3GB、30GB、300GB 等等。大多数部署不会因为把容量做到容纳 L3 及以上而实质受益,不过 把上述数字翻倍(6GB、60GB、600GB)有助于 DB compaction。

新版本:Nautilus 14.2.12、Octopus 15.2.6 及后续版本的改进,使得任意大小的 DB 设备都能被更好地利用。此外 Pacific 带来了实验性的 dynamic-level 支持。

💡 建议(基于原文):由于这些进步,老版本用户可能希望现在就预置更大的 DB 设备,以便未来升级时能享受到规模化的收益。

单一设备类型时

不使用快慢混合设备时,没有必要 为 block.db 或 block.wal 创建独立的 logical volume —— BlueStore 会自动把它们与 block 共存(colocate)在 block 的空间内。

8.6 Automatic Cache Sizing(自动缓存大小)

启用条件(两者都要满足):

  1. TCMalloc 必须被配置为内存分配器(memory allocator);
  2. bluestore_cache_autotune 配置选项必须启用(⚠️ 原文注:note that it is currently enabled by default —— 当前默认是启用的)。

工作机制:

  • BlueStore 尝试把 OSD 堆内存使用保持在某个目标大小之下(该目标由 osd_memory_target 决定);
  • 这是一种 best-effort 算法;
  • 缓存不会缩到小于 osd_memory_cache_min 所定义的大小;
  • 缓存比率按优先级层次结构选择;但如果优先级信息不可用,则用 bluestore_cache_meta_ratio 和 bluestore_cache_kv_ratio 的值作为兜底(fallback)缓存比率。

相关配置项

配置项 类型 默认值 含义 调参建议
bluestore_cache_autotune 文档未明确 启用(源文:"it is currently enabled by default") 是否启用 BlueStore 缓存自动调优 💡 保持默认开启;只在需要精确控制缓存布局时才关掉并改用手工 sizing
osd_memory_target 文档未明确 4_G(= 4294967296 字节 = 4 GiB)(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 自动缓存调优试图把 OSD 堆内存使用保持在其下的目标大小 💡 容器化部署时尤其要注意与 cgroup memory limit 对齐,避免 OOM
bluestore_cache_autotune_interval 文档未明确 5(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 自动调优的调整间隔 💡 一般不动;若观察到缓存频繁抖动可考虑放慢
osd_memory_base 文档未明确 768_M(= 805306368 字节 = 768 MiB)(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 计算内存目标时的基线/基础内存 💡 一般不动
osd_memory_expected_fragmentation 文档未明确 0.15(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 计算内存目标时预期为内存碎片预留的比例 💡 TCMalloc 碎片率明显高于预期时可调
osd_memory_cache_min 文档未明确 128_M(= 134217728 字节 = 128 MiB)(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 缓存收缩的下限 —— 自动调优时 caches do not shrink smaller than this 💡 若发现缓存被压得过小导致命中率下降,可适当抬高
osd_memory_cache_resize_interval 文档未明确 1(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 缓存 resize 的间隔 💡 一般不动

8.7 Manual Cache Sizing(手工缓存大小)

内存预算的决定方式:

  • 每个 OSD 用于 BlueStore cache 的内存量由 bluestore_cache_size 决定;
  • 若该选项未指定(即保持为 0),Ceph 用另一个配置选项决定默认内存预算:
    • 主设备是 HDD → bluestore_cache_size_hdd
    • 主设备是 SSD → bluestore_cache_size_ssd

⚠️ 原文提醒:除了配置的缓存大小之外,还有 OSD 自身消耗的内存,以及内存碎片和其它分配器开销带来的额外占用。BlueStore 和 Ceph OSD 守护进程的其他部分会尽一切努力在这个内存预算内工作。

这段缓存内存可用于存放三类东西:

  1. Key/Value 元数据(即 RocksDB 的内部缓存);
  2. BlueStore 元数据;
  3. BlueStore 数据(即最近读或最近写的对象数据)。

分配规则:

  • 缓存内存使用由 bluestore_cache_meta_ratio 和 bluestore_cache_kv_ratio 控制;
  • 为数据保留的那部分缓存 由「有效 BlueStore 缓存大小(取决于相应的 bluestore_cache_size[_ssd|_hdd] 选项和主设备的 device class)」以及「meta 与 kv 比率」共同决定;
  • 数据部分的计算公式(官方给出):
<effective_cache_size> × (1 - bluestore_cache_meta_ratio - bluestore_cache_kv_ratio)

相关配置项

配置项 类型 默认值 含义 调参建议
bluestore_cache_size 文档未明确 0(默认值取自 src/common/options/global.yaml.in @ v20.2.4;源文:若未指定即保持 0,则用 _hdd / _ssd 变体) 每个 OSD 用于 BlueStore cache 的内存量 💡 设为 0 让 Ceph 按设备类型自动选;需要精确控制时再显式设
bluestore_cache_size_hdd 文档未明确 1_G(= 1073741824 字节 = 1 GiB)(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 主设备为 HDD 时的默认缓存内存预算 💡 HDD OSD 通常给较小值(元数据访问慢,但 RAM 也少)
bluestore_cache_size_ssd 文档未明确 3_G(= 3221225472 字节 = 3 GiB)(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 主设备为 SSD 时的默认缓存内存预算 💡 NVMe OSD 可适当给大以提升命中率
bluestore_cache_meta_ratio 文档未明确 0.45(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 缓存中留给 BlueStore 元数据的比例 💡 与 kv_ratio 之和应 < 1,剩下的才是 data 部分(见上方公式)
bluestore_cache_kv_ratio 文档未明确 0.45(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 缓存中留给 RocksDB KV 的比例 💡 RGW 这类 omap 重负载可适当提高

8.8 Checksums(校验和)

覆盖范围:BlueStore 对写入磁盘的所有元数据和所有数据都做校验和。

对象 由谁做 算法
元数据 RocksDB crc32c
数据 BlueStore crc32c / xxhash32 / xxhash64

crc32c 是默认校验和算法,且适用于大多数用途。

校验和的元数据开销:

  • 全量数据校验和增加了 BlueStore 必须存储和管理的元数据量;
  • 只要有可能(例如客户端提示数据是顺序写入和读取的),BlueStore 会校验更大的块;
  • 但在很多情况下,它必须为每 4 KB 数据块存一个校验和值(通常 4 字节)。

缩短校验和以省元数据(有代价):

校验和长度 随机错误未被检出的概率
32-bit(4 字节) 约 四十亿分之一
16-bit(2 字节,crc32c_16) 1 / 65,536
8-bit(1 字节,crc32c_8) 1 / 256

想用更短的校验和值,就把校验和算法选成 crc32c_16 或 crc32c_8。

设置方式:校验和算法可以通过 per-pool 的 csum_type 配置选项指定,也可以通过全局配置选项指定:

ceph osd pool set <pool-name> csum_type <algorithm>
配置项 类型 默认值 含义 调参建议
bluestore_csum_type 文档未明确 crc32c(源文明确:"crc32c is the default checksum algorithm") 数据校验和算法。可选 crc32c、xxhash32、xxhash64、crc32c_16、crc32c_8 💡 元数据极多、想省空间时可考虑 crc32c_16(代价是检错率降到 1/65536);常规保持默认

8.9 Inline Compression(内联压缩)

支持的算法:snappy、zlib、lz4、zstd。

是否压缩由两个因素决定:① compression mode 和 ② 与写操作关联的 client hints。

四种 compression mode

mode 行为
none 永不压缩数据
passive 不压缩,除非该写操作设置了 compressible hint
aggressive 压缩数据,除非该写操作设置了 incompressible hint
force 无论如何都尝试压缩数据

关于 compressible 和 incompressible I/O hints,官方指向 rados_set_alloc_hint(C API)。

压缩的收益门槛

数据只有在 data chunk 被充分减小(由 bluestore compression required ratio 设置决定)时才会被压缩。无论使用哪种压缩模式,若压缩后的 data chunk 太大,它会被丢弃,转而存储原始(未压缩)数据。

例子:若 bluestore compression required ratio 设为 .7,则只有压缩后数据大小不超过原始数据大小的 70% 时才会真正压缩。

设置方式(per-pool 或全局)

ceph osd pool set <pool-name> compression_algorithm <algorithm>
ceph osd pool set <pool-name> compression_mode <mode>
ceph osd pool set <pool-name> compression_required_ratio <ratio>
ceph osd pool set <pool-name> compression_min_blob_size <size>
ceph osd pool set <pool-name> compression_max_blob_size <size>

可设置的五项:compression mode、compression algorithm、compression required ratio、min blob size、max blob size —— 都可以通过 per-pool 属性或全局 config 选项指定。

相关配置项

配置项 类型 默认值 含义 调参建议
bluestore_compression_algorithm 文档未明确 snappy(默认值取自 src/common/options/global.yaml.in @ v20.2.4;合法值:snappy / zlib / lz4 / zstd) 内联压缩算法 💡 追求CPU/压缩率平衡首选 lz4;要更高压缩率用 zstd
bluestore_compression_mode 文档未明确 none(默认值取自 src/common/options/global.yaml.in @ v20.2.4;合法值:none / passive / aggressive / force) 内联压缩策略 💡 生产建议从 none 或 passive 起步;force 会尝试压缩一切,CPU 代价高
bluestore_compression_required_ratio 文档未明确 0.875(默认值取自 src/common/options/global.yaml.in @ v20.2.4;源文举例用 .7) 压缩收益门槛:压缩后大小 / 原始大小 不超过此比例才真正压缩 💡 默认逻辑就是"压不动就不压",避免负收益
bluestore_compression_min_blob_size 文档未明确 0(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 压缩 chunk 的最小尺寸(小于此值不压缩) 💡 有大量小对象时抬高此值可避免无效压缩
bluestore_compression_min_blob_size_hdd 文档未明确 8_K(= 8192 字节 = 8 KiB)(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 同上,HDD 变体 💡 按主设备类型自动选择
bluestore_compression_min_blob_size_ssd 文档未明确 64_K(= 65536 字节 = 64 KiB)(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 同上,SSD 变体 💡 同上
bluestore_compression_max_blob_size 文档未明确 0(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 压缩 chunk 的最大尺寸(大于此值先拆分成不大于此值的更小 blob 再压缩) 💡 与 RBD/CephFS 的 I/O 大小对齐能提升压缩效率
bluestore_compression_max_blob_size_hdd 文档未明确 64_K(= 65536 字节 = 64 KiB)(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 同上,HDD 变体 💡 同上
bluestore_compression_max_blob_size_ssd 文档未明确 64_K(= 65536 字节 = 64 KiB)(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 同上,SSD 变体 💡 同上

8.10 RocksDB Sharding(分片)

原理

BlueStore 维护多种类型的内部 key-value 数据,全部存在 RocksDB 中。每种数据类型在 BlueStore 中被分配一个唯一的 prefix。

版本 存储方式
Pacific 之前 所有 key-value 数据存在单个 RocksDB column family:'default'
Pacific 及以后 BlueStore 可以把 key-value 数据划分到多个 RocksDB column family

收益:当 key 相似时(具体地:相似的访问频率、相似的修改频率、相似的生命周期),BlueStore 能获得更好的缓存和更精确的 compaction。在此条件下:

  • 性能被改善;
  • compaction 期间需要的磁盘空间更少(因为每个 column family 更小,且能独立于其他 column family 进行 compaction)。

启用

  • Pacific 或以后版本中部署的 OSD 默认使用 RocksDB sharding;
  • ⚠️ 但若 Ceph 是从之前版本升级到 Pacific 或更高版本的,则在 Pacific 之前创建的任何 OSD 上 sharding 是被禁用的。

为特定 OSD 启用 sharding 并应用 Pacific 默认值:先停掉该 OSD,然后执行:

ceph-bluestore-tool \
 --path <data path> \
 --sharding="m(3) p(3,0-12) O(3,0-13)=block_cache={type=binned_lru} L P" \
 reshard

相关配置项

配置项 类型 默认值 含义 调参建议
bluestore_rocksdb_cf 文档未明确 true(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 是否为 BlueStore 的 RocksDB 启用 column family(sharding) 💡 Pacific+ 新建 OSD 默认启用;升级上来的老 OSD 需用上面的 reshard 手工开启
bluestore_rocksdb_cfs 文档未明确 m(3) p(3,0-12) O(3,0-13)=block_cache={type=binned_lru} L=min_write_buffer_number_to_merge=32 P=min_write_buffer_number_to_merge=32(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 定义各个 column family 的具体配置(即上例 --sharding= 后那串内容的配置项形式) 💡 仅在需要精细调优 RocksDB 布局时修改

8.11 Throttling(限流)

配置项 类型 默认值 含义 调参建议
bluestore_throttle_bytes 文档未明确 64_M(= 67108864 字节 = 64 MiB)(默认值取自 src/common/options/global.yaml.in @ v20.2.4) BlueStore 在开始限流(defer 或阻塞)之前允许的在途(in-flight)写入字节数 💡 写压力大、出现 deferred write 堆积时可配合下面几项调节
bluestore_throttle_deferred_bytes 文档未明确 128_M(= 134217728 字节 = 128 MiB)(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 允许在途的 deferred(延迟)写入的字节数 💡 与 WAL/DB 设备性能强相关;快速设备上可放宽
bluestore_throttle_cost_per_io 文档未明确 0(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 每个 I/O 被计入限流的"成本" 💡 一般不动
bluestore_throttle_cost_per_io_hdd 文档未明确 670000(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 同上,HDD 变体 💡 一般不动
bluestore_throttle_cost_per_io_ssd 文档未明确 4000(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 同上,SSD 变体 💡 一般不动

8.12 Minimum Allocation Size(最小分配单元,重要)

概念

BlueStore 在底层存储设备上分配的最小存储量是配置好的。实践中,这是即使一个极小的 RADOS 对象在每个 OSD 的主设备上也会消耗的最少容量。

取值来源:bluestore_min_alloc_size 的值派生自 bluestore_min_alloc_size_hdd 或 bluestore_min_alloc_size_ssd,取决于该 OSD 的 rotational 属性:

  • HDD 上创建的 OSD → 用 bluestore_min_alloc_size_hdd 的当前值初始化;
  • SSD OSD(包括 NVMe 设备)→ 用 bluestore_min_alloc_size_ssd 的当前值初始化。

默认值随版本演进

版本 HDD(rotational) SSD(non-rotational)
Mimic 及更早 64KB 16KB
Octopus 64KB 改为 4KB
Pacific 改为 4KB 4KB

变更动因:托管大量小文件(S3/Swift 对象)的 Ceph RADOS Gateway(RGW)部署所经历的空间放大(space amplification)。

空间放大如何发生(原文算例)

用户对象 存成 底层分配 浪费 开销
1 KB S3 对象 单个 RADOS 对象 4 KB 底层空间 约 3 KB 被分配但从不使用 300% 开销 / 25% 效率
5 KB 用户对象 两个 RADOS 对象(4KB + 1KB) 4KB + 4KB 4KB 设备容量被闲置 开销百分比小得多

开销百分比随对象增大而迅速下降(原文提示:把它想成取模运算的余数)。

一个容易忽略的放大:每个副本都会发生

There is an additional subtlety that is easily missed: the amplification phenomenon just described takes place for each replica.

保护策略 1 KB S3 对象实际搁浅的设备容量
默认三副本(3R) 约 9 KB
EC k=4, m=2 24 KB(即 4 KB × 6)

什么时候要重视:当 RGW bucket pool 包含大量相对较大的用户对象时,这个现象的影响通常可以忽略。但对于预期会有相当比例较小用户对象的部署,应该把这个效应纳入考虑。

新型 QLC SSD 的特殊考虑

  • 4KB 默认值与传统 HDD 和 SSD 设备契合良好;
  • ⚠️ 某些新型 coarse-IU(Indirection Unit)QLC SSD,当把 bluestore_min_alloc_size_ssd 设为与该设备的 IU 匹配时(可能是 8KB、16KB,甚至 64KB),性能和磨损表现最佳;
  • 这类新型盘可以达到与传统 TLC SSD 相当的读性能、比 HDD 更快的写性能,且比 TLC SSD 更高的密度和更低的成本。

⚠️ 在这些新型设备上创建 OSD 时,必须小心:只对合适的设备应用非默认值,不要应用到传统 HDD 和 SSD 上。 避免出错的手段:仔细安排 OSD 创建的顺序、使用自定义 OSD device classes、尤其是使用中心配置的 masks。

自动探测(Quincy+)

Quincy 及以后,可以用 bluestore_use_optimal_io_size_for_min_alloc_size 选项,让每个 OSD 创建时自动发现正确的值。

⚠️ 干扰因素:bcache、OpenCAS、dmcrypt、ATA over Ethernet、iSCSI 等其它设备分层与抽象技术可能会干扰正确值的判定。此外,部署在 VMware 存储之上的 OSD 有时被发现会报告与底层硬件不匹配的 rotational 属性。

排查方法:

  1. 通过日志和 admin socket 检查这类 OSD 的启动行为(⚠️ 在较老内核上这种检查可能不如预期工作);
  2. 检查 /sys/block/<drive>/queue/optimal_io_size 的存在性和值;
  3. Reef 及以后,min_alloc_size 会由 ceph osd metadata 方便地报告出来:
ceph osd metadata osd.1701 | egrep rotational\|alloc

空间放大的外部表现(原文)

现象 排查命令
ceph df 报告的 raw 与 stored 数据之比异常高 ceph df
ceph osd df 报告的 %USE / VAR 相比其他"表面相同"的 OSD 异常高 ceph osd df
使用了 min_alloc_size 值不匹配的 OSD 的 pool 中,出现意料之外的 balancer 行为 —

⚠️ 生效时机

This BlueStore attribute takes effect only at OSD creation; if the attribute is changed later, a specific OSD's behavior will not change unless and until the OSD is destroyed and redeployed with the appropriate option value(s). Upgrading to a later Ceph release will not change the value used by OSDs that were deployed under older releases or with other settings.

相关配置项

配置项 类型 默认值 含义 调参建议
bluestore_min_alloc_size 文档未明确 由 _hdd 或 _ssd 派生(取决于 OSD 的 rotational 属性) OSD 实际生效的最小分配单元 💡 只读性质的派生值;要改就改下面两个
bluestore_min_alloc_size_hdd 文档未明确 Pacific 起为 4KB(Mimic 及更早为 64KB) HDD OSD 的最小分配单元 💡 小对象密集的 RGW 场景保持 4KB;大对象顺序负载可考虑调大以减少元数据
bluestore_min_alloc_size_ssd 文档未明确 Octopus 起为 4KB(Mimic 及更早为 16KB) SSD(含 NVMe)OSD 的最小分配单元 💡 coarse-IU QLC SSD 应设为与设备 IU 匹配(8KB/16KB/64KB),且只作用于这类设备
bluestore_use_optimal_io_size_for_min_alloc_size 文档未明确 false(默认值取自 src/common/options/global.yaml.in @ v20.2.4;Quincy 起可用) 允许每个 OSD 在创建时自动发现正确的 min_alloc_size 💡 混合介质集群推荐开启;但注意 bcache/dmcrypt/iSCSI/VMware 等抽象层会干扰判定

8.13 SPDK Usage(NVMe 用户态驱动)

前置条件:先准备好系统,参见 SPDK document。SPDK 提供一个自动配置设备的脚本,需要 root 权限运行:

sudo src/spdk/scripts/setup.sh

指定设备:必须为 bluestore_block_path 指定该 NVMe 设备的设备选择器(device selector),并加 spdk: 前缀。

# 查找 Intel NVMe SSD 的设备选择器
lspci -mm -n -D -d 8086:0953

设备选择器的形式是 DDDD:BB:DD.FF 或 DDDD.BB.DD.FF。

# 例:若 lspci 输出中找到的设备选择器是 0000:01:00.0
bluestore_block_path = "spdk:trtype:PCIe traddr:0000:01:00.0"

也可以指定基于 TCP 传输的远程 NVMeoF target:

bluestore_block_path = "spdk:trtype:TCP traddr:10.67.110.197 trsvcid:4420 subnqn:nqn.2019-02.io.spdk:cnode1"

每节点运行多个 SPDK 实例时,必须为每个实例指定它将使用的 DPDK 内存量(MB),以确保每个实例使用自己的 DPDK 内存。

colocate(数据、DB、WAL 共用一个设备)时的必配项 —— 若不设置这些,当前实现会用内核文件系统符号填充 SPDK map 文件,并使用内核驱动来下发 DB/WAL I/O:

bluestore_block_db_path = ""
bluestore_block_db_size = 0
bluestore_block_wal_path = ""
bluestore_block_wal_size = 0
配置项 类型 默认值 含义 调参建议
bluestore_block_path String 文档未明确 主设备(block)路径。使用 SPDK 时填 spdk:... 形式的设备选择器 💡 仅在使用 SPDK 用户态 NVMe 驱动时设置
bluestore_block_db_path String 文档未明确 DB 设备路径 💡 SPDK colocate 时必须显式置空
bluestore_block_db_size 文档未明确 0(默认值取自 src/common/options/global.yaml.in @ v20.2.4) DB 设备大小 💡 SPDK colocate 时必须显式置 0
bluestore_block_wal_path String 文档未明确 WAL 设备路径 💡 SPDK colocate 时必须显式置空
bluestore_block_wal_size 文档未明确 96_M(= 100663296 字节 = 96 MiB)(默认值取自 src/common/options/global.yaml.in @ v20.2.4) WAL 设备大小 💡 SPDK colocate 时必须显式置 0

8.14 DSA(Data Streaming Accelerator)Usage

用途:若想用 DML 库驱动 DSA 设备来卸载持久内存(PMEM)上的读写操作。

硬件/软件前提:

  • 安装 DML 和 idxd-config 库;
  • 仅在拥有 SPR(Sapphire Rapids)CPU 的机器上工作。

配置共享工作队列(WQ):

accel-config config-wq --group-id=1 --mode=shared --wq-size=16 --threshold=15 --type=user --name="MyApp1" --priority=10 --block-on-fault=1 dsa0/wq0.1
accel-config config-engine dsa0/engine0.1 --group-id=1
accel-config enable-device dsa0
accel-config enable-wq dsa0/wq0.1

8.15 BlueStore vs FileStore 对比

维度 BlueStore FileStore(已废弃)
状态 Luminous 12.2.z 起默认且推荐 Luminous 之前是默认(且唯一)。⚠️ Reef 起废弃且不再被支持
数据路径 直接管理裸块设备或分区,不创建也不挂载常规文件系统 依赖标准文件系统(通常是 XFS) + 一个 key/value 数据库(传统是 LevelDB,现在是 RocksDB)存部分元数据
抽象层 无中间抽象层 —— 避免了诸如 XFS 这类本地文件系统限制性能或增加复杂度 依赖传统文件系统存对象数据,整体设计导致许多性能缺陷
元数据 内嵌 RocksDB 管理(含对象名→磁盘块位置的映射) 文件系统 + KV 数据库
校验和 全量数据和元数据校验(元数据 RocksDB 用 crc32c;数据 BlueStore 用 crc32c/xxhash32/xxhash64) 本册源文未描述 FileStore 的校验和能力
内联压缩 支持(snappy/zlib/lz4/zstd) 本册源文未描述
多设备分层 支持把 WAL/DB 放到更快设备 见下面的 journal 机制
copy-on-write clone 高效实现,让常规快照和 EC 池(依赖 clone 做高效 two-phase commit)都高效 本册源文未描述
部分写 / EC overwrites 支持(allow_ec_overwrites 只能在 BlueStore 上启用) 不支持且不安全:"Using Filestore with EC overwrites is not only unsafe, but it also results in lower performance compared to BlueStore."
推荐文件系统 N/A 只推荐 XFS。btrfs 和 ext4 都有已知 bug 和缺陷,使用它们可能导致数据丢失。⚠️ 默认情况下所有 Ceph provisioning 工具都用 XFS
成熟度 当前标准 "FileStore is well-tested and widely used in production." —— 但有上述性能缺陷

迁移:官方提供 BlueStore Migration 文档(rados/operations/bluestore-migration),不在本册 25 个源文范围内。

8.16 FileStore 配置参考(历史引擎,全部逐项列出)

⚠️ 官方 note:自 Luminous 起 FileStore 不再是默认后端(BlueStore 是)。FileStore OSD 一直支持到 Quincy;在 Reef 中不被支持。

8.16.1 调试

配置项 类型 默认值 含义 调参建议
filestore_debug_omap_check Boolean false 对同步做调试检查。开销昂贵(Expensive),仅供调试 💡 只在排障时临时开

8.16.2 Extended Attributes(扩展属性)

背景:XATTRs 对 FileStore OSD 很重要。用底层文件系统存 XATTRs 有两类缺点:某些文件系统对可存储的 XATTR 字节数有限制,且某些情况下比替代的 XATTR 存储方式更慢。因此用一种外在于底层文件系统的 XATTR 存储方法可能提升性能。

规则:

  • 若底层文件系统没有大小限制 → Ceph XATTRs 用文件系统提供的 XATTR 存为 inline xattr;
  • 若有大小限制(例:ext4 限制总共 4 KB)→ 达到限制后,部分 Ceph XATTRs 会被存进 key/value 数据库。更准确地说,当 filestore_max_inline_xattr_size 或 filestore_max_inline_xattrs 阈值被达到时开始发生。
配置项 类型 默认值 含义 调参建议
filestore_max_inline_xattr_size Unsigned 32-bit Integer 0 可在文件系统(XFS/Btrfs/ext4)中存储的每个 XATTR 的最大大小。指定的大小不应超过文件系统能处理的大小。用默认值 0 表示让 Filestore 使用文件系统特定的值 💡 保持 0,让 Ceph 按文件系统自动选
filestore_max_inline_xattr_size_xfs Unsigned 32-bit Integer 65536 可存在 XFS 中的 XATTR 最大大小。仅当 filestore_max_inline_xattr_size == 0 时使用 💡 一般不动
filestore_max_inline_xattr_size_btrfs Unsigned 32-bit Integer 2048 可存在 Btrfs 中的 XATTR 最大大小。仅当上项为 0 时使用 💡 一般不动
filestore_max_inline_xattr_size_other Unsigned 32-bit Integer 512 可存在其它文件系统中的 XATTR 最大大小。仅当 filestore_max_inline_xattr_size == 0 时使用 💡 一般不动
filestore_max_inline_xattrs 32-bit Integer 0 可在文件系统中存储的每个对象的 XATTR 最大数量。默认值 0 表示让 Filestore 使用文件系统特定的值 💡 保持 0
filestore_max_inline_xattrs_xfs 32-bit Integer 10 XFS 中每对象可存的 XATTR 最大数量。仅当上项为 0 时使用 💡 一般不动
filestore_max_inline_xattrs_btrfs 32-bit Integer 10 Btrfs 中每对象可存的 XATTR 最大数量。仅当上项为 0 时使用 💡 一般不动
filestore_max_inline_xattrs_other 32-bit Integer 2 其它文件系统中每对象可存的 XATTR 最大数量。仅当上项为 0 时使用 💡 一般不动

8.16.3 Synchronization Intervals(同步间隔)

背景:Filestore 必须周期性地静默写入并同步文件系统。每次同步创建一个一致的提交点(consistent commit point)。 创建提交点时,Filestore 能够释放到该点为止的所有 journal 条目。

频率 影响
更频繁的同步 倾向于减少同步时间以及需要保留在 journal 中的数据量
更不频繁的同步 让后端文件系统合并小写和元数据更新,可能提升同步效率,但也可能增加尾延迟(tail latency)
配置项 类型 默认值 含义 调参建议
filestore_max_sync_interval Double 5 同步 Filestore 的最大间隔(秒) 💡 与 journal 大小联动;journal 小则调小此项
filestore_min_sync_interval Double .01 同步 Filestore 的最小间隔(秒) 💡 一般不动

8.16.4 Flusher(冲刷器)

背景:Filestore flusher 在同步之前用 sync_file_range 把大写入的数据强制写出。理想情况下这会降低最终同步的成本。⚠️ 但在实践中,禁用 filestore_flusher 在某些情况下似乎能提升性能。

配置项 类型 默认值 含义 调参建议
filestore_flusher Boolean false 启用 Filestore flusher ❗自 v.65 起已废弃(deprecated)
filestore_flusher_max_fds Integer 512 flusher 的最大文件描述符数 ❗自 v.65 起已废弃
filestore_sync_flush Boolean false 启用同步 flusher ❗自 v.65 起已废弃
filestore_fsync_flushes_journal_data Boolean false 在文件系统同步期间冲刷 journal 数据 💡 一般不动

8.16.5 Queue(队列)

配置项 类型 默认值 含义 调参建议
filestore_queue_max_ops Integer 50 Filestore 在阻塞新操作入队之前接受的在途操作最大数量。⚠️ 对性能影响极小(Minimal impact on performance) 💡 一般不动(官方明确说影响极小)
filestore_queue_max_bytes Integer 100 << 20 每个操作允许的最大字节数 💡 一般不动

8.16.6 Timeouts(超时)

配置项 类型 默认值 含义 调参建议
filestore_op_threads Integer 2 并行执行的文件系统操作线程数 💡 一般不动
filestore_op_thread_timeout Integer 60 文件系统操作线程的超时(秒) 💡 一般不动
filestore_op_thread_suicide_timeout Integer 180 提交操作在被取消前的超时(秒) 💡 一般不动

8.16.7 B-Tree Filesystem(btrfs)

配置项 类型 默认值 含义 调参建议
filestore_btrfs_snap Boolean true 为 btrfs Filestore 启用快照。仅用于 btrfs ❗不要用于生产(官方只推荐 XFS,btrfs 有已知 bug 可能导致数据丢失)
filestore_btrfs_clone_range Boolean true 为 btrfs Filestore 启用 range cloning。仅用于 btrfs ❗同上

8.16.8 Journal(FileStore 侧)

配置项 类型 默认值 含义 调参建议
filestore_journal_parallel Boolean false 启用并行 journaling,btrfs 的默认 💡 一般不动
filestore_journal_writeahead Boolean false 启用 write-ahead journaling,XFS 的默认 💡 一般不动
filestore_journal_trailing Boolean false ⚠️ 已废弃。永远不要使用(Never use) ❗禁止启用

8.16.9 Misc(杂项)

配置项 类型 默认值 含义 调参建议
filestore_merge_threshold Integer -10 子目录在被合并回父目录之前允许包含的最少文件数。⚠️ 负值意味着禁用子目录合并 💡 默认 -10 即默认禁用合并
filestore_split_multiple Integer 2 子目录在被拆分成子目录之前允许包含的最大文件数,公式为:(filestore_split_multiple * abs(filestore_merge_threshold) + (rand() % filestore_split_rand_factor)) * 16 💡 目录过大影响查找性能时可下调
filestore_split_rand_factor Unsigned 32-bit Integer 20 加到 split 阈值上的随机因子,用于避免过多(昂贵的)Filestore split 同时发生。⚠️ 要对已有 OSD 改这个设置,必须先把该 OSD 下线,再运行 ceph-objectstore-tool apply-layout-settings 💡 一般不动
filestore_update_to Integer 1000 限制自动升级到指定版本的 Filestore。适用于想避免升级到某个特定版本的情况 💡 一般不动
filestore_blackhole Boolean false 丢弃任何新的事务,类似重定向到 NULL 💡 仅测试用
filestore_dump_file Boolean false 事务 dump 存放的文件 💡 仅调试用(⚠️ 源文 Type 写的是 Boolean,但描述是"文件",疑为源文笔误,见「文档未明确项」第 7 条)
filestore_kill_at String false 在第 n 次机会注入一个失败 💡 仅故障注入测试用(⚠️ 源文 Type 为 String 但 Default 为 false,疑为源文笔误)
filestore_fail_eio Boolean true 遇到 EIO 时失败/崩溃 💡 一般不动(默认开启,即不允许静默吞掉 EIO)

8.17 Journal 配置参考(FileStore journal,全部逐项列出)

⚠️ Filestore has been deprecated in the Reef release and is no longer supported.

FileStore OSD 使用 journal 的两个原因:

目的 机制
Speed(速度) journal 让 Ceph OSD Daemon 能快速提交小写。Ceph 把小的随机 I/O 顺序地写入 journal,这倾向于通过给后端文件系统更多时间来合并写入从而加速突发性(bursty)负载。⚠️ 但 Ceph OSD Daemon 的 journal 会导致尖刺式性能:短促的高速写入之后,是一段没有任何写入进展的时期(文件系统在追赶 journal)
Consistency(一致性) Ceph OSD Daemon 需要一个保证原子复合操作的文件系统接口。OSD 先把操作描述写入 journal,再把操作应用到文件系统,这使得对对象的原子更新成为可能(例如对 placement group 元数据的更新)。每隔几秒 —— 在 filestore max sync interval 与 filestore min sync interval 之间 —— OSD 停止写入并把 journal 与文件系统同步,从而允许 OSD 裁剪 journal 中的操作并重用空间。发生故障时,OSD 从最后一次同步操作之后开始重放(replay)journal

注:自 Luminous 起 BlueStore 后端是首选且默认。本节信息提供给已存在的 OSD,以及极少数新部署仍选择 Filestore 的情况。

全部配置项

配置项 类型 默认值 含义 调参建议
journal_dio 文档未明确 true(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 与 journal 的 Direct I/O 相关(源文仅以 confval 列出,无散文描述) 💡 仅在调优已有 FileStore OSD 的 journal 时关注
journal_aio 文档未明确 true(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 与 journal 的 Async I/O 相关(源文仅以 confval 列出) 💡 同上
journal_block_align 文档未明确 true(默认值取自 src/common/options/global.yaml.in @ v20.2.4) journal 是否按块对齐写入(源文仅以 confval 列出) 💡 同上
journal_max_write_bytes 文档未明确 10_M(= 10485760 字节 = 10 MiB)(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 单次 journal 写入的最大字节数(源文仅以 confval 列出) 💡 同上
journal_max_write_entries 文档未明确 100(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 单次 journal 写入的最大条目数(源文仅以 confval 列出) 💡 同上
journal_align_min_size 文档未明确 64_K(= 65536 字节 = 64 KiB)(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 需要进行对齐的最小 I/O 大小(源文仅以 confval 列出) 💡 同上
journal_zero_on_create 文档未明确 false(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 创建 journal 时是否清零(源文仅以 confval 列出) 💡 同上

💡 补充:由于 FileStore 已在 Reef 废弃,且本页 7 个配置项在源文中只有 confval 指令、没有任何散文描述,上表"含义"列是根据配置项名称给出的语义推断,这属于本手册的补充,官方文档未给出权威描述。请勿据此做生产调参。

引自:BlueStore Configuration Reference、Filestore Config Reference、Journal Config Reference、Storage Devices


九、存储设备建议(Storage Devices)

9.1 集群中需要存储的几类守护进程

守护进程 存储需求
Ceph OSDs 存储 Ceph 中的大部分数据。通常每个 OSD 由单个存储设备支撑 —— 可以是传统硬盘(HDD)或固态盘(SSD)。OSD 也可以由多个设备的组合支撑:例如 HDD 存大部分数据 + SSD(或 SSD 的一个分区)存部分元数据。集群中 OSD 的数量通常是以下因素的函数:① 要存的数据量;② 每个存储设备的大小;③ 指定的冗余级别与类型(复制或纠删码)
Ceph Monitor 管理关键集群状态,包括集群成员关系(membership)和认证信息。小型集群只需几 GB 来存放 monitor database。大型集群中,monitor database 可能达到数十 GB 到数百 GB
Ceph Manager 与 monitor 守护进程一起运行,提供额外的监控能力,并向外部监控与管理系统提供接口

9.2 OSD Back Ends 的选择

后端 状态
BlueStore Luminous 12.2.z 起是默认(且推荐)后端
FileStore Luminous 之前是默认(且唯一)后端。⚠️ Reef 起废弃且不再被支持

9.3 HDD / SSD / NVMe 选型要点(基于本册源文可推导的结论)

⚠️ 说明:storage-devices.rst 没有给出具体的型号、容量或 IOPS 选型建议表。以下是从本册多个源文中可推导的、与介质选型直接相关的结论,凡属推导的均标 💡。

维度 结论 依据
OSD 与设备的对应关系 通常每个 OSD 由一个存储设备支撑(HDD 或 SSD);也可以由设备组合支撑(HDD 存数据 + SSD/SSD 分区存元数据) storage-devices.rst(原文)
OSD 数量的决定因素 数据量 × 单设备大小 × 冗余级别与类型(复制 / EC) storage-devices.rst(原文)
HDD vs SSD 的权重表达 CRUSH 权重以 1 TB = 1.00 为惯例,权重通常以 TiB 衡量 crush-map-edits.rst(原文)
哪种设备需要 block.db / block.wal 只有当该设备比主设备更快时才"有优势"(advantageous)。WAL 例:SSD 作 WAL + HDD 作主设备 bluestore-config-ref.rst(原文)
NVMe 属于 SSD class 默认 device class 自动识别为 hdd / ssd / nvme。⚠️ bluestore_min_alloc_size_ssd 适用于 SSD OSD(包括 NVMe 设备) crush-map.rst + bluestore-config-ref.rst(原文)
coarse-IU QLC SSD 应把 bluestore_min_alloc_size_ssd 设为与设备 IU 匹配(可能 8KB / 16KB / 64KB),可获得与 TLC SSD 相当的读性能、快于 HDD 的写性能、比 TLC 更高的密度和更低成本 bluestore-config-ref.rst(原文)
PMEM(持久内存) 可用 DSA(Data Streaming Accelerator) 卸载 PMEM 上的读写,但仅在有 SPR(Sapphire Rapids)CPU 的机器上工作 bluestore-config-ref.rst(原文)
用 SPDK 跑 NVMe 需要 spdk: 前缀的 device selector;也可指定 TCP 上的远程 NVMeoF target bluestore-config-ref.rst(原文)
BlueStore 高速设备的用途(SSD/NVMe/NVDIMM) 官方原文点名可用于**内部 journal(write-ahead log)**的高速设备:"like an SSD, NVMe, or NVDIMM" storage-devices.rst(原文)

💡 补充(跨章节推导的分区建议,官方文档未以"分区建议"形式给出):

  1. 全 HDD 集群:只指定 block,让 block.db / block.wal 与 block 共存(colocate)—— 源文明确"没有必要为它们创建独立 LV"。
  2. HDD + SSD 混布:block.db 放 SSD,block 放 HDD。block.db 大小取 block 的 1%~4%;RGW 至少 4%(1TB block → 至少 40GB db);RBD 1%~2% 通常够。
  3. 高速空间 < 1GB:用作 WAL;更多空间则应做 DB(因为指定 DB 但未指定 WAL 时,WAL 会隐式与 DB 共存于更快设备,既享有 WAL 收益又能额外卸载元数据)。
  4. LV 大小的历史甜蜜点:老版本约 3GB / 30GB / 300GB(对应 L0 / L0+L1 / L1+L2),翻倍到 6GB / 60GB / 600GB 有助于 compaction;Nautilus 14.2.12 / Octopus 15.2.6 之后任意大小都能被更好利用。
  5. 必须手工创建 VG/LV —— ceph-volume 目前无法自动创建。

9.4 官方推荐的文件系统(仅 FileStore 相关)

⚠️ 虽然 FileStore 能运行在大多数 POSIX 兼容文件系统上(包括 btrfs 和 ext4),但我们推荐只把 XFS 文件系统用于 Ceph。btrfs 和 ext4 都有已知的 bug 和缺陷,使用它们可能导致数据丢失。默认情况下所有 Ceph provisioning 工具都用 XFS。

引自:Storage Devices


十、upmap 与数据均衡(pg-upmap)

10.1 是什么

Luminous v12.2.z 及以后,OSDMap 中有一个 pg-upmap exception table,允许集群显式地把特定 PG 映射到特定 OSD。这让集群能微调数据分布,以在大多数情况下把 PG 均匀分布在各 OSD 上。

⚠️ 重要的前提条件:该特性要求所有客户端都能理解 OSDMap 中新的 pg-upmap 结构。

10.2 在线优化(Online Optimization)

启用

  • 前提:集群不能有任何 pre-Luminous 客户端;
  • 默认情况:新集群默认启用 balancer module,而它会使用 pg-upmap;
  • 若你想使用不同的 balancer,或想自己做自定义 pg-upmap 条目,可能需要关掉 balancer 以避免冲突:
ceph balancer off

在已有集群上启用该特性

必须把集群限制为只支持 Luminous(及更新)客户端:

ceph osd set-require-min-compat-client luminous

⚠️ 若有任何 pre-Luminous 的客户端或守护进程连在 monitor 上,该命令会失败。

查看当前在用的客户端版本:

ceph features

Balancer Module

The balancer module for ceph-mgr will automatically balance the number of PGs per OSD.

(详见 rados/operations/balancer,不在本册 25 个源文范围内。)

10.3 离线优化(Offline Optimization)

Upmap 条目由内置于 osdmaptool 的离线优化器更新。

第 1 步:取最新的 osdmap

ceph osd getmap -o om

第 2 步:跑优化器

osdmaptool om --upmap out.txt [--upmap-pool <pool>] \
[--upmap-max <max-optimizations>] \
[--upmap-deviation <max-deviation>] \
[--upmap-active]
选项 含义 默认值
--upmap-pool <pool> 指定要优化的 pool。可多次指定 —
--upmap-max <max-optimizations> 最多找出多少个 upmap 条目 10(与 ceph-mgr balancer module 相同)。⚠️ 若在做离线优化,应该用更大的数字。若找不到更多可做的变更(即 pool 分布已完美),它会提前停止
--upmap-deviation <max-deviation> 若某 OSD 的 PG 计数与计算出的目标数相差不超过此值,就认为它是完美的 5
--upmap-active 模拟 active balancer 在 upmap 模式下的行为。持续循环直到 OSD 达到均衡,并报告发生了多少轮、每轮耗时。⚠️ 每轮的耗时可用来指示 ceph-mgr 计算下一份优化计划时消耗的 CPU 负载 —

⚠️ 强烈建议(原文原话:It is highly recommended):逐 pool 单独做优化,或对"使用方式相似的 pool 集合"做优化。

"Similarly utilized pools" 的定义:映射到相同设备、且存储同类数据的 pool。

  • ✅ 是:多个 RBD image pool 互为 similarly utilized;
  • ❌ 不是:RGW index pool 与 RGW data pool 不算 similarly utilized。

第 3 步:应用变更

source out.txt

out.txt 中写入的是建议的变更;其中的命令是普通的 Ceph CLI 命令,可以按顺序执行以把变更应用到集群。

上述步骤可以重复执行任意多次,直到为每个 pool 集合达成完美的 PG 分布。

10.4 调试

目的 命令
看优化器在做什么(一些"血腥"细节) osdmaptool ... --debug-osd 10
看更多细节 osdmaptool ... --debug-crush 10

10.5 适用场景小结

场景 建议
新集群 默认已启用 balancer module(使用 pg-upmap),无需额外操作
已有集群想启用 pg-upmap 先 ceph features 确认无 pre-Luminous 客户端 → ceph osd set-require-min-compat-client luminous
想自己精确控制 upmap 条目 先 ceph balancer off,再做自定义条目
大规模/复杂集群、在线 balancer 效果不佳 用 osdmaptool --upmap 做离线优化,并逐 pool(或按 similarly utilized 分组)执行
想知道 ceph-mgr 计算优化计划要花多少 CPU 用 --upmap-active 观察每轮耗时

引自:Using pg-upmap


十一、MON 与 OSD 的交互机制

11.1 总体机制

当你执行 ceph health 或 ceph -s 时,Ceph Monitor 报告 Ceph Storage Cluster 的当前状态。Monitor 通过两种方式了解集群:

  1. 要求每个 Ceph OSD Daemon 上报;
  2. 接收 OSD 关于其邻居 OSD 状态的报告。

若 Monitor 没收到报告,或收到关于集群变更的报告,Monitor 就更新 Ceph Cluster Map 的状态。

Ceph 为 Monitor / OSD 交互提供了合理的默认设置,但可以覆盖默认值。

11.2 OSDs Check Heartbeats(心跳检测)

参数 值(源文明确)
心跳检查间隔 每个 OSD 以小于每 6 秒的随机间隔检查其他 OSD 的心跳
宽限期(grace period) 若邻居 OSD 在 20 秒宽限期内没有显示心跳,该 OSD 可以认为邻居 down 并报告给 Monitor,Monitor 随后更新 Ceph Cluster Map
可配置项 可通过在 ceph.conf 的 [mon] 和 [osd] 或 [global] 段添加 osd heartbeat grace 设置,或在运行时设置该值,来修改这个宽限期

心跳流程示意(原文 ditaa 转述):

    OSD 1                      OSD 2
      |                          |
      |----+ Heartbeat           |
      |    | Interval            |
      |<---+ Exceeded            |
      |                          |
      |       Check              |
      |     Heartbeat            |
      |------------------------->|
      |                          |
      |<-------------------------|
      |   Heart Beating          |
      |                          |
      |----+ Heartbeat           |
      |    | Interval            |
      |<---+ Exceeded            |
      |                          |
      |       Check              |
      |     Heartbeat            |
      |------------------------->|
      |                          |
      |----+ Grace               |
      |    | Period              |
      |<---+ Exceeded            |
      |                          |
      |----+ Mark                |
      |    | OSD 2               |
      |<---+ Down                |

11.3 OSDs Report Down OSDs(上报 down 的 OSD)

默认规则:

  • 默认需要来自不同 host 的两个 OSD 向 Monitor 报告另一个 OSD down,Monitor 才认可它 down;
  • 动机:有可能所有上报故障的 OSD 都在一个交换机有问题的 rack 里,而该 OSD 只是连不上它们。为避免这类误报,Ceph 把上报故障的 peer 视为"整体集群中一个潜在 subcluster(同样 lag)"的代理(proxy)。

    原文坦诚:This is clearly not true in all cases, but will sometimes help us localize the grace correction to a subset of the system that is unhappy.

  • 分组依据:mon osd reporter subtree level 用于按 peer 在 CRUSH map 中的共同祖先类型把它们归入"subcluster";
  • 默认只需要来自不同 subtree 的两份报告即可上报另一个 OSD down。

可配置项(在 ceph.conf 的 [mon] 段,或运行时设置):

配置项 作用
mon osd min down reporters 改变上报 OSD down 所需的、来自唯一 subtree 的报告数量
mon osd reporter subtree level 改变用于分组的共同祖先类型

上报流程示意:

    OSD 1              OSD 2             Monitor
      |                  |                  |
      | OSD 3 Is Down    |                  |
      |------------------+----------------->|
      |                  |                  |
      |                  | OSD 3 Is Down    |
      |                  |----------------->|
      |                  |                  |
      |                  |                  |---------+ Mark
      |                  |                  |         | OSD 3
      |                  |                  |<--------+ Down

11.4 OSDs Report Peering Failure(上报 peering 失败)

若某个 OSD 无法与它的 Ceph 配置文件(或 cluster map)中定义的任何 OSD 完成 peering,它会每 30 秒 ping 一次 Monitor 以获取最新的 cluster map 副本。

可配置项:在 ceph.conf 的 [osd] 段添加 osd mon heartbeat interval,或在运行时设置。

流程示意:

    OSD 1        OSD 2        OSD 3       Monitor
      |            |            |            |
      |  Request To|            |            |
      |     Peer   |            |            |
      |----------->|            |            |
      |<-----------|            |            |
      |    Peering              |            |
      |                         |            |
      |  Request To             |            |
      |     Peer                |            |
      |------------------------>|            |
      |                                      |
      |----+ OSD Monitor                     |
      |    | Heartbeat                       |
      |<---+ Interval Exceeded               |
      |                                      |
      |     Failed to Peer with OSD 3        |
      |------------------------------------->|
      |<-------------------------------------|
      |      Receive New Cluster Map         |

11.5 OSDs Report Their Status(上报自身状态)

行为 时间(源文明确)
Monitor 判定 OSD down 若某 OSD 不上报,在 mon osd report timeout 超时后,Monitor 认为该 OSD down
触发上报的事件 当发生可上报事件时上报:故障、PG stats 变化、up_thru 变化,或 OSD 在 5 秒内启动
最小上报间隔 可用 osd mon report interval 修改([osd] 段,或运行时)
最大上报间隔 OSD 每 120 秒向 Monitor 上报一次,无论是否发生值得注意的变化。可用 osd mon report interval max 修改([osd] 段,或运行时)

上报流程示意:

    OSD 1                    Monitor
      |                          |
      |----+ Report Min          |
      |    | Interval            |
      |<---+ Exceeded            |
      |                          |
      |----+ Reportable          |
      |    | Event               |
      |<---+ Occurs              |
      |                          |
      |     Report To            |
      |      Monitor             |
      |------------------------->|
      |                          |
      |----+ Report Max          |
      |    | Interval            |
      |<---+ Exceeded            |
      |                          |
      |     Report To            |
      |      Monitor             |
      |------------------------->|
      |                          |
      |----+ Monitor             |
      |    | Fails               |
      |<---+                     |
                                 +----+ Monitor OSD
                                 |    | Report Timeout
                                 |<---+ Exceeded
                                 |
                                 +----+ Mark
                                 |    | OSD 1
                                 |<---+ Down

11.6 配置设置

⚠️ 修改心跳设置时,应把它们放进配置文件的 [global] 段。

Monitor Settings(15 项,逐项列出)

配置项 类型 默认值 含义 调参建议
mon_osd_min_up_ratio 文档未明确 0.3(默认值取自 src/common/options/mon.yaml.in @ v20.2.4) 集群允许继续服务所需的最小 up OSD 比例 💡 低于此比例集群会停止 I/O 以保护数据;一般不动
mon_osd_min_in_ratio 文档未明确 0.75(默认值取自 src/common/options/mon.yaml.in @ v20.2.4) 集群允许继续服务所需的最小 in OSD 比例 💡 同上
mon_osd_laggy_halflife 文档未明确 1_hr(= 3600 秒)(默认值取自 src/common/options/mon.yaml.in @ v20.2.4) 计算 OSD "laggy" 评分时的半衰期 💡 与下面两项共同决定 laggy 判定;抖动频繁的环境可放慢(加大半衰期)
mon_osd_laggy_weight 文档未明确 0.3(默认值取自 src/common/options/mon.yaml.in @ v20.2.4) laggy 评分在 OSD 排名中的权重 💡 一般不动
mon_osd_laggy_max_interval 文档未明确 5_min(= 300 秒)(默认值取自 src/common/options/mon.yaml.in @ v20.2.4) laggy 评分考虑的最大间隔 💡 一般不动
mon_osd_adjust_heartbeat_grace 文档未明确 true(默认值取自 src/common/options/mon.yaml.in @ v20.2.4) 是否让 Monitor 根据观察到的 laggy 情况自动调整心跳宽限期 💡 与 11.3 节的"把上报故障的 peer 当作 subcluster 代理"机制对应
mon_osd_adjust_down_out_interval 文档未明确 true(默认值取自 src/common/options/mon.yaml.in @ v20.2.4) 是否让 Monitor 自动调整 mon_osd_down_out_interval 💡 一般不动
mon_osd_auto_mark_in 文档未明确 false(默认值取自 src/common/options/mon.yaml.in @ v20.2.4) 是否自动把 OSD 标记为 in 💡 自动化运维场景可开启
mon_osd_auto_mark_auto_out_in 文档未明确 true(默认值取自 src/common/options/mon.yaml.in @ v20.2.4) 是否自动把被 auto-out 的 OSD 重新标记为 in 💡 与 mon_osd_auto_mark_out(未在本页列出)相关
mon_osd_auto_mark_new_in 文档未明确 true(默认值取自 src/common/options/mon.yaml.in @ v20.2.4) 是否自动把新加入的 OSD 标记为 in 💡 扩容自动化场景可开启
mon_osd_down_out_interval 文档未明确 10_min(= 600 秒)(默认值取自 src/common/options/mon.yaml.in @ v20.2.4) OSD 被标记 down 多久之后被自动标记 out(从而触发数据重平衡) 💡 这是最关键的一个:太短会因短暂抖动引发大量回填;太长会延长降级窗口
mon_osd_down_out_subtree_limit 文档未明确 rack(默认值取自 src/common/options/mon.yaml.in @ v20.2.4) 限制 down-out 判定的作用范围到某种 CRUSH subtree 类型 💡 大规模集群可防止整机/整机柜下线时触发全集群重平衡风暴
mon_osd_report_timeout 文档未明确 15_min(= 900 秒)(默认值取自 src/common/options/global.yaml.in @ v20.2.4) OSD 多久不上报就被 Monitor 判定为 down 💡 与 11.5 节及 ceph pg dump_stuck stale 的定义直接对应
mon_osd_min_down_reporters 文档未明确 默认需要"来自不同 subtree 的两份报告"(源文明确:only two reports from different subtree are required) 需要多少份来自唯一 subtree 的报告才判定某 OSD down 💡 与 mon_osd_reporter_subtree_level 配对调整;机架级故障场景可能需要提高
mon_osd_reporter_subtree_level 文档未明确 host(默认值取自 src/common/options/global.yaml.in @ v20.2.4;源文给出配置项名 mon osd reporter subtree level,用于按共同祖先类型把 peer 分组进 "subcluster") 用哪一级 CRUSH 类型作为"共同祖先"来把上报 peer 分组 💡 例如设成 rack 时,来自同一 rack 的多份报告只算一份

OSD Settings(5 项,逐项列出)

配置项 类型 默认值 含义 调参建议
osd_heartbeat_interval 文档未明确 6(默认值取自 src/common/options/global.yaml.in @ v20.2.4;源文散文:每个 OSD 以"小于每 6 秒的随机间隔"检查邻居心跳) OSD 检查邻居心跳的间隔 💡 调小能更快发现故障但增加网络与 CPU 开销
osd_heartbeat_grace 文档未明确 20(默认值取自 src/common/options/global.yaml.in @ v20.2.4;源文散文:20 秒宽限期) 判定邻居 down 前等待心跳的宽限期 💡 网络抖动大的环境可适当加大;与 mon_osd_adjust_heartbeat_grace 联动
osd_mon_heartbeat_interval 文档未明确 30(默认值取自 src/common/options/global.yaml.in @ v20.2.4;源文散文:OSD 无法 peer 时每 30 秒 ping Monitor 一次) OSD 在无法 peer 时向 Monitor 请求最新 cluster map 的间隔 💡 一般不动
osd_mon_heartbeat_stat_stale 文档未明确 1_hr(= 3600 秒)(默认值取自 src/common/options/global.yaml.in @ v20.2.4) 与 OSD 向 Monitor 上报的 heartbeat 统计"过期"判定相关 💡 一般不动
osd_mon_report_interval 文档未明确 5(默认值取自 src/common/options/global.yaml.in @ v20.2.4;源文散文:提到最小上报间隔;并另有 osd mon report interval max 对应 120 秒的最大上报间隔) OSD 向 Monitor 上报的最小间隔(另:OSD 启动时在 5 秒内上报) 💡 一般不动;注意 osd_mon_report_interval_max 未出现在本页的 confval 列表中,只在散文里提到

11.7 关键时间参数速查(全部来自源文散文,可核对)

场景 时间 出处
OSD 检查邻居心跳的间隔 小于每 6 秒(随机间隔) OSDs Check Heartbeats
判定邻居 down 的 grace period 20 秒 OSDs Check Heartbeats
OSD 无法 peer 时 ping Monitor 的间隔 每 30 秒 OSDs Report Peering Failure
OSD 启动时上报 Monitor 的时限 5 秒内 OSDs Report Their Status
OSD 无论是否有变化都上报的最大间隔 每 120 秒 OSDs Report Their Status
判定 OSD down 所需的报告数 来自不同 subtree 的 2 份 OSDs Report Down OSDs
ceph pg dump_stuck 的 threshold 默认值 300 秒 placement-groups(Get Statistics for Stuck PGs)

引自:Configuring Monitor/OSD Interaction


文档未明确项

以下问题在本册覆盖的 25 个官方源文中找不到答案,本手册不做推测,如实列出:

  1. file / image / S3 object → RADOS object 的切分规则:RBD 的条带化(striping)、CephFS 的文件到对象映射、RGW 的对象布局,均由上层接口定义,相关页面(rbd/、cephfs/、radosgw/)不在本册范围内。本册只覆盖 RADOS 层(object → pool → PG → OSD)。

  2. CRUSH location 合法 type 集合的两处表述不一致:

    • crush-map.rst 的 note 列出 9 个:root、datacenter、room、row、pod、pdu、rack、chassis、host;
    • 同一文件 "Types and Buckets" 段与 crush-map-edits.rst 的 types 声明示例列出 12 个:osd、host、chassis、rack、row、pdu、pod、room、datacenter、zone、region、root。 两处不完全一致,官方文档未说明差异原因。
  3. CRUSH rule / CRUSH map 相关配置项的 type 与 default 值:pool-pg-config-ref.rst 中 osd_crush_chooseleaf_type、osd_crush_initial_weight、osd_pool_default_crush_rule 等只以 .. confval:: 出现,源文无散文描述其默认值。✅ 已补全:三项默认值已按 src/common/options/{global,osd}.yaml.in @ v20.2.4 填入 4.8 节表格(分别为 1、-1、-1)。(「类型」列仍保留「文档未明确」。)

  4. ceph osd crush weight-set create 的 mode=positional 的完整语义边界:源文只给了定义和"replica count 为 3 则有三个权重"的示例,未说明当 EC pool 或 pg_num 变化时 positional weight set 的行为。

  5. ceph pg dump_stuck 的 unclean 类别的定义:该参数出现在命令签名中,但源文未给出 unclean 的定义(只定义了 Inactive、Undersized、Stale)。

  6. ceph config set osd osd_pool_default_pg_num 缺少值:pool-pg-config-ref.rst 的示例命令写的是 ceph config set osd osd_pool_default_pg_num,后面没有值(疑为官方文档笔误)。

  7. 两个 FileStore 配置项的 Type 与 Default 不匹配:

    • filestore_dump_file::Type: Boolean,但 :Default: false,而 Description 是"Defines the file that transaction dumps are stored on"(应该是个路径);
    • filestore_kill_at::Type: String,但 :Default: false,而 Description 是"Injects a failure at the n-th opportunity"(应该是整数)。 疑为官方文档笔误,本手册照录原值并在此标注。
  8. recovery_priority 的取值范围不一致:pools.rst 明确范围为 -10 到 10(默认 0),但 placement-groups.rst 的用法示例使用了 20、19、30、15 等超出该范围的值。官方文档未澄清。

  9. osd_pool_default_size 是否等于 3:pools.rst 在 "Getting the Number of Object Replicas" 段说 "By default, Ceph maintains three replicas or copies, for a size of 3",但未明确断言 osd_pool_default_size 的默认值就是 3(该配置项在 pool-pg-config-ref.rst 中仅有 confval 指令)。✅ 已补全:src/common/options/global.yaml.in @ v20.2.4 中 osd_pool_default_size 的 default: 3,与源文散文一致。

  10. crush-device-class 在 jerasure / lrc 插件参数表中的默认值::Default: 后为空(isa / shec / clay 同样为空),文档未明确其默认行为(crush-map.rst 中 EC profile 的 crush-device-class 描述为 "default: none, which means that all devices are used",但这是 profile 层面的描述,与插件参数表层面是否等价,文档未明确)。

  11. clay 插件 crush-root 参数:参数表中列出(默认 default),但命令模板中未列。官方文档未说明这是遗漏还是有意为之。

  12. 所有通过 .. confval:: 引用、且源文散文中无描述的配置项的 type / default / description:包括 BlueStore 的 ~30 项(8.6 ~ 8.13 各表)、pool-pg 的 24 项(4.8)、mon-osd-interaction 的 20 项(11.6)、journal 的 7 项(8.17)。这些值在 rst 源文中不存在(在文档构建期从 Ceph 选项数据库拉取),本手册在对应表格中一律标注"文档未明确"。

✅ 已补全(80 处):现已按 src/common/options/*.yaml.in @ v20.2.4 权威默认值库逐项回填,覆盖 4.8 节 24 项、8.6 ~ 8.13 各表、8.17 journal 7 项、11.6 节 20 项,每处均标注来源文件名。 仍保留「文档未明确」的 6 项:crush-num-failure-domains、crush-device-class、crush-locality(均为 EC / CRUSH 命令行参数,不在 options/*.yaml.in 中);bluestore_block_path、bluestore_block_db_path、bluestore_block_wal_path(在 yaml.in 中但无 default: 字段,即无默认值)。

  1. balancer module 详细行为(11 章引用)、read-balancer、stretch-mode 的完整说明:这些页面不在本册 25 个源文范围内,本册仅在涉及处标注引用关系。

  2. ceph osd pool create 的 read_balance_score 字段:在 ceph osd pool ls detail 的示例输出中出现(值 4.00),但本册 25 个源文中没有任何页面解释该字段的含义。

  3. PG → OSD 映射的详细图解:placement-groups.rst 明确把 "Mapping PGs to OSDs" 链接到 architecture 页面,该页面不在本册范围内(应属于第 01 册)。本册第 1.4 节的演算示例为本手册 💡 补充,非官方文档内容。