在 Cloud Firestore 中,为什么无法 "single bulk" 删除集合(使用实时数据库可以做到)?
In Cloud Firestore, why is it not possible to "single bulk" delete a collection (as can be done with Realtime Database)?
使用 Firebase 实时数据库,我们只需在父节点上调用 remove ()
即可通过一条命令删除大量项目(节点已删除,所有子节点也已删除)。
但是根据 Firestore (https://firebase.google.com/docs/firestore/manage-data/delete-data#collections) 的文档:
要删除一个集合,我们必须编写一个批处理代码,循环遍历其所有文档并一个一个地删除它们。
这根本没有效率。是因为 Firestore 处于测试版还是在结构上不可能一次调用就删除完整节点(集合)?
RTDB 能够做到这一点,因为每个数据库对于单个区域都是本地的。为了提供序列化视图,当您调用 remove()
时,数据库会停止所有其他工作,直到删除完成。
此行为是多次明显中断的原因:如果 remove()
调用必须删除大量数据,则所有其他 activity 将被有效锁定,直到它完成。因此,即使对于想要删除大量数据的 RTDB 用户,我们也建议以递归方式查找和删除组中的文档 (CLI, node.js)。
另一方面,Firestore 基于更传统的 Google 式存储基础架构,其中分配了不同范围的键 dynamically to different servers(存储实际上不受 BigTable 支持,但原理相同申请)。这意味着删除数据不再是一个单一的区域操作,有效地使删除看起来像事务变得非常昂贵。 Firestore 交易目前限制为 100 个参与者,这意味着任何重要的交易批量删除都是不可能的。
我们正在研究如何最好地显示一个 API,该 API 在没有承诺的事务行为的情况下进行批量删除。很容易想象如何从移动客户端执行此操作,但正如您所观察到的那样,如果我们所做的只是为您嵌入循环和批量删除,这将不会有效。我们也不想让 REST 客户端成为第二个 class 公民。
Firestore 是一个新产品,还有很多事情要做。不幸的是,这还没有成功。虽然这是我们希望最终解决的问题,但我无法提供任何时间表。
与此同时,控制台和 firebase command-line 都提供了一种非事务性的方式来执行此操作,例如用于测试自动化。
感谢您的理解,感谢您试用 Firestore!
我很高兴从实时数据库重构我的 Firestore 应用程序,享受更短的代码和更简单的语法,直到我重构了 delete() 函数!要删除包含子集合的文档:
- 创建一个承诺数组。
get()
一个子集,没有更多的子集。
- 遍历
forEach()
函数以读取子集合中的每个文档。
- 删除每个文档,并将删除命令推送到 promises 数组中。
- 继续下一个子集合并重复此操作。
- 使用
Promise.all(arrayOfPromises)
等待所有子集合被删除。
- 然后删除top-level文档。
对于多层集合和文档,您需要将其作为一个函数,然后从另一个函数调用它以获得下一个更高层,等等。
你可以在控制台看到这个。要手动删除集合和文档,请删除 right-most 文档,然后删除 right-most 集合,依此类推。
这是我的代码,在 AngularJS 中。仅当 top-level 集合未在子集合之前被删除时才有效。
$scope.deleteClip = function(docId) {
if (docId === undefined) {
docId = $scope.movieOrTvShow + '_' + $scope.clipInMovieModel;
}
$scope.languageVideos = longLanguageFactory.toController($scope.language) + 'Videos';
var promises = [];
firebase.firestore().collection($scope.languageVideos).doc($scope.movieOrTvShow).collection('Video Clips').doc(docId).collection('SentenceTranslations').get()
.then(function(translations) {
translations.forEach(function(doc) {
console.log(doc.id);
promises.push(firebase.firestore().collection($scope.languageVideos).doc($scope.movieOrTvShow).collection('Video Clips').doc(docId).collection('SentenceTranslations').doc(doc.id).delete());
});
});
firebase.firestore().collection($scope.languageVideos).doc($scope.movieOrTvShow).collection('Video Clips').doc(docId).collection('SentenceExplanations').get()
.then(function(explanations) {
explanations.forEach(function(doc) {
console.log(doc.id);
promises.push(firebase.firestore().collection($scope.languageVideos).doc($scope.movieOrTvShow).collection('Video Clips').doc(docId).collection('SentenceExplanations').doc(doc.id).delete());
});
});
Promise.all(promises).then(function() {
console.log("All subcollections deleted.");
firebase.firestore().collection($scope.languageVideos).doc($scope.movieOrTvShow).collection('Video Clips').doc(docId).delete()
.then(function() {
console.log("Collection deleted.");
$scope.clipInMovieModel = null;
$scope.$apply();
})
.catch(function(error) {
console.log("Remove failed: " + error.message);
});
})
.catch(function(error){
console.log("Error deleting subcollections: " + error);
});
};
所有这些在实时数据库中都是一行。
这是删除集合中所有文档的最快方法:
在 python delete collection loop and python batch method
之间混合
def delete_collection(coll_ref, batch_size, counter):
batch = db.batch()
init_counter=counter
docs = coll_ref.limit(500).get()
deleted = 0
for doc in docs:
batch.delete(doc.reference)
deleted = deleted + 1
if deleted >= batch_size:
new_counter= init_counter + deleted
batch.commit()
print("potentially deleted: " + str(new_counter))
return delete_collection(coll_ref, batch_size, new_counter)
batch.commit()
delete_collection(db.collection(u'productsNew'), 500, 0)
这会以 500 个块为单位删除集合 "productNew" 中的所有文档,这是当前可以传递给提交的最大文档数。参见 Firebase write and transaction quotas。
您可以变得更复杂并处理 API 错误,但这对我来说很好用。
使用 Firebase 实时数据库,我们只需在父节点上调用 remove ()
即可通过一条命令删除大量项目(节点已删除,所有子节点也已删除)。
但是根据 Firestore (https://firebase.google.com/docs/firestore/manage-data/delete-data#collections) 的文档:
要删除一个集合,我们必须编写一个批处理代码,循环遍历其所有文档并一个一个地删除它们。
这根本没有效率。是因为 Firestore 处于测试版还是在结构上不可能一次调用就删除完整节点(集合)?
RTDB 能够做到这一点,因为每个数据库对于单个区域都是本地的。为了提供序列化视图,当您调用 remove()
时,数据库会停止所有其他工作,直到删除完成。
此行为是多次明显中断的原因:如果 remove()
调用必须删除大量数据,则所有其他 activity 将被有效锁定,直到它完成。因此,即使对于想要删除大量数据的 RTDB 用户,我们也建议以递归方式查找和删除组中的文档 (CLI, node.js)。
另一方面,Firestore 基于更传统的 Google 式存储基础架构,其中分配了不同范围的键 dynamically to different servers(存储实际上不受 BigTable 支持,但原理相同申请)。这意味着删除数据不再是一个单一的区域操作,有效地使删除看起来像事务变得非常昂贵。 Firestore 交易目前限制为 100 个参与者,这意味着任何重要的交易批量删除都是不可能的。
我们正在研究如何最好地显示一个 API,该 API 在没有承诺的事务行为的情况下进行批量删除。很容易想象如何从移动客户端执行此操作,但正如您所观察到的那样,如果我们所做的只是为您嵌入循环和批量删除,这将不会有效。我们也不想让 REST 客户端成为第二个 class 公民。
Firestore 是一个新产品,还有很多事情要做。不幸的是,这还没有成功。虽然这是我们希望最终解决的问题,但我无法提供任何时间表。
与此同时,控制台和 firebase command-line 都提供了一种非事务性的方式来执行此操作,例如用于测试自动化。
感谢您的理解,感谢您试用 Firestore!
我很高兴从实时数据库重构我的 Firestore 应用程序,享受更短的代码和更简单的语法,直到我重构了 delete() 函数!要删除包含子集合的文档:
- 创建一个承诺数组。
get()
一个子集,没有更多的子集。- 遍历
forEach()
函数以读取子集合中的每个文档。 - 删除每个文档,并将删除命令推送到 promises 数组中。
- 继续下一个子集合并重复此操作。
- 使用
Promise.all(arrayOfPromises)
等待所有子集合被删除。 - 然后删除top-level文档。
对于多层集合和文档,您需要将其作为一个函数,然后从另一个函数调用它以获得下一个更高层,等等。
你可以在控制台看到这个。要手动删除集合和文档,请删除 right-most 文档,然后删除 right-most 集合,依此类推。
这是我的代码,在 AngularJS 中。仅当 top-level 集合未在子集合之前被删除时才有效。
$scope.deleteClip = function(docId) {
if (docId === undefined) {
docId = $scope.movieOrTvShow + '_' + $scope.clipInMovieModel;
}
$scope.languageVideos = longLanguageFactory.toController($scope.language) + 'Videos';
var promises = [];
firebase.firestore().collection($scope.languageVideos).doc($scope.movieOrTvShow).collection('Video Clips').doc(docId).collection('SentenceTranslations').get()
.then(function(translations) {
translations.forEach(function(doc) {
console.log(doc.id);
promises.push(firebase.firestore().collection($scope.languageVideos).doc($scope.movieOrTvShow).collection('Video Clips').doc(docId).collection('SentenceTranslations').doc(doc.id).delete());
});
});
firebase.firestore().collection($scope.languageVideos).doc($scope.movieOrTvShow).collection('Video Clips').doc(docId).collection('SentenceExplanations').get()
.then(function(explanations) {
explanations.forEach(function(doc) {
console.log(doc.id);
promises.push(firebase.firestore().collection($scope.languageVideos).doc($scope.movieOrTvShow).collection('Video Clips').doc(docId).collection('SentenceExplanations').doc(doc.id).delete());
});
});
Promise.all(promises).then(function() {
console.log("All subcollections deleted.");
firebase.firestore().collection($scope.languageVideos).doc($scope.movieOrTvShow).collection('Video Clips').doc(docId).delete()
.then(function() {
console.log("Collection deleted.");
$scope.clipInMovieModel = null;
$scope.$apply();
})
.catch(function(error) {
console.log("Remove failed: " + error.message);
});
})
.catch(function(error){
console.log("Error deleting subcollections: " + error);
});
};
所有这些在实时数据库中都是一行。
这是删除集合中所有文档的最快方法: 在 python delete collection loop and python batch method
之间混合def delete_collection(coll_ref, batch_size, counter):
batch = db.batch()
init_counter=counter
docs = coll_ref.limit(500).get()
deleted = 0
for doc in docs:
batch.delete(doc.reference)
deleted = deleted + 1
if deleted >= batch_size:
new_counter= init_counter + deleted
batch.commit()
print("potentially deleted: " + str(new_counter))
return delete_collection(coll_ref, batch_size, new_counter)
batch.commit()
delete_collection(db.collection(u'productsNew'), 500, 0)
这会以 500 个块为单位删除集合 "productNew" 中的所有文档,这是当前可以传递给提交的最大文档数。参见 Firebase write and transaction quotas。
您可以变得更复杂并处理 API 错误,但这对我来说很好用。