Battle of the Mallocators
Mallocator 大战: RocksDB、InnoDB 以及 glibc malloc, jemalloc, tcmalloc 的性能对比
2025年4月11日,星期五
Mallocator 大战
如果你使用 RocksDB 并且想避免 OOM,那么请使用 jemalloc 或 tcmalloc,避免使用 glibc malloc。这在 2015 年是正确的,在 2025 年仍然如此(参见这里)。问题在于 RocksDB 可能会成为 allocator 的压力测试,因为它在从存储读取数据块时会进行分配(调用 malloc),然后在驱逐数据块时进行释放(调用 free)。这些分配的生命周期非常不同,因为某些数据块会在缓存中保留很长时间,这会导致使用 glibc malloc 时 RSS 比预期大得多。幸运的是,jemalloc 和 tcmalloc 在容忍这种分配模式方面表现更好,不会导致 RSS 过大。我还没有注意到 InnoDB 存在类似的问题,部分原因是它在进程启动时会为 InnoDB 缓冲池进行一些大的分配,并且不会为从存储读取的每个数据块都进行 malloc/free 操作。
最近,一位 MySQL 性能专家 Dimitri Kravtchuk 声称,使用 InnoDB 和 jemalloc 时,RSS 或 VSZ 可能会变得过大。我不知道他的设置的所有细节,而且我没有在我的设置上重现他的结果。公平地说,我在这里展示了 InnoDB + jemalloc 的 VSZ 可能比你预期的要大,但这并不是一个问题,这只是 jemalloc 的一个可能会令人困惑的特性。但是 InnoDB 使用 jemalloc 的 RSS 与我从 tcmalloc 获得的结果相似。
总结:
-
对于使用 glibc malloc 的 MyRocks,当 RocksDB 缓冲池大小为 50G 时,我在一台具有 128G 内存的服务器上遇到了 OOM。我可以通过将缓冲池大小设置为 30G 到 40G 之间来避免 OOM。在那台主机上,我通常将 jemalloc 与 MyRocks 和 100G 缓冲池一起使用。
-
关于峰值 RSS:
- 对于 InnoDB,所有 allocators 的峰值 RSS 相似,并且峰值 RSS 比 InnoDB 缓冲池大约大 1.06 倍。
- 对于 MyRocks,jemalloc 的峰值 RSS 最小,tcmalloc 略大,而 glibc malloc 则过大。对于 (jemalloc, tcmalloc, glibc malloc),分别是 10G MyRocks 缓冲池的 (1.22, 1.31, 3.62) 倍。我怀疑如果我使用 80G 缓冲池,jemalloc 和 tcmalloc 的这些比率会更小。
-
对于性能,使用 jemalloc 和 tcmalloc 的 QPS 略好于使用 glibc malloc:
- 对于 InnoDB:[jemalloc, tcmalloc] 比 glibc malloc 多 [2.5%, 3.5%] 的 QPS。
- 对于 MyRocks:[jemalloc, tcmalloc] 比 glibc malloc 多 [5.1%, 3.0%] 的 QPS。
先前的研究
我有多篇关于将 jemalloc 与 MyRocks 一起使用的博客文章。
- 2015年10月 - MyRocks 使用 glibc malloc, jemalloc 和 tcmalloc
- 2017年4月 - 大型并发分配的性能
- 2018年4月 - MyRocks 使用 jemalloc 与 glibc malloc 的 RSS
- 2023年8月 - RocksDB 和 glibc malloc
- 2023年9月 - jemalloc 4.4.0 和 4.5.0 中的一个回归 (RSS 过大)
- 2023年9月 - 更多关于 jemalloc 4.4.0 和 4.5.0 中的回归
- 2023年10月 - 甚至更多关于 jemalloc 4.4.0 和 4.5.0 中的回归
构建、配置和硬件
我从源代码编译了上游 MySQL 8.0.40 用于 InnoDB。我还从源代码编译了 FB MySQL 8.0.32 用于 MyRocks。对于 FB MySQL,我使用了截至 2024 年 10 月 23 日的源代码,git 哈希值为 ba9709c9,RocksDB 版本为 9.7.1。
服务器是 Hetzner 的 ax162-s,具有 48 核 (AMD EPYC 9454P),128G 内存,并且 AMD SMT 已禁用。它使用 Ubuntu 22.04,存储是 ext4,通过 SW RAID 1 使用 2 个本地连接的 NVMe 设备。更多细节在这里。按照标价,Google Cloud 上类似的服务器比 Hetzner 贵 10 倍。
对于 malloc,服务器使用:
-
glibc
- 版本 2.35-0ubuntu3.9
-
tcmalloc
- 由 libgoogle-perftools-dev 提供,并且 apt-cache show 声称这是版本 2.9.1
- 通过 my.cnf 中的 malloc-lib=/usr/lib/x86_64-linux-gnu/libtcmalloc_minimal.so 启用
-
jemalloc
- 由 libjemalloc-dev 提供,并且 apt-cache show 声称这是版本 5.2.1-4ubuntu1
- 通过 my.cnf 中的 malloc-lib=/usr/lib/x86_64-linux-gnu/libjemalloc.so 启用
配置文件InnoDB和MyRocks。对于InnoDB,我使用了80G缓冲池。我尝试对MyRocks使用50G缓冲池,但是glibc malloc出现OOM,所以我使用10G缓冲池重复了所有测试。使用MyRocks的glibc malloc,我可能已经能够避免OOM,缓冲池大小在30G到40G之间-但我不想花费更多时间来弄清楚,因为真正的答案是使用jemalloc或tcmalloc。
Benchmark
我使用了 sysbench,我的用法在这里解释。为了节省时间,我只运行了 42 个微基准测试中的 27 个,并且大多数测试仅测试 1 种类型的 SQL 语句。测试运行使用 16 个表和 50M 行/表。有 256 个客户端线程,每个微基准测试运行 1200 秒。通常我不运行 (client threads / cores) >> 1,但我在这里这样做是为了产生更大的压力,并复制我认为 Dimitri 所做的事情。
通常,当我运行 sysbench 时,我会对其进行配置,以使测试表适合缓冲池(块缓存),但我在这里不这样做,因为我希望 MyRocks 进行 IO,因为每次存储读取的分配都会为 allocator 带来很多问题。
运行所有测试的命令行是:bash r.sh 16 50000000 1200 1200 md2 1 0 256
峰值 VSZ 和 RSS
下表显示了基准测试期间来自 mysqld 的 VSZ 和 RSS 的峰值。最后一列是比率(峰值 RSS / 缓冲池大小)。我不确定在此工作中比较 InnoDB 和 MyRocks 之间的这些比率是否公平,因为 InnoDB 的缓冲池大小要大得多。无论如何,glibc malloc 的 RSS 比 MyRocks 缓冲池大 3 倍以上,这是一个问题。
InnoDB 具有 80G 缓冲池的峰值
| alloc | VSZ | RSS | RSS/80 | | -------- | ---- | ---- | ------ | | glibc | 88.2 | 86.5 | 1.08 | | tcmalloc | 88.1 | 85.3 | 1.06 | | jemalloc | 91.5 | 87.0 | 1.08 |
MyRocks 具有 10G 缓冲池的峰值
| alloc | VSZ | RSS | RSS/10 | | -------- | ---- | ---- | ------ | | glibc | 46.1 | 36.2 | 3.62 | | tcmalloc | 15.3 | 13.1 | 1.31 | | jemalloc | 45.6 | 12.2 | 1.22 |
性能:InnoDB
从这里的结果来看,tcmalloc 和 jemalloc 之间的 QPS 大多相似,但是有一些微基准测试中 tcmalloc 比 jemalloc 好得多,并且这些已被突出显示。
read-only_range=10000 的结果是一个异常值(tcmalloc 比 jemalloc 快得多),并且从 vmstat metrics here 我看到 jemalloc 的 CPU/operation (cpu/o) 和 context switches /operation (cs/o) 比 tcmalloc 大得多。
这些结果使用相对 QPS,它是以下内容,其中 $allocator 是 tcmalloc 或 jemalloc。当该值大于 1.0 时,使用 tcmalloc 或 jemalloc 的 QPS 较大。
(使用 $allocator 的 QPS) / (使用 glibc malloc 的 QPS)
相对于使用 glibc malloc 的结果
| col-1 | col-2 | | -------- | -------- | | | | | col-1col-2 | | | 0.99 | 1.02 | hot-points_range=100 | | 1.05 | 1.04 | point-query_range=100 | | 0.96 | 0.99 | points-covered-pk_range=100 | | 0.98 | 0.99 | points-covered-si_range=100 | | 0.96 | 0.99 | points-notcovered-pk_range=100 | | 0.97 | 0.98 | points-notcovered-si_range=100 | | 0.97 | 1.00 | random-points_range=1000 | | 0.95 | 0.99 | random-points_range=100 | | 0.99 | 0.99 | random-points_range=10 | | 1.04 | 1.03 | range-covered-pk_range=100 | | 1.05 | 1.07 | range-covered-si_range=100 | | 1.04 | 1.03 | range-notcovered-pk_range=100 | | 0.98 | 1.00 | range-notcovered-si_range=100 | | 1.02 | 1.02 | read-only-count_range=1000 | | 1.05 | 1.07 | read-only-distinct_range=1000 | | 1.07 | 1.12 | read-only-order_range=1000 | | 1.28 | 1.09 | read-only_range=10000 | | 1.03 | 1.05 | read-only_range=100 | | 1.05 | 1.08 | read-only_range=10 | | 1.08 | 1.07 | read-only-simple_range=1000 | | 1.04 | 1.03 | read-only-sum_range=1000 | | 1.02 | 1.02 | scan_range=100 | | 1.01 | 1.00 | delete_range=100 | | 1.03 | 1.01 | insert_range=100 | | 1.02 | 1.02 | read-write_range=100 | | 1.03 | 1.03 | read-write_range=10 | | 1.01 | 1.02 | update-index_range=100 | | 1.15 | 0.98 | update-inlist_range=100 | | 1.06 | 0.99 | update-nonindex_range=100 | | 1.03 | 1.03 | update-one_range=100 | | 1.02 | 1.01 | update-zipf_range=100 | | 1.18 | 1.05 | write-only_range=10000 |
性能:MyRocks
从这里的结果来看,tcmalloc 和 jemalloc 之间的 QPS 大多相似,jemalloc 略有优势,但是有一些微基准测试中 jemalloc 比 tcmalloc 好得多,并且这些已被突出显示。
下面的热点结果很奇怪(jemalloc 比 tcmalloc 快得多),并且从 vmstat metrics here 我看到 tcmalloc 的 CPU/operation (cpu/o) 和 context switches /operation (cs/o) 都大得多。
这些结果使用相对 QPS,它是以下内容,其中 $allocator 是 tcmalloc 或 jemalloc。当该值大于 1.0 时,使用 tcmalloc 或 jemalloc 的 QPS 较大。
(使用 $allocator 的 QPS) / (使用 glibc malloc 的 QPS)
相对于使用 glibc malloc 的结果
| col-1 | col-2 | | -------- | -------- | | | | | col-1col-2 | | | 0.68 | 1.00 | hot-points_range=100 | | 1.04 | 1.04 | point-query_range=100 | | 1.09 | 1.09 | points-covered-pk_range=100 | | 1.00 | 1.09 | points-covered-si_range=100 | | 1.09 | 1.09 | points-notcovered-pk_range=100 | | 1.10 | 1.12 | points-notcovered-si_range=100 | | 1.08 | 1.08 | random-points_range=1000 | | 1.09 | 1.09 | random-points_range=100 | | 1.05 | 1.10 | random-points_range=10 | | 0.99 | 1.07 | range-covered-pk_range=100 | | 1.01 | 1.03 | range-covered-si_range=100 | | 1.05 | 1.09 | range-notcovered-pk_range=100 | | 1.10 | 1.09 | range-notcovered-si_range=100 | | 1.07 | 1.05 | read-only-count_range=1000 | | 1.00 | 1.00 | read-only-distinct_range=1000 | | 0.98 | 1.04 | read-only-order_range=1000 | | 1.03 | 1.03 | read-only_range=10000 | | 0.96 | 1.03 | read-only_range=100 | | 1.02 | 1.04 | read-only_range=10 | | 0.98 | 1.07 | read-only-simple_range=1000 | | 1.07 | 1.09 | read-only-sum_range=1000 | | 1.02 | 1.02 | scan_range=100 | | 1.05 | 1.03 | delete_range=100 | | 1.11 | 1.07 | insert_range=100 | | 0.96 | 0.97 | read-write_range=100 | | 0.94 | 0.95 | read-write_range=10 | | 1.08 | 1.04 | update-index_range=100 | | 1.08 | 1.07 | update-inlist_range=100 | | 1.09 | 1.04 | update-nonindex_range=100 | | 1.04 | 1.04 | update-one_range=100 | | 1.07 | 1.04 | update-zipf_range=100 | | 1.03 | 1.02 | write-only_range=10000 |