云数据库 MongoDB 版的内存使用率是一个非常重要的监控指标。本文介绍查看云数据库 MongoDB 版实例内存使用率的方法,以及导致内存使用率高的原因和优化策略。
背景信息
云数据库 MongoDB 版进程启动后,不仅会加载二进制文件和依赖的各种系统库文件到内存,而且负责内存的分配和释放工作,例如客户端的连接管理、请求处理和存储引擎等。默认情况下,云数据库 MongoDB 版的内存分配器是Google tcmalloc,内存主要被Wiredtiger存储引擎和客户端连接及请求处理占用。
查看方法
分片集群架构下,各个分片(Shard)的内存使用与副本集架构保持一致,Config Server用于存储配置元数据,Mongos路由节点的内存使用率和聚合结果集、连接数大小、元数据大小相关。
副本集架构下,您可以通过以下方法查看内存的使用情况:
监控图分析
云数据库 MongoDB 版副本集由多种角色组成,一个角色可能对应一个或多个物理节点。云数据库 MongoDB 版副本集实例提供一个可供读写访问的Primary节点(主节点)、一个或多个提供高可用的Secondary节点(从节点)、一个隐藏的Hidden节点(隐藏节点)和一个或多个可选的ReadOnly节点(只读节点)。
在MongoDB管理控制台的监控信息页面,可以查看云数据库 MongoDB 版的内存使用率。
命令行查看
在MongoDB Shell中使用
db.serverStatus().mem
命令查看和分析内存占用情况,返回示例如下:{ "bits" : 64, "resident" : 13116, "virtual" : 20706, "supported" : true } //resident 表示该mongod物理节点占用的物理内存大小,单位为MB。 //virtual 表示该mongod物理节点占用的虚拟内存大小,单位为MB。
说明serverStatus的更多信息,请参见serverStatus。
常见原因
引擎内存
云数据库 MongoDB 版的大部分内存都会用于存储引擎缓存。考虑到兼容性和安全性,云数据库 MongoDB 版将存储引擎WiredTiger的cachesize设置为实际申请的实例内存规格大小的60%左右。具体规格,请参见产品规格。
如果存储引擎缓存使用了cachesize配置大小的95%,说明实例负载已经很高了。出于保护自身的目的,处理用户请求的线程会主动参与到刷脏的工作中来,用户侧会明显感觉到请求存在阻塞。具体规则,请参见 eviction参数说明。
您可以使用以下方法查看引擎内存的使用情况:
在MongoDB Shell中通过
db.serverStatus().wiredTiger.cache
查看。返回信息中bytes currently in the cache
后的值为内存大小。返回信息示例如下:{ ...... "bytes belonging to page images in the cache":6511653424, "bytes belonging to the cache overflow table in the cache":65289, "bytes currently in the cache":8563140208, "bytes dirty in the cache cumulative":NumberLong("369249096605399"), ...... }
在DAS控制台的性能趋势页面实时查看当前WiredTiger引擎的cache dirty比例。如何查看,请参见性能趋势。
通过云数据库 MongoDB 版自带的mongostat工具查看当前WiredTiger引擎的cache dirty比例。更多信息,请参见mongostat。
连接和请求占用的内存
如果实例的连接数很大,可能会消耗⼀部分的内存,原因如下:
每个连接,后端都有对应处理这个连接上的请求的线程。每个线程最多可以开销1MB的线程栈,通常情况下在几十KB~几百KB。
每个TCP连接在内核层面有读缓冲区和写缓冲区,由TCP内核参数tcp_rmem和tcp_wmem等确定,这块的内存使用用户无需关心。但并发连接越多,默认套接字缓存越大,则TCP占用内存越大。
每接收到⼀个请求,会有个请求上下⽂,整个过程中可能分配很多临时缓冲区,比如请求包、应答包和排序的临时缓冲区等,这些在请求结束时都会释放,但这个释放只是归还给内存分配器 tcmalloc,tcmalloc优先会还到⾃⼰的cache里,然后逐步再归还给操作系统。
很多情况下,内存使用率高的原因是tcmalloc未及时归还内存⾄操作系统,这⼀块最大可能达到几十GB。关于tcmalloc未归还OS的内存大小,可以通过命令
db.serverStatus().tcmalloc
查看。其中tcmalloc cache=pageheap_free_bytes+total_free_byte。返回信息示例如下:{ "generic":{ "current_allocated_bytes":NumberLong("9641570544"), "heap_size":NumberLong("19458379776") }, "tcmalloc":{ "pageheap_free_bytes":NumberLong("3048677376"), "pageheap_unmapped_bytes":NumberLong("544994184"), "current_total_thread_cache_bytes":95717224, "total_free_byte":NumberLong(1318185960), ...... } }
说明mongodb tcmalloc的更多信息,请参见tcmalloc。
元数据信息占用的内存
云数据库 MongoDB 版的数据库、集合、索引等内存元数据等,如果集合和索引数量很多,这⼀块占用的内存也不容忽视。尤其在云数据库 MongoDB 版4.0以前的版本,全量逻辑备份期间可能打开非常多的⽂件句柄并且未能及时归还OS导致内存快速上涨,或者低版本的云数据库 MongoDB 版在大量删除集合后可能未能删除文件句柄导致内存泄漏。
创建索引过程中的内存消耗
正常的业务数据写⼊情况下,Secondary节点会维持⼀个最大约256M的buffer用于数据回放。在创建索引方面,当Primary节点创建索引完成后,Secondary节点回放过程中可能消耗更多的内存。在云数据库 MongoDB 版4.2以前,在Primary节点上通过非background的⽅式创建索引,后端回放创建索引是串行的,最多可能消耗500M内存,而云数据库 MongoDB 版4.2以后默认废弃了background选项,允许Secondary节点并行回放创建索引,那就会消耗更多的内存,多个索引同时创建时可能导致实例内存溢出。
创建索引期间可能造成的内存消耗,详情请参见index-build-impact-on-database-performance和index-build-process。
PlanCache内存占用
在某些场景下,一个请求可能存在的执行计划非常多,这时plancache会消耗比较多的内存。在高版本云数据库 MongoDB 版中,您可以使用mgset-xxx:PRIMARY> db.serverStatus().metrics.query.planCacheTotalSizeEstimateBytes
命令查看PlanCache占用的内存大小。更多信息,请参见Secondary node memory arise while balancer doing work。
解决策略
内存优化并不是尽可能地减少内存使用,而是在保证系统性能正常的前提下,内存足够使用且稳定,在机器资源和性能中达到一个最佳的折衷。云数据库 MongoDB 版帮助用户指定了CacheSize的大小,该值不支持修改。解决内存使用的策略如下:
控制并发连接数。根据性能测试结果,数据库中能够创建100个长连接,默认MongoDB Driver可以和后端建立100个连接池。当存在很多客户端时,就需要降低每个客户端的连接池大小,一般建议与整个数据库建立的长连接控制在1000以内,连接太多会导致内存和多线程上下文的开销增加,影响请求处理延时。
降低单次请求的内存开销,例如通过创建索引减少集合的扫描、内存排序等。
在连接数合适的情况下内存占用持续增高,建议升级内存配置,避免可能存在内存溢出和大量清除缓存而导致系统性能急剧下滑。
加速tcmalloc释放内存。可以在控制台的参数设置中调整setParameter.tcmallocReleaseRate参数,参数取值为1~10,数值越大内存释放的速率越快,建议修改后观察内存监控以及业务是否受到影响。如果setParameter.tcmallocReleaseRate参数的值设置为10后效果还不明显,您可以开启setParameter.tcmallocAggressiveMemoryDecommit参数,该参数开启后会突然释放大量内存,可能会导致应用程序的响应时间暂时上升。
如果您在使用云数据库 MongoDB 版过程中遇到更多可能存在内存泄漏的场景,可以联系阿里云技术⽀持处理。
参考
参数 | 默认值 | 含义 |
eviction_target | 80 | 当cache used超过eviction_target,后台evict线程开始淘汰CLEAN PAGE。 |
eviction_trigger | 95 | 当cache used超过eviction_trigger,用户线程也开始淘汰CLEAN PAGE。 |
eviction_dirty_target | 5 | 当cache dirty超过eviction_dirty_target,后台evict线程开始淘汰DIRTY PAGE。 |
eviction_dirty_trigger | 20 | 当cache dirty超过eviction_dirty_trigger,用户线程也开始淘汰DIRTY PAGE。 |