写点什么

华为云数据库 GaussDB(for Cassandra) 揭秘第二期:内存异常增长的排查经历

发布于: 2021 年 05 月 07 日

​​摘要: 内存的异常增长对于程序来说是一个致命的问题,因为其可能触发 OOM,进程异常宕机,业务中断等结果,所以对内存进行合理的规划使用及控制就显得尤为重要。


本文分享自华为云社区《华为云数据库GaussDB(forCassandra)揭秘第二期: 内存异常增长的排查经历》,原文作者:高斯 Cassandra 官方。


华为云数据库 GaussDB(for Cassandra) 是一款基于计算存储分离架构,兼容 Cassandra 生态的云原生 NoSQL 数据库;它依靠共享存储池实现了强一致,保证数据的安全可靠。核心特点是:存算分离、低成本、高性能。

问题描述

    

GaussDB(for Cassandra)自研架构下遇到一些挑战性问题,比如 cpu 过高,内存泄漏,内存异常增长,时延高等问题,这些也都是开发过程中遇到的典型问题。分析内存异常增长是一个比较大的挑战,内存的异常增长对于程序来说是一个致命的问题,因为其可能触发 OOM,进程异常宕机,业务中断等结果,所以对内存进行合理的规划使用及控制就显得尤为重要。通过调整 cache 容量,bloom 过滤器大小,以及 memtable 大小等等,实现性能提升,读写时延改善等效果。


在线下测试过程中发现内核在长时间运行后,内存只增不减,出现异常增长的情况,怀疑可能存在内存泄漏。

分析 &验证

    

首先根据内存使用,将内存分为堆内和堆外两个部分,分别进行该两块内存的分析。确定有问题的内存是堆外内存,进一步对堆外内存分析。引入更高效的内存管理工具 tcmalloc,解决内存异常增长问题。下面为具体分析验证过程。

确定内存异常区域

    

使用 jdk 的 jmap 命令和 Cassandra 的监控(配置 jvm.memory.*监控项)等方法,每隔 1min 采集 jvm 的堆内内存及进程整体内存。

    

启动测试用例,直到内核的整体内存达到上限。分析采集到的堆内内存和进程内存变化曲线,发现其堆内内存仍保持相对稳定,未出现一直持续上涨,但期间内核的整体内存仍然在持续上涨,两者的增长曲线不符。即问题应该发生在堆外内存。

堆外内存分析验证

glibc 内存管理

    

使用 pmap 命令打印进程的内存地址空间分布,发现有大量的 64MB 的内存块和许多内存碎片,该现象与 glibc 的内存分配方式有关。堆外内存的使用和进程整体的内存增长趋势相近,初步怀疑该问题是由堆外内存导致。加之 glibc 归还内存的条件苛刻,即内存不易及时释放,内存碎片多,猜测问题和 gblic 有关系。当内存碎片过多,空闲内存浪费严重,最终进程内存的最大使用量会出现超过预期计划最大值的可能,甚至出现 OOM。

tcmalloc 内存管理

    

引入 tcmalloc 内存管理器,代替 glibc 的 ptmalloc 内存管理方式。减少过多的内存碎片,提高内存使用效率,本次分析验证采用 gperftools-2.7 源码进行 tcmalloc 的编译。运行相同的测试用例,发现内存仍在持续上涨,但是上涨幅度较之前降低,通过 pmap 打印出该内存地址分布情况,发现之前的小内存块和内存碎片显著减小,说明该工具有一定优化效果,印证了前面提到内存碎片过多的猜测。

    

但是内存异常增长的问题仍然存在,有点像是 tcmalloc 的回收不及时或者不回收导致。实际上 tcmalloc 的内存回收是比较 "reluctant" 的,主要是为了当再次需要内存申请时可以直接使用,减少系统调用次数,提高性能。基于此原因,下来进行手动调用其释放内存接口 releasefreememory。发现效果不明显,原因暂时未知(可能确实存在没待释放的空闲内存)。

手动触发 tcmalloc 的 releasefreememory 接口

 

为验证该问题,通过设置 cache 容量的方式进行。


1.   先设置 cache 的容量为 6GB,然后将读请求压起来,使 cache 的 6GB 容量填满

2.   修改 cache 的容量为 2GB,为快速是内存释放,手动调用 tcmalloc 的 releasefreememory 接口,发现没有效果,推测采用 tcmalloc 之后,内存仍然一直上涨不下跌的原因可能与该接口的有关。

3.   在 releasefreememory 接口内部的多个地方记录日志,然后启动进程再次测试,发现一处报错是在进行系统调用 madvise 时有出现失败。

    

代码位置:



​报错日志信息:



​1.   通过该处的调用失败,分析代码。发现 tcmalloc 的内存释放逻辑是“round-robin”,即中间有一个 span 释放失败,则后续待释放的 span 被终止,releasefreememory 逻辑调用结束。这个就和前面的现象吻合,执行完 releasefreememory 接口后基本没有效果,发现每次都是在释放了几十 MB 时,因为该接口的调用失败导致释放逻辑终止。


2.   再次分析该系统调用 madvise 失败原因。通过给内核的该方法打 patch,发现其失败原因是因为传入的地址块对应的内存状态是 LOCKED 状态。导致系统调用失败,报错为非法参数。


3.   内存为 LOCKED 状态,和该状态相关的有代码调用 mlock 系统方法、系统的 ulimit 配置。分析相关代码未发现异常点。查询系统 ulimit 配置,发现 max lockedmemory 为 unlimited。修改其配置为 16MB,重启 Cassandra 进程,再次测试,发现内存释放效果显著。


4.   继续运行测试,发现内存持续上涨的情况消失。在业务持续存在的情况下,内存会上涨到最高,不再上涨,保持平稳,符合内存计划使用量。业务压力减少甚至停止后,内存出现缓慢下降趋势。

解决 &总结


1.   引入 tcmalloc 工具,优化内存管理。比较优秀的内存管理器有 Google 的 tcmalloc 和 Facebook 的 jemalloc 等


2.   修改系统的 max lockedmemory 参数配置。

    

合理分配进程需要使用内存的最大值,并预留一定容量,对于不符合预期增长的内存需要进一步分析。内存相关问题和程序相关性较强。系统的关键配置需谨慎,要评估其影响。同时排查了类似的所有配置。

    

增加 releasefreememory 的命令,后端进行调用,优化 tcmalloc hold 内存不释放问题。不过 releasefreememory 命令的执行会锁整个 pageHeap,可能导致内存分配请求被 hang,所以需要小心执行。

    

后端增加可动态配置 tcmalloc_release_rate 的参数,来调整 tcmalloc 将内存交还给操作系统的频率。该值的合理范围是[0-10],0 表示永远不交还,值越大,表示交还的频率越高,默认值是 1。

结语

    

本文通过分析开发过程中遇到的内存增长问题,使用更优秀的内存管理工具,以及更细粒度的内存监控,更直观的监控数据库运行期间的内存状态,确保数据库平稳高性能运行。


点击关注,第一时间了解华为云新鲜技术~

发布于: 2021 年 05 月 07 日阅读数: 18
用户头像

提供全面深入的云计算技术干货 2020.07.14 加入

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态,方便开发者快速成长与发展,欢迎提问、互动,多方位了解云计算! 传送门:https://bbs.huaweicloud.com/

评论

发布
暂无评论
华为云数据库GaussDB(for Cassandra)揭秘第二期:内存异常增长的排查经历