Python 从GridFS中清除孤立文件
我有一个引用GridFS文件的集合,通常每个记录1-2个文件。这些集合相当大——父集合中约有705k条记录,790k个GridFS文件。随着时间的推移,出现了许多孤立的GridFS文件—父记录被删除,但引用的文件没有被删除。我现在正试图清除GridFS集合中的孤立文件 类似建议的方法的问题在于,将700k记录组合到一个大的ID列表中会产生内存约为4mb的Python列表,将其传递到fs.files集合上Mongo中的$nin查询中需要花费很长时间。执行相反的操作(获取fs.files中所有ID的列表并查询父集合以查看它们是否存在)也需要花费很长时间Python 从GridFS中清除孤立文件,python,mongodb,mongodb-query,gridfs,Python,Mongodb,Mongodb Query,Gridfs,我有一个引用GridFS文件的集合,通常每个记录1-2个文件。这些集合相当大——父集合中约有705k条记录,790k个GridFS文件。随着时间的推移,出现了许多孤立的GridFS文件—父记录被删除,但引用的文件没有被删除。我现在正试图清除GridFS集合中的孤立文件 类似建议的方法的问题在于,将700k记录组合到一个大的ID列表中会产生内存约为4mb的Python列表,将其传递到fs.files集合上Mongo中的$nin查询中需要花费很长时间。执行相反的操作(获取fs.files中所有ID的
有人提出过这个问题,并提出了一个更快的解决方案吗? < P>首先,让我们花些时间来考虑<强>实际上< <强> >是什么。首先,让我们阅读参考的手册页面: GridFS是一种用于存储和检索超过16MB BSON文档的文件的规范 因此,有了它,这很可能就是您的用例。但这里要吸取的教训是,存储文件的“转到”方法不是自动的 在您(和其他人)的案例中发生的事情是因为“驱动程序级别”规范(MongoDB本身也有no魔力),您的“文件”被“拆分”到两个集合中。一个集合用于内容的主要引用,另一个集合用于数据的“块” 您(和其他人)的问题是,既然“主”引用已被删除,您就已经设法留下了“块”。那么有了大量的孤儿,如何摆脱孤儿呢 您当前的阅读内容是“循环并比较”,并且由于MongoDB不进行连接,因此没有其他答案。但是有一些事情可以帮上忙 因此,与其运行一个庞大的
$nin
,不如尝试做一些不同的事情来打破这种局面。考虑逆序的工作,例如:
db.fs.chunks.aggregate([
{“$group”:{“\u id”:“$files\u id”},
{“$limit”:5000}
])
因此,您要做的是从所有条目中获取5000个条目的不同的“files\u id”值(即对fs.files
的引用)。然后,您当然会返回循环,检查fs.files
是否有匹配的\u id
。如果找不到内容,则从“块”中删除与“文件id”匹配的文档
但这仅仅是5000,所以保留该集中找到的最后一个id,因为现在您将再次运行相同的聚合语句,但不同:
db.fs.chunks.aggregate([
{“$match”:{“files_id”:{“$gte”:last_id}},
{“$group”:{“\u id”:“$files\u id”},
{“$limit”:5000}
])
因此这是有效的,因为ObjectId
值正在增加或“不断增加”。因此,所有新的条目总是大于上一个条目。然后,您可以再次循环这些值,并在未找到的位置执行相同的删除操作
这会“永远”吗。嗯,是的。您可以为此雇用员工,但请阅读文档。但总的来说,这是使用两个系列所付出的代价
回到起点。规范是这样设计的,因为它特别想绕过16MB限制。但是如果这不是你的限制,那么首先要问你为什么要使用
MongoDB在给定BSON文档的任何元素中存储“二进制”数据都没有问题。因此,您不需要仅使用来存储文件。如果你这样做了,那么你的所有更新都将是完全“原子的”,因为它们一次只作用于一个集合中的一个文档
因为有意地将文档分割到不同的集合中,所以若您使用它,那个么您将承受痛苦。因此,如果您需要它,请使用它,但是如果您需要而不是,则只需将BinData
存储为正常字段,这些问题就会消失
但至少你有一个比把所有东西都载入内存更好的方法。我想在这个讨论中补充我的一点。根据差异的大小,您可能会发现首先查找文件的标识是合理的,您必须首先保留,然后删除不应保留的块。当您管理大量临时文件时,可能会发生这种情况 在我的例子中,我们每天都有相当数量的临时文件保存到GridFS中。我们目前有大约180k个临时文件和一些非临时文件。当到期指数达到时,我们最终得到大约40万孤儿 在试图查找这些文件时,要知道的有用的一点是ObjectID是基于时间戳的。因此,您可以缩小日期之间的搜索范围,但将范围限定在
\u id
或文件\u id
上
要开始查找文件,我从以下日期开始循环:
var nowDate = new Date();
nowDate.setDate(nowDate.getDate()-1);
var startDate = new Date(nowDate);
startDate.setMonth(startDate.getMonth()-1) // -1 month from now
var endDate = new Date(startDate);
endDate.setDate(startDate.getDate()+1); // -1 month +1 day from now
while(endDate.getTime() <= nowDate.getTime()) {
// interior further in this answer
}
并收集到文件的可变ID,该ID确实存在于集合。文件:
var found = db.getCollection("collection.files").find({
_id: {
$gte: idGTE,
$lt: idLT
}
}).map(function(o) { return o._id; });
目前,我在found
变量中有大约50个ID。现在,为了删除.chunks
集合中孤立项上的大量内容,我正在循环搜索要删除的100个ID,因为我没有找到任何内容:
var removed = 0;
while (true) {
// note that you have to search in a IDs range, to not delete all your files ;)
var idToRemove = db.getCollection("collection.chunks").find({
files_id: {
$gte: idGTE, // important!
$lt: idLT, // important!
$nin: found, // `NOT IN` var found
},
n: 0 // unique ids. Choosen this against aggregate for speed
}).limit(100).map(function(o) { return o.files_id; });
if (idToRemove.length > 0) {
var result = db.getCollection("collection.chunks").remove({
files_id: {
$gte: idGTE, // could be commented
$lt: idLT, // could be commented
$in: idToRemove // `IN` var idToRemove
}
});
removed += result.nRemoved;
} else {
break;
}
}
之后增加日期以接近当前日期:
startDate.setDate(startDate.getDate()+1);
endDate.setDate(endDate.getDate()+1);
有一件事我现在无法解决,那就是移除操作需要相当长的时间。根据文件\u id
查找和删除块需要每~200个块(100个唯一id)3-5秒
startDate.setDate(startDate.getDate()+1);
endDate.setDate(endDate.getDate()+1);
var startDate = new Date();
startDate.setDate(startDate.getDate()-3) // from -3 days
var endDate = new Date();
endDate.setDate(endDate.getDate()-1); // until yesterday
var idGTE = new ObjectID(startDate.getTime()/1000);
var idLT = new ObjectID(endDate.getTime()/1000);
var found = db.getCollection("collection.files").find({
_id: {
$gte: idGTE,
$lt: idLT
}
}).map(function(o) { return o._id; });
db.getCollection("collection.chunks").deleteMany({
files_id: {
$gte: idGTE,
$lt: idLT,
$nin: found,
}
}, {
writeConcern: {
w: 0 // "fire and forget", allows you to close console.
}
});
/*
* This function will count orphaned chunks grouping them by file_id.
* This is faster but uses more memory.
*/
function countOrphanedFilesWithDistinct(){
var start = new Date().getTime();
var orphanedFiles = [];
db.documents.chunks.distinct("files_id").forEach(function(id){
var count = db.documents.files.count({ "_id" : id });
if(count===0){
orphanedFiles.push(id);
}
});
var stop = new Date().getTime();
var time = stop-start;
print("Found [ "+orphanedFiles.length+" ] orphaned files in: [ "+time+"ms ]");
}
/*
* This function will delete any orphaned document cunks.
* This is faster but uses more memory.
*/
function deleteOrphanedFilesWithDistinctOneBulkOp(){
print("Building bulk delete operation");
var bulkChunksOp = db.documents.chunks.initializeUnorderedBulkOp();
db.documents.chunks.distinct("files_id").forEach(function(id){
var count = db.documents.files.count({ "_id" : id });
if(count===0){
bulkChunksOp.find({ "files_id" : id }).remove();
}
});
print("Executing bulk delete...");
var result = bulkChunksOp.execute();
print("Num Removed: [ "+result.nRemoved+" ]");
}