MongoDB中是否有一种方法可以识别(以便删除)除每组最近的N条记录以外的所有记录?
我们有一个MongoDB集合,我们收集了#34; docs"和"记录"。每个日志都保留对docId和日期的引用:
({_id: ObjectId})
({ docId: String, date: Date})
日志数据库变得越来越大。我想删除除以下任何日志之外的所有日志:
我知道如何删除超过30天的所有日志。但我不知道如何保留文档的最新N日志。
答案 0 :(得分:2)
在单一陈述中无法完成。你能做的就是基本上确定最后的12"对于每个可能的"docId"
值,然后在发出删除文档的请求时,通过将列表添加到$nin
来排除这些文档被删除。
您没有指定首选编程环境,但这是使用nodejs的一般过程:
const MongoClient = require('mongodb').MongoClient;
const uri = 'mongodb://localhost/test';
(async function() {
let db;
// Calculate date cutoff
let oneDay = 1000 * 60 * 60 * 24,
thirtyDays = oneDay * 30,
now = Date.now(),
cutoff = new Date(
( now - ( now % oneDay ) ) - thirtyDays
);
try {
db = await MongoClient.connect(uri);
let log = db.collection('log');
let doc = db.collection('doc');
await new Promise((resolve,reject) => {
let ops = [];
let stream = doc.find();
stream.on('error', reject);
stream.on('end', async () => {
if ( ops.length > 0 ) {
await log.bulkWrite(ops);
ops = [];
}
resolve();
});
stream.on('data', async (data) => {
// Pause processing input stream
stream.pause();
// get last 12 for doc
let last = await (log.find({ docId: data._id })
.project({ _id: 1 })
.sort({ date: -1 }).limit(12)).map(d => d._id);
ops.push({
deleteMany: {
filter: {
_id: { $nin: last },
docId: data._id,
date: { $lt: cutoff }
}
}
});
if ( ops.length >= 1000 ) {
await log.bulkWrite(ops);
ops = [];
}
// Resume processing input stream
stream.resume()
});
});
} catch(e) {
console.error(e);
} finally {
db.close();
}
})();
这里的基本前提是你循环使用" doc"收集然后执行查询" log"收集以返回最近的12个文件。然后,我们列出了每个找到的文档中的_id
值(如果有的话)。
这样做的关键是接下来要做的是对数据库发出deleteMany
操作。因为会有很多这些,所以我们将使用.bulkWrite()
而不是每次迭代源文档时发出请求。这样可以大大减少网络流量并延迟。
然后基本陈述是删除"docId"
与光标中的源匹配当前文档的所有文档,以及日期早于30天截止点的位置。
其他条件使用$nin
来排除"排除"任何被识别为在最近的12"来自上一个查询。这样可以确保始终保留这些文档,因为它们不会被删除。
ops.push({
deleteMany: {
filter: {
_id: { $nin: last },
docId: data._id,
date: { $lt: cutoff }
}
}
});
这就是它真正存在的一切。剩下的处理是关于积累"批次"当实际请求被发送到服务器以处理并实际删除文档时,直到有1000个条目(合理的大小,但是对于请求的任何16MB BSON限制都是可能的)。
当光标耗尽时,过程完成,任何剩余的"批处理"指示已经提交。
有一件事你可以摆脱目前的"即将到来的" MongoDB的发布是它允许"非相关" $lookup
的形式,这意味着我们基本上可以获得前12名"对于单个请求中的每个目标文档,而不是发出多个查询。
这样做是因为这种形式的$lookup
需要一个"管道"作为参数而不是基于本地和外键匹配的固定输出。这样,我们就可以$match
,$sort
和$limit
返回结果。
const MongoClient = require('mongodb').MongoClient;
const uri = 'mongodb://localhost/test';
(async function() {
let db;
// Calculate date cutoff
let oneDay = 1000 * 60 * 60 * 24,
thirtyDays = oneDay * 30,
now = Date.now(),
cutoff = new Date(
( now - ( now % oneDay ) ) - thirtyDays
);
try {
db = await MongoClient.connect(uri);
await new Promise((resolve,reject) => {
let ops = [];
let stream = db.collection('doc').aggregate([
{ "$lookup": {
"from": "log",
"let": {
"id": "$_id"
},
"pipeline": [
{ "$match": {
"docId": { "$eq": { "$expr": "$$id" } }
}},
{ "$sort": { "date": -1 } },
{ "$limit": 12 },
{ "$project": { "_id": 1 } }
],
"as": 'docs'
}},
]);
stream.on('error', reject);
stream.on('end', async () => {
if ( ops.length > 0 ) {
await db.collection('log').bulkWrite(ops);
ops = [];
}
resolve();
});
stream.on('data', async (data) => {
stream.pause();
ops.push({
deleteMany: {
filter: {
_id: { $nin: data.docs.map(d => d._id) },
docId: data._id,
date: { $lt: cutoff }
}
}
});
if ( ops.length >= 1000 ) {
await db.collection('log').bulkWrite(ops);
ops = [];
}
stream.resume();
});
});
} catch(e) {
console.error(e);
} finally {
db.close();
}
})();
关键是$expr
,它仅在3.5.12开发版本中定稿。这允许有效的$match
表达式,然后使其成为处理单独查询的可行替代方案。
当然,你真的想等待它为生产做好准备。但了解它是件好事,这样你就可以转换到最终升级基础MongoDB这样的过程。