概述
阿里云数据库MongoDB的空间使用率是一个非常重要的监控指标,如果实例的存储空间完全打满,将会直接导致实例不可用。一般来说,当一个MongoDB实例的存储空间使用比例达到80-85%以上时,就应及时进行处理,要么降低数据库实际占用空间的大小,要么对存储空间进行扩容,以避免空间打满的风险。
然而,阿里云数据库MongoDB的空间使用情况分析并不简单,本文将由浅入深帮您查看,分析和优化云数据库MongoDB的空间使用。
1 查看空间使用
1.1 副本集模式
部署架构为副本集模式下,提供有多种查看空间使用的方法,您可以根据自身需求,由浅入深了解MongoDB的空间使用情况。
1.2总体概览
在MongoDB控制台的“基本信息”页中会显示实例的总存储空间使用大小,但这里只有当前的空间的总使用率,没有具体的各类数据分别占用空间大小的信息,也没有空间使用的历史信息,如下图:
1.3 监控图分析
MongoDB副本集由多种角色组成,一个角色可能对应一个或多个物理节点。阿里云MongoDB对用户暴露Primary和Secondary节点,另外还提供有只读实例的角色。可以通过点击"监控信息",选择对应的角色查看MongoDB空间相关的监控情况,如下图:
其中一个MongoDB物理节点的空间使用由两大块组成,分别为data_size和log_size,其中这里的data_size是数据磁盘使用空间(不包括local库),主要包括collection打头的数据物理文件,index打头的索引物理文件,以及少部分元数据物理文件,比如WiredTiger.wt。
log_size包括local库的物理大小,mongodb运行日志大小,以及少部分的审计日志大小。
ins_size=data_size+log_size
1.4 详细分析
如果需要深入分析副本集中库或表的空间详细占用,除了MongoDB自带的db.stats(),db.$collection_name.stats()以外,我们推荐使用阿里云MongoDB控制台的提供的"CloudDBA-空间分析",通过"CloudDBA-空间分析",您可以实现以下目的:
l 查看库表空间概览,日均增长量,预测可用天数。
l 查看异常库表空间使用。
l 详细业务表的空间使用,包括索引和数据逻辑大小,压缩率分析,平均行长等更多内容。
更多的CloudDBA-空间分析内容参考:
https://help.aliyun.com/document_detail/162422.html
更多mongodb官方提供的分析命令详解参考:
l https://docs.mongodb.com/manual/reference/method/db.stats/
l https://docs.mongodb.com/manual/reference/method/db.collection.stats/
l https://docs.mongodb.com/manual/reference/method/db.collection.storageSize/
l https://docs.mongodb.com/manual/reference/method/db.collection.totalIndexSize/
l https://docs.mongodb.com/manual/reference/method/db.collection.totalSize/
2 分片集群
在分片集群的部署模式下,由于一个集群下可能存在多个Shard,没有整体空间使用率的概念。所以控制台不再提供"基本信息"中的空间使用率。
2.1 监控图分析
阿里云MongoDB的监控信息中详细提供了集群组建mongos路由节点,config server配置节点以及shard节点的空间使用率。不过通常来说,mongos和config server节点不会成为空间瓶颈,建议选择忽略,直接查看各个shard中各个角色的空间使用情况,如下图:
2.2 详细分析
分片集群的空间使用的详细查看方法与副本集模式略有不同,需要逐个登录各个shard通过命令查看,在每个分片上的空间使用情况详情就与副本集情况完全一样。
另外一方面,分片集群中的空间问题除了空间使用率以外,还存在非常大量的"各shard空间使用不均匀"问题,后者非常难以分析,目前CloudDBA暂不支持。本文后续章节会重点带您了解和深入分析“不同分片下空间使用不均衡”,“同一副本集下各个角色空间使用不均衡”。
3 空间问题的疑难杂症分析和解决方法
3.1 空间问题上涨概要分析和一般解决思路
当收到磁盘空间报警后,一般的分析和解决思路如下:
1. 确认当前的空间使用值,确认当前空间使用中各个库表的详细占用情况。
2. 确认引起空间增长的主要源头,比如是日志类增长,还是具体的某个业务表写入暴涨。
3. 确认当前的增长是否符合预期,针对不符合预期的大量写入场景做应用分析。
4. 确认当前的数据是否存在大量碎片,能否通过回收碎片的方式回收空间。
5. 确认是否需要做磁盘空间扩容,或者部署定时历史数据删除或TTL Index。
6. 历史数据大量删除完成,通过compact或者重做副本集的方式回收碎片空间。
3.2 compact方法和compact期间对实例的影响
compact 一个集合,会加集合所在DB的互斥写锁,会导致该DB上所有的读写请求都阻塞,因为 compact 执行的时间可能很长,跟集合的数据量相关,所以强烈建议在业务低峰期执行,避免影响业务。
compact方法很简单,建议优先在备库上执行,并通过主备切换的方式来减少compact期间对业务的影响,指令为
db.runCommand({compact: "collectionName"})。
另外,MongoDB4.4以后的官方版本,Compact命令将不再阻塞业务读写,更多compact的命令的使用方法和限制参考:
l https://docs.mongodb.com/manual/reference/command/compact/
l https://mongoing.com/archives/26907
l https://help.aliyun.com/document_detail/96530.html
3.3 compact无效
如前文所述,在大量remove的场景下我们会需要使用compact来回收碎片空间。然而在极端场景下,compact操作虽然提示成功了,但实际上磁盘空间并没有回收,这应该算是一个BUG或者是MongoDB在compact设计上的缺陷,并不是只要存在碎片就一定能回收成功。compact的基本原理并不是立马开辟新的空间存放数据来替换原来的文件,而是将数据不断地往前面的空间空洞挪动,所以在某些场景下虽然存在空间空洞,但内部的compact算法并不能保证肯定可以复用这些空洞。针对这种compact失效的场景,如果纠结于空间使用,可以通过重建副本的方式解决。
还有一种场景,在MongoDB3.4以前的版本,compact或许还存在一个BUG:在大量删除数据后,compact无法回收索引文件,只对数据文件生效。这个可以通过使用命令db.$table_name.stats().indexSizes或者直接查看索引物理文件大小确认。针对这种情况,建议将内核版本升级至3.4以上。
3.4 journal log过大导致主备空间差距巨大
在极端情况下,journal log可能触发bug导致空间无限上涨,可以通过MongoDB运行日志查看到类似以下内容:
2019-08-25T09:45:16.867+0800 I NETWORK [thread1] Listener: accept() returns -1 Too many open files in system
2019-08-25T09:45:17.000+0800 I - [ftdc] Assertion: 13538:couldn't open [/proc/55692/stat] Too many open files in system src/mongo/util/processinfo_linux.cpp 74
2019-08-25T09:45:17.002+0800 W FTDC [ftdc] Uncaught exception in 'Location13538: couldn't open [/proc/55692/stat] Too many open files in system' in full-time diagnostic data capture subsystem. Shutting down the full-time diagnostic data capture subsystem.
该bug的触发条件为宿主机的open files达到设置上限,导致MongoDB内部的log server清理线程中断,4.0以前的官方版本均有该问题,如果有遇到您可以将内核版本升级到4.0以上,或者可以通过重启mongod进程临时解决,具体的bug链接参考:https://jira.mongodb.org/browse/WT-4083
3.5备库延迟和增量备份可能导致Secondary日志空间持续增长
默认的官方MongoDB,oplog是一个固定集合,大小基本是固定的,主备之间的物理文件大小不会有太大差异。阿里云MongoDB出于oplog经常过期导致的节点recovering状态,开发了oplog自适应特性。也就是说,在极端场景出现主备延迟的情况下,实际oplog可以使用的大小不再受限于配置文件定义的固定集合大小,理论上可以达到用户申请磁盘容量的20%。这就造成了一个问题,当备库延迟恢复后,oplog之前占用的物理空间并不会回缩。
另外,处于备份和恢复效率的考虑,阿里云MongoDB使用物理备份的方式在Hidden备份mongodb实例,期间会涉及到大量的chepoint导致占用了更多的数据和日志空间。
针对以上场景,当空间占用量不是特别大的话通常建议直接忽略即可。也可以根据需要对oplog做单独的compact操作,compact期间会阻塞所有的写操作。方法如下:
db.grantRolesToUser("root", [{db: "local",role: "dbAdmin"}])
use local
db.runCommand({compact: "oplog.rs",force: true })
4 不同分片之间的空间使用不均衡
4.1 sharding key类型选择不合理
在一个分片集群中,片键类型的选择至关重要,一般会使用hash分片或者ranged分片两种类型。通常情况下,在磁盘均衡度方面,hash分片的策略会比ranged好很多,因为根据不同的key值,MongoDB通过内部的哈希函数可以使得数据均匀地分布在不同地分片上,如下图所示:
而range分片一般是根据key的大小范围进行数据分布,所以往往会造成这样的一个现象:新插入的数据在一个热点的chunk上,不但会引起该chunk所在的shard磁盘IO过高,也会带来短期数据不均匀的场景。如下图所示,所有的数据写入chunk C所在分片,当chunk C写满以后会在本分片中split出新的chunk,后续通过集群负载均衡器Balancer迁移chunk,但这个迁移需要耗费大量的时间和IO,在一个高并发写入的场景下,数据的负载均衡速度可能跟不上数据写入速度,从而造成分片之间数据容量不均的问题。在这种使用场景下,不建议使用range分片策略。
更多的Sharding Key类型介绍参考:
l https://docs.mongodb.com/manual/core/sharding-shard-key/
l https://docs.mongodb.com/manual/core/hashed-sharding/
l https://docs.mongodb.com/manual/core/ranged-sharding/
4.2 sharding key字段选择不合理
通过sh.status()可以看到各个分片上的chunk数量基本一致,但实际上绝大部分数据都只存在部分chunk上,导致这些热点chunk所在的分片数据量远大于其他分片,通过查看MongoDB运行日志可以查看到明显的告警信息:
2019-08-27T13:31:22.076+0800 W SHARDING [conn12681919] possible low cardinality key detected in superHotItemPool.haodanku_all - key is { batch: "201908260000" }
2019-08-27T13:31:22.076+0800 W SHARDING [conn12681919] possible low cardinality key detected in superHotItemPool.haodanku_all - key is { batch: "201908260200" }
2019-08-27T13:31:22.076+0800 W SHARDING [conn12681919] possible low cardinality key detected in superHotItemPool.haodanku_all - key is { batch: "201908260230" }
mongos负载均衡主要考虑的是各个shard的chunk数量保持相当,就认为数据是均衡的,所以就会出现以上的极端场景:虽然各个shard数量相当,但实际数据严重倾斜。因为一个chunk内shardKey几乎完全相同但又触发到64M的chunk分裂阈值,这时就会分裂出一个空的chunk。久而久之,虽然chunk的数量变多了并且完成了chunk的迁移,但实际上迁移走的chunk都是空的chunk,造成了chunk数量均衡但实际数据不均衡的情况。(这应该是Balancer在迁移chunk时基于代价的考虑,认为空chunk迁移代价更低,所以优先选了空的chunk迁移)
更多split的介绍参考:
l https://docs.mongodb.com/manual/core/sharding-data-partitioning/
l https://docs.mongodb.com/manual/tutorial/split-chunks-in-sharded-cluster/
针对这种情况,只能在架构上重新设计,选择合适的区分度较高的列作为sharding key。
4.3 部分db未做sharding
MongoDB分片集群允许部分db做sharding,部分db不做shading。那么必然会带来这样的一个问题:不做shading的db的数据必然只能存在一个分片上,如果该db数据量很大,可能会造成该分片的数据量远大于其他分片。
除了上述情况,比较容易引起该问题的使用场景通常是因为从一个源端mongos集群导入到一个新的mongos集群,而逻辑导入过程中忽略了一步:实现在目标端mongos集群做好sharding设计,因为逻辑导入并不会自动做sharding设计,这一步需要事先做好。
针对这种问题,我们建议:
l 如果是因为目标集群初始化导入,导入之前做好分片设计。
l 如果不做sharding的库很多且数据量基本相当,Mongos提供有movePriamy命令将指定db迁移到指定分片。
l 如果存在某个db的数据量极大且未做sharding,建议对其做sharding设计或者将其拆分出来当作单一的副本集对待。
l 即使出现这种情况,如果磁盘总量足够充裕,建议忽略。
4.4 大规模的movechunk操作可能引起分片的磁盘占用不均
movechunk的本质是向目标端shard写入数据后remove源端数据。默认情况下,remove操作不会释放空间,因为针对wiredTiger引擎来说每个表都有独立的数据文件和索引文件,如果该文件不删除,总的磁盘空间就不可能回缩。通常最容易引起该问题的操作为:之前的sharding集群中未做shard设计,运行一段时间后才做了Sharding。
从原理上说movechunk引起的空间碎片和大规模delete一样,因此针对出现大量movechunk或者remove文档的情况,可以针对该分片进行compact操作来回收碎片空间,正常情况下compact后数据会进行重组进而回收文件的碎片空间。
更多movechunk的介绍参考:
l https://docs.mongodb.com/manual/tutorial/migrate-chunks-in-sharded-cluster/
l https://docs.mongodb.com/manual/tutorial/manage-sharded-cluster-balancer/
5 阿里云MongoDB产品在空间使用优化上的规划
5.1计算存储分离
当前的阿里云MongoDB单个物理节点最大支持的存储空间是3T,如果单实例或副本集模式下的节点数据量超过3T就必须业务拆分或者升级至分片集群,而且规格扩容涉及到数据迁移会导致耗时较长。
阿里云MongoDB在副本集部署模式下即将支持云盘以实现计算存储分离。在云盘存储下,单个节点的数据存储上限将达到数十T,以及云原生架构带来的分钟级扩容能力。
5.2 ECS快照备份
在高版本的阿里云MongoDB实例中,由于oplog多线程回放技术的越发成熟,基本上不会再有备库延迟问题,所以阿里云MongoDB内核层面不再对oplog做定制化改造,与官方的"固定集合"形态保持一致,备库延迟引起的oplog放大问题通过升级至最新版内核即可得到解决。
另外,当前阿里云MongoDB正在研发采用ECS快照技术备份MongoDB实例,当故障发生时可绕过耗时的OSS物理备份下载,达到分钟级的恢复能力。并且使用ECS快照备份后,传统物理备份方式可能导致Hidden节点空间上涨的问题也会得到解决。
5.3 Compact指令的内核改造优化
MongoDB4.4的Compact指令并不会阻塞读写,阿里云MongoDB在内核层面将该patch移植至云数据库 MongoDB 3.4及以上版本,通过DAS控制台点击的方式实现空间碎片整理的产品化。
以上内容来自于《云数据库运维实战手册》电子书,可点击https://developer.aliyun.com/topic/download?id=8198下载完整版,助力云运维能力更上一层楼!