【巡检问题分析与最佳实践】MongoDB 内存高问题

往期分享

RDS MySQL

RDS MySQL 实例空间问题

RDS MySQL 内存使用问题

RDS MySQL 活跃线程数高问题

RDS MySQL 慢SQL问题

RDS MySQL 实例IO高问题

RDS MySQL 小版本升级最佳实践

RDS PostgreSQL

RDS PostgreSQL 实例IO高问题

RDS PostgreSQL 慢SQL问题

RDS PostgreSQL CPU高问题

RDS SQL Server

RDS SQL Server 磁盘IO吞吐高问题

RDS SQL Server CPU高问题

RDS SQL Server 空间使用问题

Redis

Redis 流控问题

Redis 内存高问题

Redis CPU高问题

概述

阿里云数据库MongoDB的内存使用率是一个非常重要的监控指标,然而MongoDB的内存使用率并非是简单的越小越好,而突发的高内存使用率也需要引起用户足够的关注,因为它往往是业务侧的变动引起,突发的内存飙升容易引起实例OOM。

MongoDB 进程启动后,除了跟普通进程一样,加载 binary、依赖的各种library 到内存,其作为一个DBMS,还需要负责客户端连接管理、请求处理、数据库元数据、存储引擎等很多工作,这些工作都涉及内存的分配与释放。默认情况下,MongoDB使用Google tcmalloc作为内存分配器,内存占用的大头主要是"Wiredtiger存储引擎"与 "客户端连接及请求的处理"。

本文将由浅入深帮您查看、分析和优化云数据库MongoDB的内存使用。

查看内存使用

部署架构为副本集模式下,提供有多种查看内存使用的方法,您可以根据自身需求,由浅入深了解MongoDB的内存使用情况。

部署架构为分片集群模式下,各个Shard的内存使用与副本集保持一致;Config Server仅仅存储配置元数据,基本上不会造成内存瓶颈,一般可以忽略;Mongos路由节点的内存使用往往与聚合结果集,连接数大小,元数据大小有关。

监控图分析

MongoDB副本集由多种角色组成,一个角色可能对应一个或多个物理节点。阿里云MongoDB对用户暴露Primary和Secondary节点,另外还提供有只读实例的角色。可以通过点击"监控信息",选择对应的角色查看MongoDB内存有关的监控情况,如下图:

【巡检问题分析与最佳实践】MongoDB 内存高问题

除了总体内存使用率外,MongoDB的WiredTiger引擎的内存使用也至关重要,下图展示了WiredTiger引擎的内存使用情况,其中"maximum bytes configured"即当前配置的引擎cache的总大小,与配置文件中的cacheSizeGB保持一致;"bytes_read_into_cache"表示每秒从磁盘将数据加载到内存的大小 ,"bytes_written_from_cache"表示每秒从cache中刷新脏页到磁盘的大小,这两个值越大,反应内存使用越吃紧,磁盘压力相应也会越大。

【巡检问题分析与最佳实践】MongoDB 内存高问题

命令行查看

除了使用阿里云控制台提供的监控图查看以外,您也可以直接使用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引擎占用的内存大小

【巡检问题分析与最佳实践】MongoDB 内存高问题

查看当前wiredTiger引擎的cache dirty比例

您可以通过mongostat或者阿里云数据库自治服务DAS实时查看当前的cache dirty,目前阿里云数据库MongoDB暂不支持cache dirty历史情况查看。

【巡检问题分析与最佳实践】MongoDB 内存高问题

更多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 内存高问题

关于更多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过程中遇到更多可能存在内存泄漏的场景,可以与技术支持人员联系。





上一篇:【巡检问题分析与最佳实践】Redis内存高问题


下一篇:【巡检问题分析与最佳实践】PolarDB 流量 & 代理问题