注意

本文档适用于 Ceph 的开发版本。

数据去重

简介

由于额外的元数据管理和原始数据处理流程,在现有软件堆栈上应用数据去重并不容易。

在典型的去重系统中,输入源(即数据对象)通过分块算法被分割成多个块。然后,去重系统将每个块与之前存储在存储中的现有数据块进行比较。为此,去重系统采用一个指纹索引,其中存储每个块的哈希值,以便通过比较哈希值而不是搜索底层存储中的所有内容来轻松找到现有块。

要在 Ceph 上实现去重存在许多挑战。其中,有两个问题对于去重至关重要。首先是管理指纹索引的可扩展性;其次是确保新应用的去重元数据与现有元数据之间的兼容性很复杂。

关键思想

1. 内容哈希(双重哈希):每个客户端可以使用 CRUSH 找到一个对象 ID 对应的数据对象。通过 CRUSH,客户端知道对象在 Base 层的位置。通过对 Base 层的对象内容进行哈希,生成一个新的 OID(块 ID)。Chunk 层存储具有原始对象部分内容的新 OID。

客户端 1 -> OID=1 -> HASH(1的内容)=K -> OID=K -> CRUSH(K) -> 块的位置

2. 自包含对象:外部元数据设计使得与存储功能集成变得困难,因为现有的存储功能无法识别额外的外部数据结构。如果我们可以设计一个没有任何外部组件的数据去重系统,那么原始的存储功能就可以被重用。

更多细节请参阅 https://ieeexplore.ieee.org/document/8416369

设计

基于池的对象管理:我们定义了两个池。元数据池存储元数据对象,块池存储块对象。由于这两个池是根据目的和用法划分的,因此可以根据其不同的特性更有效地管理每个池。Base 池和块池可以根据其用法分别选择复制和纠删码之间的冗余方案,并且每个池可以根据所需的性能放置在不同的存储位置。

关于如何使用,请参阅 osd_internals/manifest.rst

使用模式

每个 Ceph 接口层都为去重和分层提供了独特的机遇和成本。

RadosGW

S3 大数据工作负载似乎是去重的好机会。这些对象往往是写入一次,读取为主的对象,不会发生部分覆盖。因此,提前进行指纹识别和去重是有意义的。

与 cephfs 和 rbd 不同,radosgw 有一个系统,用于在逻辑 s3 对象的头对象中存储显式元数据,以定位剩余部分。因此,radosgw 可以直接使用 refcounting 机制 (osd_internals/refcount.rst),而不需要 rados 对清单的直接支持。

RBD/Cephfs

RBD 和 CephFS 都使用确定性命名方案将块设备/文件数据分区到 rados 对象上。因此,重定向元数据需要作为 rados 的一部分包含在内,大概是透明的。

此外,与 radosgw 不同,rbd/cephfs rados 对象可能会看到覆盖。对于这些对象,我们不希望进行去重,而且我们也不想在热路径中支付写入延迟惩罚来这样做。因此,在后台对冷对象执行分层和去重可能更受青睐。

然而,一个重要的细节是,rbd 和 cephfs 工作负载通常都具有快照的使用特征。这意味着 rados 清单支持需要对快照的稳健支持。

RADOS 机制

有关 rados 重定向/块/去重支持的更多信息,请参阅 osd_internals/manifest.rst。有关 rados 引用计数支持的更多信息,请参阅 osd_internals/refcount.rst

状态和未来工作

目前,OSD 中存在对清单对象的一些初步支持以及一个去重工具。

RadosGW 数据仓库工作负载可能代表了此功能最大的机会,因此首要任务可能是向 refcount 池添加直接的指纹识别和重定向支持到 radosgw。

除了 radosgw,完成 OSD 中清单对象支持的工作,特别是与快照相关的工作,将是 rbd 和 cephfs 工作负载的下一步。

如何使用去重

  • 此功能具有高度实验性,可能会更改或删除。

Ceph 使用 RADOS 机制提供去重。下面我们解释如何执行去重。

先决条件

如果 Ceph 集群是从 Ceph 主线启动的,用户需要检查是否安装了包含 ceph-dedup-tool 的 ceph-test 包。

详细说明

用户可以使用 estimatesample-dedupchunk-scrubchunk-repair 操作使用 ceph-dedup-tool。为了给用户提供更好的便利性,我们通过 ceph-dedup-tool 启用了必要的操作,我们建议通过使用任何类型的脚本自由使用以下操作。

1. 使用 ceph-dedup-tool 估算目标池的存储空间节省率。

