250830-Mongodb清理磁盘空间碎片-db-runCommand-compact

问题描述

私服客户业务量的增加,录音数据不断增加,面临着磁盘空间不足的情况,因为 MongoDB 的特性,直接删除数据占用的磁盘空间并不会释放,即使
drop collection 也不行,除非 drop database。

如果一个 db 曾经有大量的数据一段时间后又删除的话,硬盘空间就是一个问题,如何收回被 mongodb 占用的多余空间?

磁盘空间越来越大,为了避免磁盘空间超过 95%以上导致锁库,清理方案迫在眉睫。

预估能整理的磁盘大小:

1
2
3
4
5
6
7
db
.call_audio
.chunks
.stats()
.wiredTiger["block-manager"]["file bytes available for reuse"]

>>1795354353454byte (B)换算为TB后为1.63287TB

解决方法选型

SUCC-执行离线修复mongod –repair(指定数据目录和目标数据库):

1
2
3
4
# 格式:mongod --repair --dbpath <数据存储目录> --repairDatabase <目标数据库名>
mongod --repair --dbpath /var/lib/mongodb --repairDatabase your_database_name

/var/lib/mongo

方法一:官方推荐 compact 整理(推荐指数 5 颗星)

img

操作步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
0.连接MongoDB 节点1.use '业务DB' 
//切换到业务库2.查看预估可以整理的磁盘碎片大小
db
.call_audio.chunks
.stats()
.wiredTiger["block-manager"]["file bytes available for reuse"]

>>17953543534543.进行碎片整理

db.runCommand({compact:"<collection_name>",force:true});

db.runCommand({compact:"device_data",force:true});

说明:(<collection_name>:集合名。
您可以通过show tables命令查询现有的集合。
force为可选项,如您需要在副本集实例的Primary节点执行该命令,需要设置force的值为true。)

4.等待执行,返回{ "ok" : 1 }
代表执行完成。
说明:(如果您执行的目标是一个大集合,而compact命令马上就返回{ "ok" : 1 }集合的物理空间也没有任何变化,则表示该集合没有必要进行碎片整理。

compact操作不会传递给Secondary节点,当实例为副本集实例时,请重复上述步骤通过MongoDB shell连接至Secondary节点,执行碎片整理命令。)
5.验证磁盘空间有没有变化

在MongoDB中,db.runCommand({compact:"collectionName",force:true})
用于压缩指定集合的数据文件,释放未使用的空间。
如果在执行该命令时出现超时问题,
可以尝试以下几种方法来处理:

1. 增加超时时间

MongoDB允许你设置命令的超时时间。
你可以通过maxTimeMS选项来设置命令的最大执行时间(以毫秒为单位)。
例如:

1
2
3
4
5
db.runCommand({
compact: "device_data",
force: true,
maxTimeMS: 3600000 // 设置超时时间为3600秒(1小时)
});

注意点:

1
2
3
4
5
当然以上是我们期望的多么完美的运行过程!!!
注意点:
1.执行前一定要停掉业务系统,确保没有读写操作,否则会导致锁住
2.大部分情况下磁盘慢场景下,都不满足磁盘整理的条件,所以操作后很快就返回了,如果删除了很多数据,并且还有不小于10%-20%剩余空间的话,还可以跑一下
3.不要气馁,还有其他方法释放空间

方法二:secondary 节点同步(推荐指数 5 颗星)

主要思想就是:在有新机器(磁盘)的情况下,新建一个 secondary 节点,使之与 primary 节点开始数据同步。数据的同步与直接复制数据文件不同,MongoDB
会只同步数据,因此同步完成后的数据文件是没有空集合的,以此实现了磁盘空间的回收。

上步骤:

img

注意点:

1
2
3
4
5
6
7
8
注意点:    
1.一定要做好数据的备份。
2.一定要注意主从配置优点:
1.同步的方法是比较好的,第一基本不会阻塞副本集的读写,
2.第二消耗的时间相对比较短,2T数据大概在74H内同步完成,且不影响正常使用。
3.second会清理掉大量的磁盘空间,达到我们的目的
4.索引会自动创建,不用再手动去创建缺点:
1.不用于复制包含分片集合的数据库

方法三:copyDatabase(推荐指数 3 颗星)

MongoDB 还支持在线复制数据:db.copyDatabase(“from”,”to”,”IP:port”),此种方法也能释放空间,因为 db.copyDatabase
复制的数据,而不是表示在磁盘中的数据文件。但是该方法在 4.0 版本起被弃用,3.x
版本还能继续使用,还可以复制远端数据库哦,方便多节点复制操作,但是需要我们备份好主库索引,手动创建索引。

主流程:

1
2
3
0.停掉所有业务数据读写操作1.db.copyDatabase("from","to","127.0.0.1:16161");复制出一个新的to数据库。这个已经是最小数据占用的数据。会在数据目录下产生to的相关数据文件。
2.将所有程序的配置从from库改为to库。测试无误。
3.这时可以删除from库。方法。use from 后 db.dropDatabase()。这个方法的好处是可以时间将磁盘上的数据删除掉,节省出很大的空间,但是需要手动创建索引。

上方法:

1
前提停掉所有服务0.Primary 对要迁移的db进行授权use 业务DB;db.createUser( { user: "rcrai", pwd: "xxxx", roles: [ "readWrite", "dbAdmin" ] } )db.grantRolesToUser('rcrai',[{ role: "dbAdmin", db: "业务DB" }]) 1.second机器上执行copy操作db.copyDatabase("源业务DB", "目标业务DB", "10.10.10.26:30017","rcrai","xxxx");2.等待执行完成,执行创建索引

