​TencentOS 内核特性助力数据库性能提升30%,内存占用下降15%

2024-08-16 18:38:58 浏览数 (3)

随着云服务行业的快速发展和竞争压力的增大,用户对使用的云服务的成本效益,特别是对云数据库的性价比提出了更高的要求。为了应对这一挑战,部分提供商选择推出更低价的产品,此方式虽然提升了性价比,但是更换架构所带来的兼容性以及性能不一致等问题,用户往往难以评估,对于用户的核心业务提升帮助不大。

本文将介绍 TencentOS 内核团队与数据库内核团队合作使用的一系列技术,这些技术的使用在原有架构上提升了 30%的性能,降低内存占用 15%,也在不需更换底层架构的情况下完成了优化。

01、架构升级的挑战与影响

针对当下市场环境,云原生数据库团队决定做出两方面的升级动作:

  1. 新架构从传统物理机转向 TKE HouseKeeper,以实现计算资源与存储资源的解耦,提升资源利用率以及丰富产品形态;
  2. 存量物理机从 Intel 转向 AMD,在单位成本内提供更高的单核性能,以及大规格独享型实例。

1.1 从物理机转向云原生的挑战

云原生带来了丰富且灵活的计算资源底座,且计算资源与存储资源分离,可以满足用户不同类型的诉求,但随着底座架构的变化,也带来一些原有竞争力上的挑战:

  • 对于物理机形态,由于使用了云原生构建的服务(采用云盘),所以在存储点查性能以及 IO 性能上存在天然的瓶颈;
  • 相对物理机更小的 node 规格,对各个数据库节点的内存使用和分配策略不能照搬物理机策略,此时产生了挑战;

1.2 云盘 IO 的挑战

云盘的 IO 总带宽与购买磁盘的大小相关,根据现网用户所使用的磁盘大小推算,大多数用户分配到的 IO 上限并不会很高,如能够优化写入或读取就可以明显的提升数据库实例的整体性能;针对云盘带来的 IOPS 性能上限问题,如 AWS 为了解决该问题引入了写入优化型(原子写)以及查询优化型实例。以上解决方案均依赖于 AWS 底层硬件,用户必须选择对应硬件底座才能开启该能力,受限明显。为了保持体验的一致性以及普适性,我们的目标是在任何情况下均能实现原子写能力。

针对以上问题,TXSQL 内核团队协同 TencentOS 内核团队,引入 16k 原子写能力,不仅使我们和 AWS 在写入能力上保持一致,还具备更好的普适性,并且超越了其他友商。

MySQL 的 page size 一般为16KB。即由 MySQL 发起的一次性写入及校验均为16KB。但操作系统内核及磁盘一次写入磁盘是以 4KB 页进行的,因此在系统崩溃以及掉电等场景下,操作系统及磁盘无法保证 16KB 页的原子性,即磁盘上可能出现部分页为新数据,其余页为旧数据这种新旧数据混合不一致的情况(partial writes/torn writes)。

为了解决这种场景下的问题,MySQL 会使用双写机制解决异常情况下的数据不一致问题。MySQL 在将脏数据下刷时,会首先将脏数据拷贝到一个双写缓存(内存)中,之后将该缓存写入共享表空间以及记录日志(磁盘),待该操作完成后,再调用 fsync 将双写缓存中的数据刷入实际文件对应的(磁盘)空间。如果异常情况发生,可利用共享表空间以及记录日志数据进行恢复。

由此造成的影响:

  1. 数据在磁盘上会写入两次,占用磁盘带宽*2,造成性能负担。
  2. 这两次写入的磁盘地址大概率并不连续,造成性能抖动。

MySQL innodb 双写介绍:

https://dev.mysql.com/doc/refman/8.0/en/innodb-doublewrite-buffer.html

因此需要一个在内核态的底层实现方案,在保障原子性的前提条件下,降低写入磁盘的次数。从而提升 MySQL 数据库的性能效率。

02、原子写方案介绍

上游社区一直有关于支持原子写的讨论。其核心功能依托并基于硬件原子写的能力,通过打通文件系统、block 块层、SCSI/NVME 层对可变长(2 的 N 次方倍)的块进行读写,将不会分割该行为,以保证其原子性。

详见:

Block atomic write support:

https://lwn.net/Articles/933015/

XFS atomic write support:

https://lwn.net/Articles/959348/

ext4 atomic write support [RFC]

国内某开源社区基于 xfs 进行的扩展,在文件系统层,基于 reflink 的功能,对文件设定 16k 固定大小的连续块。在 staging 区域中进行写入,在写入完成后通过变更引用地址这一方式达到原子操作的效果。

相关功能:

