本篇文章着眼于 Linux 页面大小对数据库性能的影响,以及如何优化数据库 Kubernetes 节点。
大多数流行的数据库都受益于 Linux 大页面。
Kubernetes 最初旨在大规模编排容器的生命周期,用于轻量级、无状态应用程序,如 Ngnix、Java 和 Node.js。对于这个用例,Linux 4K 页面是正确的选择。
最近,通过添加Statefulsets、Persistent Volumes和大页面等功能,Kubernetes 得到了增强,以支持大型、有状态、持久性数据库。
下图显示了使用 Linux 大页面对数据库性能的影响有多大。
上图显示,对于相同的数据库,相同的数据,相同的工作负载,使用Linux 2MB页面而不是4K页面时吞吐量可以提高8倍。该图还显示,随着并发水平的提高,大页面的好处也随之增加。
文章的其余部分介绍了一些背景概念,并着眼于影响数据库工作负载的 Linux 页面大小的因素。
Linux 页面大小
所有现代多用户操作系统都使用虚拟内存来使不同的进程能够使用内存而不必担心底层细节。Linux x86 64 的系统使用分页进行虚拟内存管理。
Linux x8664 支持以下页面大小:
页大小是可用于虚拟内存管理的连续数据的最小单位。
页面的大小是一种权衡。4K 页面最大限度地减少了小内存分配的内存浪费。对于大内存分配,使用 2MB 或 1GB 页面总共需要更少的页面,而且速度会明显更快,因为将虚拟内存转换为物理内存地址会产生相关成本。
TLB 缓存命中和未命中
Linux 上任何进程的每次内存访问(例如,无论是 Nginx、Node.js 还是 MySQL)都需要从虚拟内存转换为物理内存。由于这是一个常规的操作,所有 CPU 都有某种形式的转换后备缓冲区[TLB],它充当最近转换的内存地址的缓存。
所有从虚拟内存到物理内存的转换首先查看 TLB 中是否已经存在映射。如果映射已经存在,则称为 TLB 缓存命中。TLB 缓存命中非常快,并且发生在硬件中。当 TLB 缓存中不存在从虚拟内存到物理内存的转换时,称为 TLB 缓存未命中。TLB 缓存未命中需要通过页面遍历在 Linux 内核页表中的软件中解决映射。尽管页面遍历是高效的 C 代码,但它比通过 TLB 缓存在硬件中进行映射要慢得多。
为什么 TLB 缓存未命中对数据库很重要
所有数据库最终都需要访问内存中的数据进行读取或写入。所有这些数据库读取或写入都需要至少进行一次 TLB 查找。TLB 缓存未命中会显著减慢数据库的读写速度:
如果您有具有可变长度数据类型(例如字符串、JSON、CLOB 或 BLOB)的行/记录,那么这些行/记录的宽度很容易超过 4KB。当 Linux 页面大小为 4KB 时,访问宽度为 20KB 的单行/记录通常需要至少五次 TLB 查找。如果使用 2MB 或 1GB Linux 页面,访问相同的 20KB 行/记录通常只需要一次 TLB 查找。所以一般来说,数据库行(rows)/记录(record)越宽,大页面与 4K Linux 页面的优势就越大。
挑战在于 CPU 具有少量 TLB 缓存条目:
由于 L1 CPU 缓存通常只有大约 64 个 TLB 4K 条目,而最新的 Intel 和 AMD CPU 上的 L2 缓存则有 512 到 1024 个 4K 条目,如果您的数据库具有宽行/记录并访问许多不同的行/记录,那么它几乎总是会得到 TLB 缓存未命中。
如果您使用 2MB 页面,那么您不太可能遇到 TLB 缓存未命中,因为您有效地使 TLB 缓存更大:
减少 TLB 缓存未命中的数量可以对数据库性能产生显著的积极影响。
基准
Linux 并不关心你的数据库是 MySQL、PostgreSQL 还是 Oracle。Linux 并不关心您的应用程序是用 Node.js、Java、Go、Rust 还是 C 编写的。Linux 性能取决于诸如工作负载每单位时间发生多少 TLB 缓存未命中等指标。
以下基准测试着眼于几种配置:
窄行(Narrow)/记录 [128 字节],访问 1 亿条不同的行/记录的概率均匀
中等行(Medium)/记录 [8 KB],平均访问 1 亿条不同的行/记录的概率
更宽的行(Wider)/记录 [16 KB],甚至有可能访问 1 亿条不同的行/记录
要最小化变量的数量:
这种配置意味着没有磁盘 IO 或网络处理,因此工作负载会在 CPU 和/或内存访问上出现瓶颈。
128 字节行/记录的4K Linux 页面
上图显示,在 AMD EPYC 7J1C3 @ 2.55 GHz 处理器上使用 4K Linux 页面和 128 个数据库连接,在单台 Linux 机器上每秒可以执行超过 350 万次数据库读取。
128 字节行/记录的4K 与 2MB 页面
上图显示,对于相同的硬件、相同的数据库、相同的表、相同的数据、相同的查询,2 MB 的大页面可以实现比使用 4K Linux 页面时多出 8 倍的吞吐量。
对于窄行/记录,吞吐量提高 8 倍是一个显著的结果。
8 KB行/记录的4K 与 2MB 页面
对于 8KB 宽的数据库行/记录,2MB 页面可以提供比 4K 页面多 8 倍的吞吐量。
对于中等宽度的行/记录,吞吐量提高 8 倍是一个重要的结果。
16 KB行/记录的4K 与 2MB 页面
上图显示,对于相同的硬件、相同的数据库、相同的表、相同的数据、相同的查询,2 MB 的大页面可以实现比使用 4K Linux 页面时多出 5 倍的吞吐量。
对于更宽的行/记录,吞吐量提高 5 倍是一个重要的结果。
2MB 和 1GB Linux 页面怎么样
很容易看出 2MB Linux 页面与 4K 页面的优势,例如提高 8 倍。您是否还希望看到 2MB 和 1GB Linux 页面之间的显著差异?
由于所有测试的行宽都可以放入 2MB 页面,唯一的变量是 2MB 与 1GB Linux 页面的 TLB 缓存未命中率,用于 1 亿不同的行/记录。
对于所有经过测试的行宽 [128 字节、8KB 和 16KB],1GB Linux 页面的吞吐量比 2MB Linux 页面高 1% 到 21%。
虽然高达 21% 的吞吐量改进不如 8 倍令人印象深刻,但它仍然存在一些差异。
也许行/记录宽于 2MB 的测试会显示显著差异?
Kubernetes 节点专业化
在 Kubernetes 的早期,工作负载往往用于小型、无状态的“基于 Web”的应用程序,例如负载均衡器、Web 服务器、代理和各种应用程序服务器。对于这个用例,使用 Linux 4K 页面是一个合适的选择。
最近,更专业的工作负载正在 Kubernetes 集群中运行,这些集群具有不同的硬件和/或软件要求。例如,机器学习工作负载可以在通用 x86 64位 CPU 上运行,但在具有 GPU 或 ASIC 的 Kubernetes 节点上运行速度往往要快得多。此外,某些 Kubernetes 节点可能专门用于具有快速本地存储、更多 RAM 或可能运行 ARM 64 CPU。
因此,并非所有 Kubernetes 节点都具有完全相同的 CPU、RAM、存储等,一些节点可以使用守护程序集或节点标签来定义和公开这些节点的特定功能。使用POD 标签[使用选择器来匹配节点标签],允许 Kubernetes 调度程序在最合适的节点上自动运行 POD。
上图显示了具有四种类型的专用节点的 Kubernetes 集群。
你可以做些什么来优化 Kubernetes 上的数据库性能
通常不在您控制范围内的事情:
在范围内可以控制你的Kubernetes集群的事情:
您可以选择为要在其上运行数据库工作负载的一组机器配置具有 2MB 或 1GB 大页面的 Kubernetes 节点 [即 Linux 主机]。
在 Linux上配置大页面的方式与Kubernetes 无关。您必须在 Linux 内核中配置大页面,因为您无法在 Kubernetes 或容器级别执行此操作。通常你想关闭透明大页面,因为它们通常不会提高数据库性能,只会浪费内存。
在 Linux x8664 上配置 2MB 页面对于任何 Linux 发行版都相当简单,通常无需更改启动时间参数即可完成。
配置 1GB Linux 页面的步骤因发行版而略有不同,并且需要启动时间参数。我能够在最近的 Intel Xeon 和 AMD CPU 上配置 1 GB Linux 页面,用于:
您应该为 Kubernetes 上的数据库配置多少大页面
这个问题是特定于数据库的。这取决于您的 Kubernetes 节点有多少 RAM、您希望在该节点上运行多少其他 [非数据库] POD、这些 POD 需要多少 RAM,以及最终您的数据库通过使用更多内存而受益多少。
总结