背景
Ceph 社区最近冻结了即将发布的 Ceph Reef 版本,今天我们研究一下 Reef 在 10 个节点、60 个 NVMe 驱动器集群上的 RGW 性能。
我们部署了 20 个 RGW 实例和 200 个 hsbench S3 客户端来在 512 个bucket 中执行高度并行的工作负载。
在多次的测试中,我们发现Reef 通常比 Quincy 快 1-5%。
在 3X 复制时,4MB GET 的速度大约为 53GB/s,4MB PUT 的速度为 18.6GB/s。
这两种情况下我们都达到了网络的瓶颈。Reef 在 3X 复制时也实现了大约 312K 4KB GET/s 和 178K 4KB PUT/s。
在大对象测试中,不同版本之间的 CPU 使用率是相等的。然而,在 4KB PUT 和 GET 测试中,Reef 使用的 CPU 资源比 Quincy 多 23%。随着 Reef 版本发布的临近,我们将时刻关注这一点。
最后,我们注意到 OSD 和 RGW 之间的总 CPU 使用率存在显着偏差,具体取决于正在运行的工作负载类型。同时这也对我们的硬件选型有很大的影响。
介绍
在本系列的第 1 部分(https://ceph.io/en/news/blog/2023/reef-freeze-rbd-performance/)中,我们研究了如何使用 CBT (https://github.com/ceph/cbt)工具来了解 RBD 性能在多个 Ceph 版本之间的变化。
在第 1 部分中,CBT 使用 Jens Axboe 的 fio (https://github.com/axboe/fio)工具针对 RBD 镜像进行性能测试。CBT 还可以利用 S3 基准测试工具来测试 RGW。
在之前的 Ceph 版本中,我们已经使用此功能来对性能进行测试。结合 CBT、hsbench (https://github.com/markhpc/s3bench.git) S3 基准测试和 git bisect,我们发现有两个代码的提交更新会损害 Ceph Pacific 版本中的 RGW 性能和效率。
一旦确定了这些代码问题,Ceph 开发人员就能很快进行修复,使性能和 CPU 使用率恢复到我们在 Nautilus 中看到的水平。
在本文中,我们将使用相同的工具来测试 Reef 在冻结后的性能。
集群设置
Nodes | 10 x Dell PowerEdge R6515 |
CPU | 1 x AMD EPYC 7742 64C/128T |
Memory | 128GiB DDR4 |
Network | 1 x 100GbE Mellanox ConnectX-6 |
NVMe | 6 x 3.84TB Samsung PM983 |
OS Version | CentOS Stream release 8 |
Ceph Version 1 | Quincy v17.2.5 (built from source) |
Ceph Version 2 | Reef 9d5a260e (built from source) |
所有节点都连接到同一台 Juniper QFX5200 交换机上,并通过单个 100GbE QSFP28 连接。同时我们部署了 Ceph 并使用 CBT (https://github.com/ceph/cbt/)启动了 fio 测试。
除非特别说明,否则每个节点都安装 6 个 OSD,同时 fio 运行 6 个 librbd 类型的进程。
基于英特尔的系统可以配置 "latency-performance" 以及 "network-latency" 来对系统进行调优。这避免与 CPU C/P 状态转换带来延迟。
基于 AMD Rome 的系统在这方面的调优并没有太大的改变,而且我们还没有确认 tuned 实际上限制了 C/P 状态转换,但是对于这些测试,tuned 配置文件仍然设置为 “network-latency”。
测试设置
CBT 需要针对所部署的 Ceph 集群调整几个参数。每个 OSD 都配置 8GB 内存,并且在禁用 cephx 的情况下使用 msgr V1。没有对 RGW 进行特殊调整。hsbench (https://github.com/markhpc/hsbench.git) 用于 S3 测试。
每个节点都启动了 20 个 hsbench 进程,每个进程都连接到一个不同的 RGW 实例。
因此,每个 RGW 进程都有一个关联的 hsbench 进程从不同的节点连接到它(总共 200 个 hsbench 进程)。每个 hsbench 进程都配置为使用 64 个线程并使用 512 个公共的 bucket 。
同时,也禁用了部分 Ceph 的后台服务进程,如 scrub、deep scrub、pg autoscaling 和 pg balancing。在所有 RGW 池中使用了 3X 复制。
数据和索引池各有 8192 个 PG,而 root,control,meta,以及 log 池各有 64 个 PG。分别使用 200 万个 4MiB 对象和 2 亿个 4KiB 对象为 quincy 和 reef 运行了两个单独的测试 case。
每个测试 case 按以下顺序运行:
Test | Description |
cxi | Clear all existing objects from buckets, delete buckets, initialize buckets |
p | Put objects into buckets |
l | List objects in buckets |
g | Get objects from buckets |
d | Delete objects from buckets |
4MB PUT
Reef 比 Quincy 要快一些,在所有三个测试中保持大约 2% 的领先优势。RGW 和 OSD 进程的 CPU 使用率在这两个测试中是相似的。
令人惊讶的是,Quincy 和 Reef 都显示 RGW CPU 使用在 5 个核心左右,但当测试大约 2-3 分钟后翻倍至 10 个核心。
在本系列的第 1 部分(https://ceph.io/en/news/blog/2023/reef-freeze-rbd-performance/)中,我们研究了 4MB 的顺序写入吞吐量,发现使用 librbd,我们可以保持大约 25GB/s(复制时为 75GB/s)。
为什么我们在这里看到 4MB PUT 的速度只有大约 18.5GB/s?很大一部分原因是我们传输了两次数据。一次从客户端到 RGW,然后再次从 RGW 到 OSD。一些传输将发生在位于同一节点上的 RGW 实例或 OSD 上。
同时,我们也不能忽略复制,因为至少有 2 次从主 OSD 到次 OSD 的额外传输,这些传输必须落在其他节点上。
Probability of 2 transfers = 1/10 * 1/10 = 1/100
Probability of 4 transfers = 9/10 * 9/10 = 81/100
Probability of 3 transfer = 1 - 1/100 - 81/100 = 18/100
相对于最大网络吞吐量,我们应该期望的一般最佳情况下的性能是:
100 / (2 * (1/100) + 4 * (81/100) + 3 * (18/100)) = 26.31%
在 librbd 的情况下,我们总是以 3X 复制进行 3 次传输(1 次到主 OSD,然后 1 次到另外两个次 OSD):
100 / 3 = 33.3%
用上述的结果乘以我们的期望性能:
25GB/s * 26.31/33.3 = 19.75GB/s
看起来我们做的比 RBD 稍微差一点,但一旦考虑到额外的网络开销,那么则差不了太多。
4MB LIST
LIST测试的完成时间完全由对象的数量决定。在这种情况下,因为我们总共只有 2M 个对象分布在 512 个桶中,所以单个桶列 LIST 时间非常短。Reef 再次与 Quincy 一样快或略快。
4MB GET
在此测试中,RGW 和 OSD 进程的 CPU 使用率非常一致。与 PUT 测试一样,我们必须考虑必须传输数据的次数。
因为我们使用的是复制,所以我们总是只将数据从 OSD 传输到 RGW 一次,然后再传输到客户端。
Probability of 0 transfers = 1/10 * 1/10 = 1/100
Probability of 2 transfers = 9/10 * 9/10 = 81/100
Probability of 1 transfer = 1 - 1/100 - 81/100 = 18/100
100 / (0 * (1/100) + 2 * (81/100) + 1 * (18/100)) = 55.6%
在这里,我们为 Quincy 维持约 51GB/s,为 Reef 维持约 53GB/s。这非常接近这些 100GbE NIC 应该能够提供的最大值。
4MB DELETE
这里的 DELETE 测试“吞吐量”数字似乎高得惊人,但是 DELETE 性能主要取决于被删除对象的数量而不是对象的大小,并且实际删除过程是异步的。话虽如此,Reef 的平均速度似乎再次略快于 Quincy。
在第 3 次测试迭代中,两者似乎都具有更快的删除速度。其原因尚不清楚。
4KB PUT
与之前的测试一样,Reef 比 Quincy 快一点。两者的 OSD CPU 使用率大致相同,但 RGW CPU 使用率似乎上升了大约 15%。
从好的方面来说,在 Quincy 的 RGW 中,每次测试迭代结束时 CPU 消耗都会急剧上升,这在 Reef 中没有发生。
这里的一个重点是 OSD 和 RGW 都尽可能地保持 180K PUT/s。RGW 要求每个 PUT 操作到主 OSD 的 3 次往返(2 次同步,1 次异步)以正确写入数据并保持桶索引正确同步。
4KB LIST
与之前的测试相比,对象越多,桶 LIST 时间就越长。Reef 在第一次迭代中只是稍微快一点,但 Quincy 和 Reef 在其他方面差不多。
4KB GET
首先,Reef 中的 GET 性能比 Quincy 中更快、更稳定。话虽如此,这里有一些非常有趣的行为。
在 Quincy 中,测试的前 2 分钟主要是极高的 CPU 使用率。一旦完成,稳定状态的 CPU 使用率比 Reef 低很多。OSD CPU 使用率同样较低。
在此测试中,Reef 在 RGW 端使用了大约 10-20% 的 CPU,在 OSD 端使用了大约 17-23% 的 CPU。Reef 的行为似乎更加一致。
虽然 RGW CPU 使用率在测试开始时较高,但远不及 Quincy 测试中发生的变化。
4KB DELETE
最后,Reef 在删除 4K 对象时平均要快 5%。同样,这是一个异步过程,因此结果可能与 RGW 在后台删除内容的速度不匹配。
结论
这些测试仅展示了 RGW 性能特征的一小部分。我们没有在本文中查看单操作延迟,或尝试不同的客户端来进行测试。尽管如此,在我们的测试中,我们看到了两种趋势:
1. Reef 的性能比 Quincy 高出约 1-5%,并且在所有测试中都相当一致,并且发生在大多数测试迭代中。
2. 在某些测试中,尤其是 4K GET 测试中,Reef 的 CPU 使用率高于 Quincy。这在 RGW 中表现的很明显,另外我们在 OSD 中也发现了这个问题。我们希望在发布 Reef 之前能跟进解决这个问题。
关于这些结果,还有一些额外的事情需要注意。
在之前的测试中,我们为一个 60 NVMe 集群使用了 10 个 RGW 实例。
在这些测试中,我们使用了 20 个 RGW 实例,发现小对象 PUT 和 GET 测试的性能明显高于我们之前看到的性能。很有可能将 RGW 数量增加到更高的比率,也许每 2 个甚至每 1 个 OSD 有 1 个 RGW 实例可能会产生更好的小对象性能。
第二个有趣的事情是,在这些测试中,CPU 消耗差异很大,RGW 和 OSD 进程之间的 CPU 消耗比率也发生了变化。
如果我们查看 Reef 结果并计算 RGW 和 OSD 使用的大概核心数,我们得到:
4MB
Test | Result | Total RGW Cores | Total OSD Cores | Total Cores | RGW/OSD Core Ratio |
4MB PUT | 18.6GB/s | 184 Cores | 152 Cores | 336 Cores | 6/5 |
4MB GET | 53GB/s | 88 Cores | 55 Cores | 143 Cores | 8/5 |
4KB
Test | Result | Total RGW Cores | Total OSD Cores | Total Cores | RGW/OSD Core Ratio |
4KB PUT | 178K IOPS | 269 Cores | 475 Cores | 744 Cores | 11/20 |
4KB GET | 312K IOPS | 302 Cores | 102 Cores | 404 Cores | 3/1 |
除了了解这些测试中使用的内核数量之外,还可以从这些结果中获取另外一个结论:在容器中运行 Ceph 是非常不错的。
因为 Ceph 的最新版本有大量的外部依赖项,这使包管理变的非常麻烦。除此之外,很多用户已经采用了容器化管理应用程序,并希望以同样的方式部署 ceph。
对此的一个需求是,大家希望在容器级别分配 Ceph 守护进程静态资源分配:一定数量的内存和一定数量的内核来运行。这是也是可能比较麻烦的地方。
关于内存配额的问题我们放到以后再进行讨论,但简而言之:在非专门设计的应用程序中保证内存使用是极其困难的。
默认情况下,我们能做的就是让 Ceph 进程监视它们的映射内存并自动调整非必要内存使用量。因为 Ceph 包含了底层内存自动调整和缓存管理代码。
限制 CPU 核心数通常不会导致与限制内存相同的问题。在这些测试中,PUT 和 GET 之间的差距接近 6 倍。
如果我们只针对一种场景进行优化,那么我们要么会影响到另一种场景,要么会有大量多余的 CPU 核心在不同的时间点处于空闲状态。
如果我们需要更多的 4K GET 吞吐量,那么也许在 Ceph 的某个未来版本中,我们可以按需启动或关闭 RGW 容器。
但就目前而言,我们需要灵活变通从而实现 Ceph RGW 存储的高性能和高效率。