MongoDB之如何优雅的清理亿级大表

背景

最近公司的其中一个业务量暴增,MongoDB月增数据从之前的百GB级别暴增到TB级别,磁盘剩余空间马上开始预警,新硬件设备采购需要时间,无法快速扩容,另一方面为了节省成本就先尝试进行数据清理。最终决定先清理几个二三百G的集合,数据量大概在40亿左右,释放一部分磁盘空间,不至于磁盘被快速撑爆。

其中一个待集合大小:数据量35亿,占用磁盘空间300G

数据清理方案

数据清理需要进行3方面的考量,第一,这些数据需要归档吗?第二,如何平稳的清理?即保证在数据清理的过程中不会引起系统负载的升高,不会对现有业务造成冲击,甚至hang死数据库;第三,在保持平稳的前提下,如何快速的清理?

对于第一个的问题,通过和业务沟通过之后已经确认这些集合数据不需要归档,可以放心清理。

对于第二和第三个问题则主要看操作手法了,手法精妙则又快又稳,手法但凡出现偏差则连夜站票跑路。能想到的手法主要有两种:第一,通过逻辑delete删除的方式,比如一次删5000~1w,然后sleep 1s;第二,通过drop的方式直接删除集合。我们将在接下来讨论这两种方案可行性。

逻辑delete删除

对于逻辑一次性批量删除多个文档的方法主要有以下几种方法:

  • collection.remove()

  • collection.deleteMany()

  • delete命令

  • bulk remove方式

下面我们将分别测试各种方式的效率,我们将每次删除5w行,然后看谁的表现更好。

collection.remove()方法

PRIMARY> var collection = db.getCollection('my_collection');
PRIMARY> function myRemove(){
...     var docsToDelete = collection.find({},{_id:1}).limit(50000).toArray();
...     var idsToDelete = docsToDelete.map(function(doc) { return doc._id; });
...     print(new Date());
...     print(collection.remove({_id: {$in: idsToDelete}}));
...     print(new Date());
... }
PRIMARY> myRemove()
Fri Aug 23 2024 14:51:14 GMT+0800 (CST)
WriteResult({ "nRemoved" : 50000 })
Fri Aug 23 2024 14:51:15 GMT+0800 (CST)

结果:删除耗时1s

collection.deleteMany()

PRIMARY> var collection = db.getCollection('my_collection');
PRIMARY> function myDeleteMany(){
...     var docsToDelete = collection.find({},{_id:1}).limit(50000).toArray();
...     var idsToDelete = docsToDelete.map(function(doc) { return doc._id; });
...     print(new Date());
...     var deleteResult=collection.deleteMany({_id: {$in: idsToDelete}})
...     print(deleteResult.deletedCount);
...     print(new Date());
... }
PRIMARY> myDeleteMany()
Fri Aug 23 2024 14:57:25 GMT+0800 (CST)
50000
Fri Aug 23 2024 14:57:26 GMT+0800 (CST)

结果:删除耗时1s

delete命令

PRIMARY> var collection = db.getCollection('my_collection');
PRIMARY> function myDelete(){
...     var docsToDelete = collection.find({},{_id:1}).limit(50000).toArray();
...     var idsToDelete = docsToDelete.map(function(doc) { return doc._id; });
...     print(new Date());
...     var deleteResult=db.runCommand({delete: "my_collection",deletes: [ { q: { _id: {$in: idsToDelete} }, limit: 0 } ]});
...     print(deleteResult.n);
...     print(new Date());
... }
PRIMARY> myDelete()
Fri Aug 23 2024 15:05:27 GMT+0800 (CST)
50000
Fri Aug 23 2024 15:05:29 GMT+0800 (CST)

结果:删除耗时2s

bulk remove方式

PRIMARY> var collection = db.getCollection('my_collection');
PRIMARY> function myBulkRemove(){
...     var docsToDelete = collection.find({},{_id:1}).limit(50000).toArray();
...     var idsToDelete = docsToDelete.map(function(doc) { return doc._id; });
...     print(new Date());
...     var bulk = collection.initializeOrderedBulkOp();               
...     bulk.find( {_id: {$in: idsToDelete}} ).remove();
...     print(bulk.execute());
...     print(new Date());
... }
PRIMARY> myBulkRemove()
Fri Aug 23 2024 14:59:50 GMT+0800 (CST)
BulkWriteResult({
    "writeErrors" : [ ],
    "writeConcernErrors" : [ ],
    "nInserted" : 0,
    "nUpserted" : 0,
    "nMatched" : 0,
    "nModified" : 0,
    "nRemoved" : 50000,
    "upserted" : [ ]
})
Fri Aug 23 2024 14:59:51 GMT+0800 (CST)

结果:删除耗时1s

对比来看,各个删除方法的效率都差不多,那么通过这种逻辑删除的方案可行吗?首先看平稳性性,通过逻辑删除的方式我们可以随时中断,随时开始,一次删多少、间隔几秒,完全自主可控,可以说是非常平稳了。但是效率方面就不太尽人意了,假设为了删除操作不会影响到线上业务,设置一次删除1w,删除间隔为1s,那么可以理解为每秒删除5000。做个简单的计算:5000*3600*24=4亿3000万,那我删除一张40亿的集合,就要删将近10天10夜,即使加加量,平均每秒删1w,也需要5天5夜,效率方面完全不能接受。

drop直接删除集合

通过drop直接删除集合和通过逻辑delete删除完全是另一个极端,直接就是一刀切,一条命令下去直接就给集合嘎了,效率非常快,但是问题是,drop的时候还会顺带的清理数据文件和索引文件,那么必然会引起磁盘IO的快速上升,进而影响到线上业务。

那么就没有一个完美解决方案吗?有!

解决方案就是我们先将待删除集合的数据文件和索引文件找出来,然后给其建立硬链接,随后再执行drop操作,drop操作在清理磁盘数据文件和索引文件的时候只会删除其链接,不会真正删除文件,等操作完成之后,我们就可以再分配一点点的删除残留的数据文件和索引文件,尽享丝滑,即不会对业务造成任何影响,删除效率也非常高。

具体操作如下:

# 确认数据文件路径 
db.my_collection.stats().wiredTiger.uri 

# 获取文档中的索引
db.my_collection.getIndexes()

# 确认每个索引的路径
db.my_collection.stats({indexDetails:true}).indexDetails._id_.uri 

# 给数据文件和每个索引文件创建硬连接 
ln collection-21--2919691543483114411.wt collection-21--2919691543483114411.wt.hdlk
ln index-28--2919691543483114411.wt index-28--2919691543483114411.wt.hdlk

# 删除文档
db.my_collection.drop()

# 分批一点点删除数据文件和索引文件

最终平稳且快速的清理了历史集合,且对线上业务没有造成任何冲击,可以说是非常优雅了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吃饭端住碗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值