往期分享
RDS MySQL
RDS PostgreSQL
RDS SQL Server
Redis
概述
阿里云数据库MongoDB的内存使用率是一个非常重要的监控指标,然而MongoDB的内存使用率并非是简单的越小越好,而突发的高内存使用率也需要引起用户足够的关注,因为它往往是业务侧的变动引起,突发的内存飙升容易引起实例OOM。
MongoDB 进程启动后,除了跟普通进程一样,加载 binary、依赖的各种library 到内存,其作为一个DBMS,还需要负责客户端连接管理、请求处理、数据库元数据、存储引擎等很多工作,这些工作都涉及内存的分配与释放。默认情况下,MongoDB使用Google tcmalloc作为内存分配器,内存占用的大头主要是"Wiredtiger存储引擎"与 "客户端连接及请求的处理"。
本文将由浅入深帮您查看、分析和优化云数据库MongoDB的内存使用。
查看内存使用
部署架构为副本集模式下,提供有多种查看内存使用的方法,您可以根据自身需求,由浅入深了解MongoDB的内存使用情况。
部署架构为分片集群模式下,各个Shard的内存使用与副本集保持一致;Config Server仅仅存储配置元数据,基本上不会造成内存瓶颈,一般可以忽略;Mongos路由节点的内存使用往往与聚合结果集,连接数大小,元数据大小有关。
监控图分析
MongoDB副本集由多种角色组成,一个角色可能对应一个或多个物理节点。阿里云MongoDB对用户暴露Primary和Secondary节点,另外还提供有只读实例的角色。可以通过点击"监控信息",选择对应的角色查看MongoDB内存有关的监控情况,如下图:
除了总体内存使用率外,MongoDB的WiredTiger引擎的内存使用也至关重要,下图展示了WiredTiger引擎的内存使用情况,其中"maximum bytes configured"即当前配置的引擎cache的总大小,与配置文件中的cacheSizeGB保持一致;"bytes_read_into_cache"表示每秒从磁盘将数据加载到内存的大小 ,"bytes_written_from_cache"表示每秒从cache中刷新脏页到磁盘的大小,这两个值越大,反应内存使用越吃紧,磁盘压力相应也会越大。
命令行查看
除了使用阿里云控制台提供的监控图查看以外,您也可以直接使用MongoDB Shell命令查看和分析内存占用情况,如下示例:
PRIMARY> db.serverStatus().mem { "bits" : 64, "resident" : 13116, "virtual" : 20706, "supported" : true } //resident表示该mongod物理节点占用的物理内存大小,单位为M //virtual表示该mongod物理节点占用的虚拟内存大小,单位为M
另外,MongoDB serverStatus中自带了大量的关于wiredTiger引擎和tcmalloc的内存使用使用详情,可以通过的db.serverStatus().wiredTiger.cache和db.serverStatus().tcmalloc进行查看,后文我们会重点展开讲解。
更多serverStatus的信息展示详情建议参考:https://docs.mongodb.com/manual/reference/command/serverStatus/
内存使用详细分析
引擎内存使用
MongoDB使用tcmalloc作为内存分配器,其中WiredTiger引擎占用的Cache是其中最大的一部分,引擎内存最大使用内存由配置参数cachesize决定。为了兼容性能和安全性,阿里云数据库MongoDB为cachesize设置的大小默认为申请内存的60%左右,由于存在向上取整等因素,详细的cachesize大小设置可参照下表:
class_code | 规格大小 | 实际大小 | Wt cacheSizeGB |
dds.mongo.small | 1024 | 2048 | 1 |
dds.mongo.mid | 2048 | 4096 | 1 |
dds.mongo.standard | 4096 | 7168 | 2 |
dds.mongo.large | 8192 | 12288 | 5 |
dds.mongo.xlarge | 16384 | 24576 | 10 |
dds.mongo.2xlarge | 32768 | 49152 | 20 |
dds.mongo.4xlarge | 65536 | 98304 | 40 |
dds.mongo.monopolize | 225280 | 225280 | 96 |
dds.mongo.2xmonopolize | 450560 | 450560 | 264 |
mongo.x8.medium | 16384 | 16384 | 10 |
mongo.x8.large | 32768 | 32768 | 20 |
mongo.x8.xlarge | 65536 | 65536 | 40 |
mongo.x8.2xlarge | 131072 | 131072 | 77 |
mongo.x8.4xlarge | 262144 | 262144 | 154 |
dds.sn4.8xlarge.3 | 131072 | 131072 | 64 |
通常情况下,即使数据量远超过cachesize的大小,wiredTiger也不会将cachesize耗尽,如果超过了cachesize配置大小的95%,那表示系统性能已经处于较为危险的状态。WiredTiger 在内存使用接近一定阈值就会开始做淘汰,避免内存使用满了阻塞用户请求,这个过程称为"eviction"。
参数 | 默认值 | 含义 |
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 |
在这个规则下,一个正常运行的 MongoDB 实例,cache used 一般会在 0.8 * cacheSizeGB
及以下,偶尔超出问题不大;如果出现 used>=95% 或者 dirty>=20%,并一直持续,说明内存淘汰压力很大,用户的请求线程会阻塞参与page淘汰,请求延时就会增加,这时可以考虑"扩大内存"或者"扩大IOPS"。
查看当前wiredTigerd引擎占用的内存大小
查看当前wiredTiger引擎的cache dirty比例
您可以通过mongostat或者阿里云数据库自治服务DAS实时查看当前的cache dirty,目前阿里云数据库MongoDB暂不支持cache dirty历史情况查看。
更多mongostat的使用方式可以参考:https://docs.mongodb.com/v4.2/reference/program/mongostat/
连接和请求占用的内存
如果实例的连接数很大,可能会消耗相当一部分的内存,这是因为:
- 每个连接,后端启动一个线程处理这个连接上的请求,每个线程最多1MB的线程栈开销,平时一般在几十KB - 几百KB 之间。
- 每个tcp连接在内核层面有read、write buffer,极端情况下可能涨到16MB,由tcp内核参数tcp_rmem和tcp_wmem等确定,这块的内存使用用户无需关心。但并发连接越多,默认套接字缓存越大,则tcp占用内存越大。
- 每接收到一个请求,会有个请求上下文,整个过程中可能分配很多临时buffer,比如请求包、应答包、从引擎数据的buffer、排序的临时buffer等,这些在请求结束都会释放,但这个释放只是说归还给内存分配器 tcmalloc,tcmalloc优先会还到自己的cache里;然后逐步再归还给操作系统。所以很多情况下,内存使用率高的原因是tcmalloc未及时归还内存至操作系统,这一块最大可能达到数十GB。
关于tcmalloc未归还OS的内存大小,可以通过以下命令查看:
tcmalloc cache大小=pageheap_free_bytes + total_free_byte
关于更多mongodb tcmalloc的更多内容参考:https://mongoing.com/archives/34751
元数据信息占用的内存
MongoDB的database、collection、index等内存元数据等,如果集合和index数量很多,这一块占用的内存也不容忽视。尤其在MongoDB4.0以前的版本,全量逻辑备份期间可能打开非常多的文件句柄并且未能及时归还OS导致内存快速上涨,或者低版本的MongoDB在大量删除collection后可能未能删除文件句柄导致内存泄漏。
阿里云MongoDB建议库表数量控制在10W以内,并使用MongoDB4.0以上的内核版本,更多关于这块的详细分析参考:
https://jira.mongodb.org/browse/WT-4336
创建index过程中的内存消耗
正常的业务数据写入情况下,Secondary会维持一个最大约256M的buffer用于数据回放。在Create Index方面,当Primary创建index完成后,Secondary节点回放过程中可能消耗更多的内存。在MongoDB4.2以前,在Primary上通过非background的方式create index,后端回放创建index是串行的,最多可能消耗500M内存;而MongoDB4.2以后默认废弃了background选项,允许Secondary并行回放create index,那就会消耗更多的内存,多个index同时build时可能导致实例OOM。
更多关于create index期间可能造成的内存消耗参考:
https://docs.mongodb.com/manual/core/index-creation/#index-build-impact-on-database-performance
https://docs.mongodb.com/manual/core/index-creation/#index-build-process
PlanCache内存占用
在某些场景下,比如一个SQL可能存在的执行计划非常多,这时plancache可能会消耗比较多的内存,在高版本MongoDB中可以通过以下命令查看PlanCache占用的内存大小,默认大小未Byte。
mgset-xxx:PRIMARY> db.serverStatus().metrics.query.planCacheTotalSizeEstimateBytes
NumberLong(750695)
更多内容参考我们给官方提的bug链接:https://jira.mongodb.org/browse/SERVER-48400
内存使用的通用优化思路
首先需要强调一点,内存优化并非是为了尽可能减少内存使用,而是在保证系统性能完全没问题的前提下,内存使用尽可能稳定和够用,从而在机器资源和性能中达到一个最佳的折衷。
阿里云MongoDB帮用户指定了比较合适的CacheSize大小,不建议也无法修改该值。
控制并发连接数,这个是最直接有效的方法,根据性能测试结果,100个长连接足以压满数据库,默认 MongoDB driver也是跟后端建立100的连接池。当连接到客户端(ECS机器)很多时,就需要降低每个客户端的连接池大小,一般建议跟整个数据库建立的长连接控制在1000以内,连接太多,一是内存开销,另外多线程上下文的开销也会增加,影响请求处理延时。
降低单次请求的内存开销,比如通过建索引,减少 COLLSCAN、内存排序等。
另外,在连接数合适的情况下内存占用持续走高,建议升级内存配置,从而避免可能存在OOM和疯狂的evict导致系统性能急剧下滑。
最后,如果您在使用阿里云MongoDB过程中遇到更多可能存在内存泄漏的场景,可以与技术支持人员联系。