ceph-dedup-tool --op estimate
  --pool [BASE_POOL]
  --chunk-size [CHUNK_SIZE]
  --chunk-algorithm [fixed|fastcdc]
  --fingerprint-algorithm [sha1|sha256|sha512]
  --max-thread [THREAD_COUNT]

此 CLI 命令将显示当对池应用去重时可以节省多少存储空间。如果节省的空间量高于用户的预期,则该池可能值得执行去重。用户应指定 BASE_POOL,去重目标对象存储在其中。用户还需要运行多次 ceph-dedup-tool,并使用不同的 chunk_size 来找到最佳的块大小。请注意,在 fastcdc 分块算法(不固定)的情况下,最佳值可能因每个对象的内容而异。

示例输出

{
  "chunk_algo": "fastcdc",
  "chunk_sizes": [
    {
      "target_chunk_size": 8192,
      "dedup_bytes_ratio": 0.4897049
      "dedup_object_ratio": 34.567315
      "chunk_size_average": 64439,
      "chunk_size_stddev": 33620
    }
  ],
  "summary": {
    "examined_objects": 95,
    "examined_bytes": 214968649
  }
}

上面是执行 estimate 时的示例输出。target_chunk_size 与用户给定的 chunk_size 相同。dedup_bytes_ratio 显示从检查的字节中冗余了多少字节。例如,1 - dedup_bytes_ratio 表示节省的存储空间百分比。dedup_object_ratio 是生成的块对象 / examined_objectschunk_size_average 表示执行 CDC 时平均划分的块大小——这可能与 target_chunk_size 不同,因为 CDC 根据内容生成不同的块边界。chunk_size_stddev 表示块大小的标准偏差。

2. 创建块池。

ceph osd pool create [CHUNK_POOL]

3. 运行去重命令(有两种方式)。

  • sample-dedup

ceph-dedup-tool --op sample-dedup
  --pool [BASE_POOL]
  --chunk-pool [CHUNK_POOL]
  --chunk-size [CHUNK_SIZE]
  --chunk-algorithm [fastcdc]
  --fingerprint-algorithm [sha1|sha256|sha512]
  --chunk-dedup-threshold [THRESHOLD]
  --max-thread [THREAD_COUNT]
  --sampling-ratio [SAMPLE_RATIO]
  --wakeup-period [WAKEUP_PERIOD]
  --loop
  --snap

sample-dedup 命令会生成由 THREAD_COUNT 指定的线程,以对 BASE_POOL 上的对象进行去重。根据采样率——如果 SAMPLE_RATIO 为 100,则进行全面搜索,线程会在迭代期间选择性地执行去重,如果块冗余超过 THRESHOLD 次。如果设置了 --loop,线程将在 WAKEUP_PERIOD 后唤醒。如果没有,线程将在一次迭代后退出。

示例输出

$ bin/ceph df
--- RAW STORAGE ---
CLASS     SIZE    AVAIL     USED  RAW USED  %RAW USED
ssd    303 GiB  294 GiB  9.0 GiB   9.0 GiB       2.99
TOTAL  303 GiB  294 GiB  9.0 GiB   9.0 GiB       2.99

--- POOLS ---
POOL   ID  PGS   STORED  OBJECTS     USED  %USED  MAX AVAIL
.mgr    1    1  577 KiB        2  1.7 MiB      0     97 GiB
base    2   32  2.0 GiB      517  6.0 GiB   2.02     97 GiB
chunk   3   32   0  B          0    0   B      0     97 GiB

$ bin/ceph-dedup-tool --op sample-dedup --pool base --chunk-pool chunk
  --fingerprint-algorithm sha1 --chunk-algorithm fastcdc --loop --sampling-ratio 100
  --chunk-dedup-threshold 2 --chunk-size 8192 --max-thread 4 --wakeup-period 60

$ bin/ceph df
--- RAW STORAGE ---
CLASS     SIZE    AVAIL     USED  RAW USED  %RAW USED
ssd    303 GiB  298 GiB  5.4 GiB   5.4 GiB       1.80
TOTAL  303 GiB  298 GiB  5.4 GiB   5.4 GiB       1.80

--- POOLS ---
POOL   ID  PGS   STORED  OBJECTS     USED  %USED  MAX AVAIL
.mgr    1    1  577 KiB        2  1.7 MiB      0     98 GiB
base    2   32  452 MiB      262  1.3 GiB   0.50     98 GiB
chunk   3   32  258 MiB   25.91k  938 MiB   0.31     98 GiB
  • object dedup

ceph-dedup-tool --op object-dedup
  --pool [BASE_POOL]
  --object [OID]
  --chunk-pool [CHUNK_POOL]
  --fingerprint-algorithm [sha1|sha256|sha512]
  --dedup-cdc-chunk-size [CHUNK_SIZE]

