GaussDB(DWS) mmap锁分析以及优化

举报
NicholasD 发表于 2025/07/26 10:57:22 2025/07/26
【摘要】 mmap_sem锁产生的问题分析及优化

一,mmap锁介绍

mmap_sem锁本质是一个进程级别的读写锁:

写锁保护VMA的修改,例如:mmap分配虚拟内存,munmap释放虚拟内存;

读锁保护VMA不被并发修改,例如:page_fault时VMA到物理内存的映射;

二、mmap锁产生的问题

mmap_sem读写锁设计上可以实现并发的多线程读访问。但这个并发是有条件的。

如下图场景,线程1和线程3的读锁无法并发访问,线程3需要等待线程2的写锁释放后才能加读锁。

即有大量写锁时,该锁其实就变成互斥锁了,所以应用实现中应该尽可能避免触发mmap写锁。


page fault问题案例:cgroup限额CPU排队mmap读写锁阻塞

如下图,在小资源场景,与CPU调度互相等待,产生进程级阻塞

0某现网环境配置限额小资源池,仅绑定两个CPU,但有2000+业务线程只在CPU1/2上调度。

1,新分配内存的首次读写和释放需要使用持有mmap读锁,大量mmap读锁申请,如果没有写锁,完全可以并行。

2pm启动pg子线程需要持有mmap的写锁,将阻塞在这之后的mmap读锁(R7-R9无法与R1-R6并行)。

3,同样,R7-R8的读锁又会阻塞后续的写锁W2

4R7-R9中有大量的mmap读锁任务,但因为大多限制在CPU1/2上调度,在CPU调度队列上排队,进一步阻塞W1

5pm启动pg子线程需要持有mmap的写锁,此时要等待R7-R9中的所有mmap读锁任务被CPU调度完成。

6pm启动pg子线程阻塞在mmap写锁,最终60s超时报错,无法对外提供服务。

cache回收阻塞问题案例:高IO业务导致集群hang

如下图,在高IO压力场景,cache回收阻塞后,导致进程mmap锁阻塞

1,高IO业务读取大量的磁盘文件,产生大量的磁盘IOcache,同时消耗大量内存

2pm线程fork pg线程时调用mmap因内存不足触了阻塞式直接内存回收

3,直接内存回收需要将脏页刷盘,刷盘时被业务的磁盘IO阻塞,影响回收性能

4pm最终60s超时报错,无法对外提供服务

Linux开发人员也知道这把锁罪大恶极,做了很多优化,但都没法根治

 

优化点

说明

kernel

mmap_lock: add tracepoints around lock acquisition

独立mmap_lock函数,并增加trace点,增强DFX分析

kernel

Speculative page faults(投机性缺页,Android已合入,TencentOS已应用

写锁只修改引用计数,读操作过程中引用计数没有修改则无需加锁,反之浪费一次操作

kernel

per-VMA lock(未正式合入)

每一块VMA单独一把锁,减少锁粒度

阿里

VLDB :《Async-fork》

fork 调用过程中最耗时的页表拷贝部分从父进程移动到子进程

三,DWS mmap锁的使用场景

无标题.png

如上图所示,从代码层面上分析,DWS使用mmap的场景主要是子进程启动,子线程启动,LLVM,jemalloc预热等阶段。

使用BPF trace工具,调用命令以下命令,可以在运行态对mmap写锁进行调用栈抓取与分析

./trace -p gaussdb进程号 -KU -T 'down_write_killable(struct rw_semaphore *sem)(sem == &($task->mm->mmap_sem))'

TPCDS并发场景下使用BCC可以观察到写锁热点如下:

总次数

子线程

jemalloc

llvm

子进程

73277

29554

23181

20552

手动触发

DWS由于是多线程架构,进程级别的mmap锁一但出现阻塞,轻则性能下降,重则进程hang死。

 四,DWS mmap锁优化策略

1,子进程优化

子进程启动时持有mmap写锁主要是在copy_mm阶段(子进程复制父进程的VMA虚拟内存地址),这些子进程基本都是调用简单的外部命令获取一些信息,无需父进程的全量内存信息。

通过使用vfork对fork的替换,实现fork/system/popen的重新封装,绕开copy_mm,不再持有mmap写锁。


2,子线程优化

子线程启动时持有mmap写锁主要是在栈内存空间的mmap调用上。

通过在用户态提前mmap内存,并对栈内存进行池化管理,调用pthread_attr_setstack使用用户态栈内存,实现pthread_create不再调用mmap,规避mmap写锁。

3,其他优化

LLVM需要临时mmap分配内存生成可执行代码,执行完成后又需要munmap释放,都需要加mmap写锁,需要对LLVM内存进行池化管理

jemalloc在预热阶段,mmap内存需要加锁写,释放内存时使用madvise(DONTNEED),只需要加读锁,且并不会归还虚拟内存,预热完成后,将不再需要调用mmap申请更多的虚拟内存,即不再需要加mmap写锁

优化后使用BPF trace工具观察,mmap写锁已经基本消除

TPCDS 20分钟

优化前首轮

优化前次轮

优化后首轮

优化后次轮

mm写锁次数

66484

65617

464

29

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。