https://gitee.com/anolis/storage-cloud-kernel

基于 f2fs 的 atomic write 功能实现。其根据元 inode 大小创建了一个 COW inode(orphan inode),后续写入都写到 COW inode 里面。Commit 中带有 fsync 调用,在 commit 之前看的是旧的数据,commit 之后看到新的数据。

相关功能:

https://lore.kernel.org/linux-fsdevel/1411707287-21760-1-git-send-email-jaegeuk@kernel.org/

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=3db1de0e582c358dd013f3703cd55b5fe4076436

通过对比以上方案及分析优劣势后,TencentOS 内核团队决定在 TK4(Linux 5.4)中基于 xfs 实现 MySQL 双写场景支持,通过利用 xfs COW 的特性,数据写入一个新的区域,待全部写入完成后再指向新的区域。 上层应用(MySQL)以固定大小进行写入操作。

由于 MySQL 固定以 16K 页面大小进行原子输入,可在使用 xfs 文件系统(利用底层引用 reflink 的特性)做 COW 操作。从而使写操作在不使用共享表空间以及记录日志的情况下,完成原子操作。将两次写入(磁盘)变为写入一次。

当在 TXSQL 内核上打开原子写能力后,为了避免系统表空间出现异常,系统表空间依然采用双写机制。

以下为测试结果:

读写混合场景下各云测试结果

只写场景下测试结果

从上图可以看出,在使用了原子写能力后,针对读写混合场景,对比原来有 30% 的性能提升;针对只写场景,大并发极限性能提升超过 1 倍,效果显著,整体优于友商。

测试环境如下:

  • 规格选择为中等规格 4vCPU 16GB 内存/100GB 存储。主要考虑是,一方面是改规格为核心业务使用的起步规格,另外,4vCPU 规格的实例数为现网实例总数的42.64%,更具代表性。
  • 对比测试方法使用 sysbench 的读写混合模型(oltp_read_write)进行测试,单表大小为 100 万,共十个表,单次测试时长为 300 秒,分别测试了如下并发度的性能表现:16、32、64、128。
  • 写入对比测试使用 sysbench 的只写模型(oltp_write_only)进行测试,单表大小为 100 万,共十个表,单次测试时长为 300 秒,分别测试了如下的并发度的性能表现:32、64、128、256。

03、小规格的资源冗余挑战

在物理机形态中,会预留一部分计算资源(8%)用于部署在物理机上的数据库实例在极端条件下的临时计算资源共享,降低数据库实例运行出现异常的概率。与物理机(内存规格在 700G 以上)形态不同,云原生架构下为更好利用资源,单个节点的计算规格并没有物理机高,按照 8% 资源预留给数据库实例进行共享时能够带来的冗余相对较小。为了降低冗余较小带来的运行风险,引入使用了「悟净」内存压缩能力,也是该能力在云原生环境下第一次使用在数据库产品上。

为了在极限场景中进行稳定性以及效果验证,针对常见 4 种模型,2 种场景分别进行了压力测试,结果如下:

场景模型

oltp_read_only

oltp_write_only

oltp_read_write

oltp_update_index

sum avg

IO 场景

0.8324%

2.3978%

2.4960%

-1.3831%

0.8714%

内存场景

-2.0108%

-0.9624%

-0.4278%

-1.5733%

-1.2540%

与未开启「悟净」内存压缩能力在4c16g 规格下性能对比,性能波动在 3% 以内

场景

MiB(节省内存)

IO 场景

1067

内存场景

1842.23

与未开启「悟净」内存压缩能力在 4c16g 规格下内存节省数据对比,极限环境下内存节省在 6%~11.2%

以上测试结果均在极限压力下测试获得,测试时保持了 CPU 使用率为 100%,在空闲情况下,有更大的内存节省,解决了小规格节点有可能导致的内存抢占问题,提升运行稳定性,留下更多突发业务请求容错空间。

04、原子写方案介绍

除了以上两个特性外,在新版本的 TencentOS 中还提供了不少用于性能增强的特性,在使用后均获得了不同程度的性能提升。

优化特性

优化效果

代码段大页

sysbench多并发场景QPS提升 8%

numa-aware spinlock

sysbench 低并发场景 QPS 提升 5%,高并发场景提升 7%

投机性缺页异常-SPF

数据库预热时间(QPS 从 0 到峰值)优化 30%-50% 耗时

ORC unwinder优化

sysbench 多并发场景 QPS 提升 5%

接下来我们会详细介绍相关特性的技术原理。

4.1 代码段大页优化

