背景
某天早上照常进行数据库巡检,发现了MongoDB 集群分片中某个节点的磁盘使用率已经达到了 75%。我立即对该节点的数据库和集合进行了空间分析,发现一个名为 "visitor" 的集合数据量已经达到了 20 多亿条,占用了 260GB 的磁盘空间。我与研发团队讨论后决定清理数据,但需要保留最近半年的数据。然而,我们面临一个尴尬的问题:时间字段没有索引!!!
问题分析
问题主要还是前期产品设计没有考虑历史数据清除策略,任由其数据肆意增长,增长到20亿,时间字段也未添加索引。同时还存在一个严重弊端,这么大的集合未开启分片,导致整个集合数据都存储到同一个shard分片上。shard分片磁盘使用严重倾斜,其他分片只用了25%,当前索引顺序也存在不合理的地方。
集合索引
代码语言:javascript复制db.getCollection("visitor").createIndex({
companyId: NumberInt("1"),
visitorStaticId: NumberInt("1")
}, {
name: "companyId_1_visitorStaticId_1",
unique: true
});
解决方案
方案一:添加索引
夜深人静的时候悄悄地把createTime字段后台模式添加索引,综合业务场景(AI客服)、配置(8C16G)、库涉及的业务等,此方案可能会把数据库整崩溃,风险极大,不采用。
方案二:按天迁移数据到新集合
通过写脚本,按照每天的维度,将最近半年的数据分批导入到新表,然后进行rename操作。粗浅地将脚本写完后,进行了简单测试,发现没有索引,查询一天的数据太久,这种方式周期太长,工作量也较大,数据准确性存在较大风险。不采用
代码语言:javascript复制// 获取当前日期
var currentDate = new Date();
// 获取180天前的日期
var startDate = new Date();
startDate.setDate(startDate.getDate() - 180);
// 将日期转换为毫秒数
var startTime = startDate.getTime();
var endTime = currentDate.getTime();
// 一天的毫秒数
var oneDay = 24 * 60 * 60 * 1000;
// 循环处理每一天的数据
for (var time = startTime; time < endTime; time = oneDay) {
// 构造查询条件
var query = {
"createTime": {
"$gte": time,
"$lt": time oneDay
}
};
// 查询数据
var results = db.el_frequent_visitor.find(query);
// 将查询结果插入到 t2 集合中
results.forEach(function(doc) {
db.t2.insert(doc);
});
}
方案三:使用DTS将数据导入新建集合
步骤一:新建优化好的集合
代码语言:javascript复制//建新的集合
db.createCollection("visitor_tmp0426");
//修改联合索引顺序
db.getCollection("visitor_tmp0426").createIndex({
visitorStaticId: NumberInt("1"),
companyId: NumberInt("1")
}, {
name: "visitorStaticId_1companyId__1",
unique: true
});
//创建时间字段索引
db.visitor_tmp0426.createIndex({"createTime":1}, {background: true})
//开启库、集合分片
sh.enableSharding("calldb")
sh.shardCollection("calldb.visitor_tmp0426",{"visitorStaticId":"hashed"})
步骤二:使用DTS进行数据迁移
步骤三:重命名集合
三种方案,最后采用了第三种,这种相对安全、稳定,对数据库影响较小。
注意事项
- 注意磁盘的使用量
- DTS速率尽量选用规格较低的
- 业务低峰操作
大家如果还有更好的建议,踊跃发言,一起看看还有没有更合理的方案