object-dedup 命令触发对由 OID 指定的 RADOS 对象进行去重。必须指定上面显示的所有参数。CHUNK_SIZE 应取自上面步骤 1 的结果。请注意,执行此命令时,默认会设置 fastcdc,其他参数(例如 fingerprint-algorithmCHUNK_SIZE)将设置为池的默认值。去重后的对象将出现在块池中。如果对象随时间发生变异,用户需要重新运行 object-dedup,因为块边界应根据更新的内容重新计算。如果目标对象已快照,用户需要指定 snap。去重完成后,BASE_POOL 中的目标对象大小为零(已收回),并生成块对象——这些对象出现在 CHUNK_POOL 中。

4. 读取/写入 I/O

在步骤 3 之后,用户无需考虑任何有关 I/O 的问题。去重后的对象与现有 RADOS 操作完全兼容。

5. 运行 scrub 来修复引用计数

在处理去重 RADOS 对象的引用计数时,引用不匹配在极少数情况下可能由于误报而发生。这些不匹配将通过定期 scrub 池来修复

ceph-dedup-tool --op chunk-scrub
  --chunk-pool [CHUNK_POOL]
  --pool [POOL]
  --max-thread [THREAD_COUNT]

chunk-scrub 命令识别元数据对象和块对象之间的引用不匹配。chunk-pool 参数告诉 ceph-dedup-tool 目标块对象位于何处。

示例输出

通过使用 chunk-get-ref 将引用 (dummy-obj) 插入到块对象 (2ac67f70d3dd187f8f332bb1391f61d4e5c9baae) 中,故意创建引用不匹配。

$ bin/ceph-dedup-tool --op dump-chunk-refs --chunk-pool chunk --object 2ac67f70d3dd187f8f332bb1391f61d4e5c9baae
{
  "type": "by_object",
  "count": 2,
    "refs": [
    {
      "oid": "testfile2",
            "key": "",
            "snapid": -2,
            "hash": 2905889452,
            "max": 0,
            "pool": 2,
            "namespace": ""
    },
    {
      "oid": "dummy-obj",
      "key": "",
      "snapid": -2,
      "hash": 1203585162,
      "max": 0,
      "pool": 2,
      "namespace": ""
    }
  ]
}

$ bin/ceph-dedup-tool --op chunk-scrub --chunk-pool chunk --max-thread 10
10 seconds is set as report period by default
join
join
2ac67f70d3dd187f8f332bb1391f61d4e5c9baae
--done--
2ac67f70d3dd187f8f332bb1391f61d4e5c9baae ref 10:5102bde2:::dummy-obj:head: referencing pool does not exist
--done--
 Total object : 1
 Examined object : 1
 Damaged object : 1

6. 修复不匹配的块引用

如果在 chunk-scrub 之后发生任何引用不匹配,建议执行 chunk-repair 操作来修复引用不匹配。chunk-repair 操作有助于解决引用不匹配并恢复一致性。

ceph-dedup-tool --op chunk-repair
  --chunk-pool [CHUNK_POOL_NAME]
  --object [CHUNK_OID]
  --target-ref [TARGET_OID]
  --target-ref-pool-id [TARGET_POOL_ID]

chunk-repair 修复 target-ref,这是 object 的错误引用。为了正确修复它,用户必须输入正确的 TARGET_OIDTARGET_POOL_ID

$ bin/ceph-dedup-tool --op chunk-repair --chunk-pool chunk --object 2ac67f70d3dd187f8f332bb1391f61d4e5c9baae --target-ref dummy-obj --target-ref-pool-id 10
2ac67f70d3dd187f8f332bb1391f61d4e5c9baae has 1 references for dummy-obj
dummy-obj has 0 references for 2ac67f70d3dd187f8f332bb1391f61d4e5c9baae
 fix dangling reference from 1 to 0

$ bin/ceph-dedup-tool --op dump-chunk-refs --chunk-pool chunk --object 2ac67f70d3dd187f8f332bb1391f61d4e5c9baae
{
  "type": "by_object",
  "count": 1,
  "refs": [
    {
      "oid": "testfile2",
      "key": "",
      "snapid": -2,
      "hash": 2905889452,
      "max": 0,
      "pool": 2,
      "namespace": ""
    }
  ]
}

由 Ceph 基金会为您呈现

Ceph 文档是由非营利性 Ceph 基金会 资助和托管的社区资源。如果您希望支持这项工作和我们的其他努力,请考虑 立即加入