注意点:

1
注意:second copy没执行完成和second索引没有手动创建完成前,一定不要启动业务读写,copy操作比较消耗IO,容易干挂docker的网络环境优点:    1.纯copy数据方式,速度快 2T数据耗时15小时完成    2.支持远程复制    3.second会清理掉大量的磁盘空间缺点:    1.不能自动创建索引    2.MongoDB4.0不支持,推荐方法三    3.不用于复制包含分片集合的数据库

方法四:mongodump And mongorestore(推荐指数 2 颗星)

原理简单粗暴,停服务,dump 出来数据,再 restore 回去。

上方法:

1
1.停业务服务,停止业务数据读写2.执行dump数据操作mongodump --host 10.10.12.25 --port 30017 -u rcrai -p eawmzaxtfnptvtpxefkd --authenticationDatabase admin --oplog -o /data/dumpmongo/3.执行restore恢复mongorestore --host 10.10.12.26 --port 30018 -u rcrai -p eawmzaxtfnptvtpxefkd --authenticationDatabase admin --oplogReplay /data/dumpmongo

注意点:

1
优点:    1.操作简单,步骤少    2.只适用于小量数据(小于200G的场景)缺点:    1.如果数据量比较大(大于200G),mongorestore执行时间较长(3T数据恢复大于100H)

方法五:db.repairDatabase()(推荐指数 1 颗星)

官网该命令的定义:清理无效或损坏的数据并重建数据库索引。类似于文件系统修复命令
fsck,所以此命令主要是用于修复数据。但是需要停机业务服务,即便你不停业务服务读写的话 MongoDB 自己也会锁住直到 repair
完成。注意要有足够的磁盘空间,需要额外一倍的空间,如果 MongoDB 占用了 500G,那么 repair 时还需要额外的 500G+2G 空间。

上方法:

1
use 业务DB;db.repairDatabase();

注意点:

1
注意:    1.db.repairDatabase()主要用于修复数据。若你拥有数据的完整副本,且有权限访问,请使用第二种方法“secondary节点同步”    2.在执行命令前请保证你有比较新的备份    3.此命令会完全阻塞数据库的读写,谨慎操作    4.此命令执行需要数据文件所在位置有等同于所有数据文件大小总和的空闲空间再加上2G    5.在使用MMAPv1存储引擎的secondary节点上执行该命令可以压缩集合数据    6.在使用WiredTiger存储引擎的MongoDB库上执行不会有压缩的效果    7.再碰到特殊情况要停止运行该命令时,可通过db.currentOp()查询进程信息,然后通过db.killOp()干掉进程优点:    1.可以追加磁盘,然后将目标目录指向新加的磁盘缺点:    1.非常消耗时间    2.在生产上操作如果意外停止可能会造成数据无法恢复的危险。    3.如果磁盘空间不足,小于现在这个db占用的空间,这种情况是用不了

总结

最终我们采用的是第二种和第三种方法去做的磁盘清理方案,操作客户的数据最终还是要从时效性,稳定性,以及失败的可恢复性去考虑。大家一定要注意:1.客户数据基本都在
T
级别以上,操作大规模数据属于高危操作,每一步都要慎重,且每一个环节和步骤都要在测试环节做大数据量的测试操作。2.一定要有至少一份的备份数据,且一段时间(一周)内不能被清除。3.方案不止要准备一种,私服环境多样,根据实际情况选择合适的方案。

【注】通过对官网的文档查阅不难发现,

MongoDB删除集合数据,物理磁盘空间不会直接释放,即使drop collections也无济于事。
除非drop databases。
在MongoDB4.0及以下,官网提供了一种回收MongoDB磁盘空间的方法,即 db.repairDatabase(),但该操作有一定的风险性。
注意MongoDB4.0以上版本 db.repairDatabase()方法已经被废弃。

【注】4.2 修复数据库repairDatabase()

db.repairDatabase()操作需要停业务进行,因为MongoDB会锁库直到 repair 操作完成。
另外,必须注意预留足够的磁盘空间,需要额外一倍的空间,如果MongoDB 占用数据磁盘了100G,那么 repair 时还需要额外的100G+2G 空间。
也可以追加磁盘,然后将目标目录指向新加的磁盘。

【注】.选择压缩率更高的算法

MongoDB 默认的建表方式采用 snappy 压缩算法。如果有需要,可以采用压缩率更高的 zlib 和 zstd 算法。
我曾经在某些业务中使用 zlib 算法,相比 snappy 能再节省 50% 的存储空间,仅供参考。

1
db.runCommand({create:<collection name>, storageEngine: {wiredTiger: {configString: "block_compressor=zlib"}}})

【注】.避免索引占用太多空间

可以在 mongo shell 终端执行 db.collection.stats() 查看索引大小。如果索引大小过大,需要进行优化:

通过 indexStats
命令查看索引使用情况,对于不会使用的索引,可以考虑删除。
参考:https://www.mongodb.com/docs/v6.0/reference/operator/aggregation/indexStats/
索引包含的字段应该尽量精简;
对于大 value 字段,可以在业务允许的场景下考虑使用 Hash 索引。参考下面的测试,可以将索引的大小降低 1 个数量级;

1
2
3
4
5
使用 YCSB 插入约 260 万条数据,对其中一个字段建索引,该字段为 100B 大小的BinData.
发现 Hash 索引比普通索引的存储空间降低了一个数量级:
Hash 索引 49MB VS 普通索引 310MB.
"field0_1" : 310861824,
"field0_hashed" : 49340416