业务背景
系统中有个mongodb集合每天增长上千万,时间长了,系统中这个集合已经有几亿数据了,需要写一个定时任务,把集合中三个月前的数据删除,并且后面每天凌晨执行这个定时任务.
方案分析
方案一
使用Mongo的游标MongoCursor,遍历获取集合id获取id列表idList,然后根据idList批量删除
方案二
使用mongoTemplate.find,每次查询1万条数据,遍历获取集合id获取id列表idList,然后根据idList批量删除
代码片段
方案一
/**
* 删除集合数据
*
* @param time 传三个月前的时间戳
*/
private void deleteData(Long time) {
Query query = new Query(new Criteria("ct").lt(time));
MongoCursor<Document> dbCursor = mongoTemplate.getCollection(COLLECTION_NAME)
.find(query.getQueryObject())
//设置游标查询不超时
.noCursorTimeout(true)
//设置批量从数据库中获取的数据量
.batchSize(10000)
.iterator();
int i = 0;
List<String> idList = new ArrayList<>();
Document next;
while (dbCursor.hasNext()) {
i++;
next = dbCursor.next();
String id = next.get("_id").toString();
idList.add(id);
if (i % 10000 == 0) {
List<String> idListToDelete = new ArrayList<>();
idListToDelete.addAll(idList);
// 清空数据
idList.clear();
log.info("delete: {}", i);
// 异步删除数据
threadPool.execute(new Runnable() {
@Override
public void run() {
doDeleteData(idListToDelete);
}
});
}
}
// 再次判断
if (CollectionUtils.isNotEmpty(idList)) {
doDeleteData(idList);
idList.clear();
}
log.info("data delete total: {}", i);
}
方案二
/**
* 删除集合数据
*
* @param time 传3个月前时间戳
*/
private void deleteData(Long time) {
Query query = new Query(new Criteria("ct").lt(time));
// 只需要去_id字段即可,避免取多个字段,数据量大了占内存过多
query.fields().include("_id");
query.limit(10000);
List<XXX> xxxList = mongoTemplate.find(query, XXX.class);
List<String> idListToDelete;
int i = 0;
while (CollectionUtils.isNotEmpty(xxxList)) {
i += xxxList.size();
idListToDelete = xxxList.stream().map(o -> o.getId()).collect(Collectors.toList());
doDeleteData(idListToDelete);
log.info("delete: {}", i);
xxxList = mongoTemplate.find(query, XXX.class);
}
log.info("delete total: {}", i);
}
/**
* 执行删除数据
*/
private void doDeleteData(List<String> idList) {
long s0 = System.currentTimeMillis();
Query query = new Query(new Criteria("_id").in(idList));
mongoTemplate.remove(query, XXX.class);
long s1 = System.currentTimeMillis();
log.info("delete data size: {}, cost: {}", idList.size(), (s1 - s0));
}
对比
两种方案都是可行的,只不过是速度上的差别,大致速度如下(当时没有记录数据,大致是这个速度)
发送量 | 方案一(游标) | 方案二(query) |
10万 | 7s | 4s |
400万 | 7分 | 2分 |
总结
实际在测试的过程中,发现删除数据其实花费时间很少,大概200多毫秒删1万条,但MongoCursor在遍历循环的时候比较耗时while (dbCursor.hasNext()) { }。
而mongoTemplate.find在查询的数据的时候,如果数据有索引,每次查询1万条其实速度是挺快的,根据找到符合条件的1万条数据,立即返回。(顺便说下,mongoTemplate.count计算总数比较慢)
遇到类似的问题,可以结合业务,对两种方案进行测试,一般第二种方案效率更高一点。
有其他更好的方案欢迎交流!