Reddit 挑战已接受 - 使用 NVMe 是否可以实现 10k IOPS?

大家好,Ceph 社区!又到了发布博客文章的时候了!最近,Reddit 的 ceph 子版块中的一位用户 询问 Ceph 是否能够在单个客户端的组合随机读/写 FIO 工作负载中提供 10K IOPS。该配置包括 6 个节点,每个节点配备 2 个 4TB FireCuda NVMe 驱动器。他们想知道是否有人愿意基准测试类似的配置并报告结果。在 Clyso,我们正在积极努力改进 Ceph 代码以实现更高的性能。我们有自己的测试和配置来评估对代码的更改,但碰巧我们进行工作的一个地方(上游 Ceph 社区性能实验室!)似乎非常适合测试该用户的请求。我们决定花几个小时的时间尝试一下。我们的朋友 u/DividedbyPi 在 45drives.com 上写道,他们也会尝试一下,并在未来几周在他们的 YouTube 频道上报告结果。我们认为这可能是一种从多个供应商那里获得结果的有趣方式。让我们看看会发生什么!
致谢
首先,感谢 Clyso 为这项工作提供资金,以造福 Ceph 社区。同时感谢 IBM / Red Hat 和 Samsung 为上游 Ceph 社区提供用于此测试的硬件。感谢所有为使 Ceph 变得伟大的 Ceph 开发人员。最后,感谢 /u/nNaz 的联系和推动新的 Ceph 基准测试!
用户设置
用户在本文的原始 Reddit 帖子中指出,他们拥有 6 个节点,每个节点都有 2 个 4TB Seagate FireCuda NVMe 驱动器和 64GB 内存。他们没有提到他们拥有什么样的 CPU 或网络,但两者对于此测试都可能很重要。方便的是,他们已经有一个想要评估的 FIO 调用
fio --randrepeat=1 --ioengine=libaio --direct=1 --gtod_reduce=1 --name=test --filename=test --bs=4k --iodepth=64 --size=150G --readwrite=randrw --rwmixread=75
虽然我们在上游实验室没有 FireCuda 驱动器,但我们确实拥有具有多个 4TB Samsung PM983 驱动器和每个节点 128GB 内存的节点。
集群设置
| 节点 | 10 x Dell PowerEdge R6515 |
|---|---|
| CPU | 1 x AMD EPYC 7742 64C/128T |
| 内存 | 128GiB DDR4 |
| 网络 | 1 x 100GbE Mellanox ConnectX-6 |
| NVMe | 6 x 4TB Samsung PM983 |
| 操作系统版本 | CentOS Stream release 8 |
| Ceph 版本 1 | Pacific v16.2.13 (从源代码构建) |
| Ceph 版本 2 | Quincy v17.2.6 (从源代码构建) |
| Ceph 版本 3 | Reef v18.1.0 (从源代码构建) |
为了尽可能地匹配用户的设置,我们只使用了集群中的 6 个节点,每个节点在单个 NVMe 驱动器上运行 2 个 OSD 用于 Ceph。其余节点用作客户端节点。所有节点都位于同一 Juniper QFX5200 交换机上,并通过单个 100GbE QSFP28 链路连接。Ceph 使用 CBT 部署,并启动了 FIO 测试。在 Intel 系统上一个重要的操作系统级别优化是将 TuneD 配置文件设置为“latency-performance”或“network-latency”。这主要通过避免与 CPU C/P 状态转换相关的延迟峰值来帮助。基于 AMD Rome 的系统似乎不太敏感,并且我尚未确认 tuned 实际上是否限制了 C/P 状态转换,但是,tuned 配置文件仍然设置为“network-latency”用于这些测试。
测试设置
CBT 被配置为使用一些修改后的设置来部署 Ceph,而不是默认设置。每个 OSD 都被赋予了 8GB 的内存目标(我们认为这很合理,因为用户的节点有 64GB 用于 2 个 OSD)。使用了内核 RBD 卷,Msgr V1 和 cephx 被禁用。FIO 被配置为首先使用大写预填充 RBD 卷,然后是用户的随机读/写测试。某些后台进程,例如 scrub、deep scrub、pg autoscaling 和 pg balancing 被禁用。使用了具有静态 4096 PGs(高于通常建议的值)和 3x 复制的 RBD 池。用户只要求使用单个 FIO 卷和单个 150GB 文件进行测试。我们按要求进行了测试,但为了稍微推动集群,还使用 16 个单独的 FIO 进程写入跨 4 个客户端节点上的专用 RBD 卷,每个卷 16GB。
为了匹配用户的 FIO 设置,必须将对 gtod_reduce 选项的支持添加到 cbt 的 FIO 基准测试包装器中。gtod_reduce 选项可以通过大幅减少 FIO 需要发出的 gettimeofday(2) 调用来提高性能,但它也会禁用某些功能,例如测试期间的 op 延迟信息收集。为了评估影响,我们同时启用和禁用了 gtod_reduce 进行了测试
根据结果,我们决定禁用 gtod_reduce,以便我们也可以报告延迟数据。请注意,通过启用此 FIO 选项可以提高约 3-4% 的性能。除此之外,所有其他选项要么在 CBT 中可用,要么已经是 FIO 中的默认选项。例如,CBT YAML 文件基准测试部分包含以下设置用于单客户端测试
benchmarks:
fio:
client_endpoints: 'fiotest'
op_size: [4096]
iodepth: [64]
size: 153600 # data set size per fio instance in KB
mode: ['randrw']
rwmixread: 75
procs_per_endpoint: [1]
osd_ra: [4096]
log_avg_msec: 100
cmd_path: '/usr/local/bin/fio'
这产生了一个类似于用户的 FIO 行,但有一些差异主要与记录测试过程中的 IOPS/延迟数据有关
fio --ioengine=libaio --direct=1 --bs=4096 --iodepth=64 --rw=randrw --rwmixread=75 --rwmixwrite=25 --size=153600M --numjobs=1 --name=/tmp/cbt/mnt/cbt-rbd-kernel/0/`hostname -f`-0-0 --write_iops_log=/tmp/cbt/00000000/Fio/output.0 --write_bw_log=/tmp/cbt/00000000/Fio/output.0 --write_lat_log=/tmp/cbt/00000000/Fio/output.0 --log_avg_msec=100 --output-format=json,normal > /tmp/cbt/00000000/Fio/output.0
单客户端和多客户端 IOPS
Ceph 不仅能够在此混合工作负载中实现 10K IOPS,在单客户端测试中甚至快了一个数量级。我们从单个客户端针对这个小型 12 OSD 集群实现了大约 92K 随机读和 31K 随机写 IOPS。然而,我们运行多客户端测试的原因是展示这个小集群在服务其他客户端时有多少余量。使用相同的混合工作负载和 16 个客户端,我们实现了超过 500K 随机读和大约 170K 随机写 IOPS,仅使用 12 个 NVMe 后端 OSD。在多客户端测试中,Quincy 和 Reef 分别比 Pacific 提高了大约 6% 和 9% 的性能。启用 gtod_reduce 可以提高另外 3-4% 的性能。
单客户端和多客户端 CPU 使用率
我们在 Clyso 经常看到的一个问题是,预先存在的 NVMe 后端 Ceph 集群中的 CPU 尺寸不足以满足 NVMe 驱动器。为了服务单个客户端工作负载,每个 OSD 消耗大约 3-4 个 AMD EPYC 核心。为了服务多客户端工作负载,每个 OSD 消耗大约 11-12 个核心!也就是说,即使每个节点只有 2 个 OSD,每个节点也需要 24 个 EPYC 核心才能实现这些驱动器的最大性能。更快的驱动器可能需要更大/更快的处理器。是什么消耗了所有的 CPU?
在我之前的 博客文章 关于 Ceph CPU 伸缩中,我提到有 4 个主要的 CPU 消耗者
| 名称 | 数量 |
|---|---|
| OSD 工作线程 | 16 |
| 异步 Messenger 线程 | 3 |
| Bluestore KV 同步线程 | 1 |
| Bluster “Finisher” 线程 | 1 |
在某些情况下,RocksDB 压缩线程也可能定期使用 CPU 资源。BlueStore KV 同步线程很容易成为小随机写的瓶颈,尤其是在使用较低时钟 CPU 时。然而,总体的 CPU 消耗主要是在 OSD 工作线程和异步 messenger 线程中完成的。这包括 crc 检查、编码/解码、内存分配等。
单客户端和多客户端 Cycles/OP
编辑:之前报告的 cycles/OP 数字被严重夸大了,因为 cycles/OP 解析代码中的一个错误。这使得 OSD 看起来比实际效率低得多。经过验证,更正后的数字在通过使用聚合平均 CPU 消耗量和 IOPS 而不是聚合周期和 OPS 计算得出的粗略预期数字的 ~14-17% 以内。我们认为剩余的差异主要是由于时钟速度随时间的变化(频率缩放)以及 CPU 消耗报告方式在 collectl 中的差异。感谢 Dan van der Ster 发现此问题。
在单客户端和多客户端测试中,效率似乎从发布到发布略有提高,这很好。然而,一个更有趣的结论是,我们在高负载的多客户端测试中处理数据的效率比低负载的单客户端测试中更高。为什么会这样?在上一节中,我们讨论了 OSD 中大部分工作是在 OSD 工作线程和异步 messenger 线程中完成的。当 OSD 接收到新的 IO 时,它首先由与相应网络连接关联的异步 messenger 线程处理并移动到用户空间。然后,它被放置到给定 OSD 分片的调度工作队列中。如果队列之前为空,则与该分片关联的所有线程都会唤醒并且不会再次休眠,直到分片的工作队列为空。当只有一个客户端执行 IO 时,集群的负载明显较小,并且分片队列通常会在短时间内为空。线程会不断唤醒和休眠。当集群负载很高时,队列更有可能有工作要做,因此线程会花更少的时间在睡眠和唤醒上。相反,他们会花更多的时间做实际工作。这是 Clyso 正在积极努力改进 ceph-osd 代码的领域。
单客户端和多客户端平均延迟
Ceph 能够在单客户端测试中保持亚毫秒级的平均读写延迟。在多客户端测试中,我们看到了 Pacific、Quincy 和 Reef 之间的不同行为。Pacific 显示最低的读延迟和最高的写延迟,而 Reef 显示读延迟略有增加,但写延迟大幅降低。Quincy 的行为介于两者之间,但更接近 Reef 而不是 Pacific。
单客户端和多客户端 99.9% 延迟
正如预期的那样,99.9% 的延迟略高。对于读,我们可以达到略低于 1.3 毫秒,对于写,可以达到大约 2.1-2.3 毫秒,具体取决于发布版本。多客户端测试中的尾部延迟明显更高,但是 Quincy 和尤其是 Reef 显示出比 Pacific 在此测试中低得多的写尾部延迟。
结论
那么最终我们做得怎么样?目标是在这个混合读/写 FIO 工作负载中达到 10K IOPS,读 75%,写 25%。我假设这意味着目标是 7500 读 IOPS 和 2500 写 IOPS。让我们比较一下我们做得如何
单客户端 IOPS
| 发布版本 | 读目标 | 读 IOPS | 改进 | 写目标 | 写 IOPS | 改进 |
|---|---|---|---|---|---|---|
| v16.2.13 | 7500 | 88540 | 11.8X | 2500 | 30509 | 12.2X |
| v17.2.6 | 7500 | 89032 | 11.9X | 2500 | 30644 | 12.3X |
| v18.1.0 | 7500 | 89669 | 12.0X | 2500 | 30940 | 12.4X |
只是为了好玩……多客户端 IOPS
| 发布版本 | 读目标 | 读 IOPS | 改进 | 写目标 | 写 IOPS | 改进 |
|---|---|---|---|---|---|---|
| v16.2.13 | 7500 | 490773 | 65.4X | 2500 | 163649 | 65.5X |
| v17.2.6 | 7500 | 521475 | 69.5X | 2500 | 173887 | 69.6X |
| v18.1.0 | 7500 | 535611 | 71.4X | 2500 | 178601 | 71.4X |
是的,我相信我们已经完成了挑战!我还怀疑,使用更快的 NVMe 驱动器可以进一步提高结果(这些是几年前的价值型企业级驱动器!)。也许这里的一些有进取心的读者会接受挑战并提供更高的 Ceph 结果?我很期待找到答案!感谢您的阅读,如果您有任何问题或想进一步讨论 Ceph 性能,请随时 联系。