在数据库 MySQL 中,其代码段粗略估计达到 20M 之多,代码段在程序访问中,属于高频热点访问数据,而这部分代码在现有内核中,是以 4K 粒度形式访问的,高频度访问以及有限的 iTLB 缓存容量,导致程序在执行过程中,持续性出现大量的 iTLB miss 问题,通过减少 iTLB miss,可以提高程序代码段部分指令缓存命中率,从而提供程序的执行效率,达到优化性能的目的。

在方案设计中,我们考虑了透明大页和标准大页两种方案,下面介绍其各自优劣。

  1. 透明大页 THP(Transparent Huge Pages):是动态可生成的,不用预留,即用即申即可,但是透明大页不是必定就能合成成功的,首先需要唤醒 khugepage 去执行,后面申请连续的 4k page,达到 2M,才能满足要求,此外对于大内存耗用的业务场景,存在申请连续 2M 内存失败的风险。
  2. 标准大页(HUGETLB):采用标准大页,需要预留,一当预留成功后,不用考虑申请失败的问题,但是缺点也明显,标准大页由于需要先预留后才能使用,并且预留部分无法被其他业务使用,存在通用性差的问题,此外预留意味着束缚,预留多了,导致浪费,预留少了,大页使用不足。

通过以上分析,并结合数据库业务情况,我们最终决策采用透明大页的方案,此外还针对这部分做了部分优化,整体设计框图如下图所示:

代码段大页处理逻辑

在代码开发过程中,我们会在 VMA 创建过程中,对非首地址对齐的区间进行优化,此外还会针对部分略微少于 2M 的区间,进行自动补全,这样保证了代码段部分尽可能合并成大页,还增加细粒度开关,控制生成代码段大页的合成范围。

代码段大页优化结果

使用数据库 8C32G 规格实例进行了测试,验证了代码段优化系列的效果。测试规格:8C32G,tables=100 table-size=25000,MySQL 内核版本 8.0.30 20230630 版本进行测试,结果如下:

32&512并发只读优化结果

32&512 并发读写优化结果

4.2 NUMA-aware qspinlocks

内核中现有「qspinlock」实现是基于 MCS lock 实现,也称之为锁队列机制,它会将系统中所有在等待这个锁的 CPU 排成队(queue),并且用链表来管理,该方案优势非常明显,即不用过多担心 cache miss 导致的性能波动问题,每一个 CPU 都只会维护与自己相关的锁队列结构体,并且只有当前锁释放后,才能更新本地的标志值,让其他 CPU 去获取该锁。

采用 MCS 锁的优势主要在通过 per-cpu 方式维护的锁队列,消除了cache-line跳来跳去导致的程序性能波动问题,此外,还存在一定的公平性,通过队列机制,确保每一个 CPU 都能成功获取锁。但是在非一致性内存访问(NUMA,non-uniform memory-access)系统平台上还存在进一步优化空间,该平台通常包含多个NUMA节点(统称为node),每个节点下管理着多个 CPU,访问那些连接到本 CPU节点的内存,比起连接到其他节点的内存速度更快。

当然,无论内存连接在哪个节点上,对 cache 的访问都是(相对)最快的,但是在基于 NUMA 节点之间移动 cache line 的动作开销很大,远差于同一节点上的 CPU 之间发生 cache line 来回跳动的情况。

例如下图所示,msc spinlock 维护 CPU 队列是基于 NUMA 交叉方式保存的,这就导致每一次选择下一个 CPU 时,都发生 cache line 跨 NUMA 跳动的情况,导致性能波动,此外 spinlock 中通常用于保护数据结构,所以当 CPU 之前争抢锁的目的是为了访问同一个数据结构时,就会发生连锁效应,导致数据结构也发生 cache line 跨 NUMA 跳动的情况,这样对系统性能会造成更大的伤害。

MSC spinlock处理流程

NUMA-awared qspinlock 出现就是为了解决队列中 CPU 跨 NUMA 交叉分布的情况,该方法通过维护两个队列的形式,主队列(primary queue)和次要队列(secondary queue),将本地 NUMA 节点所在的 CPU 都放在主队列中,其他远端 NUMA 放在从队列中,所以在 CPU 争抢锁的过程中,会尽可能地将锁移交给同一节点上的另一个 CPU,以此来防止锁在 NUMA 节点之间来回移动。

通过这种方式,在等待锁的 CPU 会分成两个队列,主队列只会包含当前持有锁的 CPU 所在的同一 NUMA 节点上等待的 CPU, 而从队列则保护非当前 NUMA 所在节点的所有 CPU。

NUMA-aware qspinlocks 处理逻辑

但是这种方式又会导致其他弊端出现,例如在 CPU 争抢锁非常严重的情况下,主队列一直有 CPU 在等待锁,这将会导致主队列上一直占有锁,而从队列无法获取锁的问题,所以社区又针对此进行了进一步优化,针对主队列中 CPU 10ms 未被清空的情况,会将整个从队列中 CPU 插入到主队列的头部,从而迫使主队列选择从队列中的 CPU ,从而达到锁使用均衡的目的。

NUMA-aware 性能优化结果

使用数据库 8C32G 规格实例进行了测试,验证了代码段优化系列的效果。测试规格:8C32G,tables=100 table-size=25000,MySQL 内核版本 8.0.30 20230630 版本进行测试,结果如下:

4.3 投机性缺页异常-SPF

投机性缺页异常其实就是降低了 mmap_sem 的锁的影响粒度,由原本整个程序 mm_struct 粒度转移到每一段虚拟地址 virtual memory areas (VMA)粒度,在 VMA 粒度中,不再持有锁,如果在内核各种路径处理中,发现 VMA 没有发生篡改,例如没有发生 VMA 拆分、合并或者被释放的情况,那就认为「投机」成功,整个缺页路径不再持有锁,如果发生 VMA 被篡改的情况,那就任务「投机失败」,缺页异常路径将再次回到传统缺页异常路径中。

投机性缺页失败的场景主要是如下两种:

  1. VMA 发生拆分或者合并情况: 为此投机性缺页异常特性在 VMA 引入了顺序锁 seqlock, 该锁用于判定 VMA 是否发生了篡改,在 VMA 发生合并或者拆分处理过程中,都会先增加该 count 值,在每一次缺页异常路径中,都会先保存顺序锁 count 值,在缺页处理完成之后,同样以原子方式再次获取该 count 值,如果值仍然没有发生变化,就可以确定 VMA 没有发生修改,「投机」成功,否则表示失败。
  2. VMA 发生被释放情况:这种情况可以使用 Read-Copy-Update(RCU)来避免,此处使用了 RCU 的变体 SRCU,因为缺页异常路径是运行睡眠的,所以环境语义不能变,在投机性缺页异常处理过程中,会调用 get_vma()方式,增加原子变量 vm_ref_count 引用值,异常处理完成后,将调用 put_vma()将原子变量减一,在要释放 VMA 的路径,只需要通过判定该原子变量 vm_ref_count 引用值是否为 0 即可。

投机性缺页异常处理流程

4.4 ORC Unwinder

在 Linux 内核中,Stacktrace 对于调试内核问题、分析性能瓶颈以及诊断内核崩溃等场景非常重要,是不可或缺的功能,但为了实现稳定的 Stacktrace 内核也会有一些额外性能损耗。

长期以来, Stacktrace 是依赖 DWARF Unwinder 实现的。DWARF Unwinder 利用调试信息(Debuginfo)来解析函数调用栈,在这个过程中,Frame Pointer 起到了至关重要的作用。Frame Pointer 是一个预留寄存器,用于存储当前函数的栈帧基址。

在 x86_64 体系结构中,通常使用 RBP 寄存器来实现。当一个函数被调用时,RBP 寄存器首先被更新为当前栈帧的基址,旧的 RBP 被存于栈顶,而函数返回时,执行出栈并将 RBP 寄存器恢复为之前的值,从而 DWARF Unwinder 借助 RBP 信息即可正确地、链式地回溯函数的栈帧,并根据调试信息计算出当前函数的参数、局部变量和返回地址等信息,从而生成 Stacktrace 信息。

然而,这种做法会导致寄存器资源的浪费,特别是在寄存器资源紧张的架构(如 x86_64)中,固定的入栈出栈也增加了函数的运行开销。而 ORC Unwinder 就是为了解决这个问题的。

在 x86_64体系结构中,ORC Unwinder 重新设计了编译时生成的相关调试信息的格式,仅使用 RIP-relative 地址即可获取调用栈回溯过程中所需的信息。这意味着在运行时,只需要依赖 RIP 寄存器信息,就可以访问调用栈中的所有信息,并通过事先计算且经过验证的信息找出上一级栈帧,从而避免使用额外的寄存器,也避免了频繁入栈出栈。ORC Unwinder 可以显著提高运行时的性能,特别是在寄存器资源紧张的架构(如 x86_64)中,这一优势更为明显。

05、总结与展望

TencentOS 内核团队与数据库内核团队合作使用的技术,这些技术不仅成熟而且带来了明显的提升,从市场竞争力来看,这些技术的使用不仅在原有架构上提升了性能,也在不需更换底层架构的情况下完成了优化,提升了性价比。随着业务的需求变化以及技术的发展,未来还将继续为大家带来更多的特性。

-End-

0 人点赞