Cloud Functions中Firestore递归删除失败:无法获取待删文档
Firestore递归删除Cloud Functions报错:Failed to fetch documents to delete >= 3 times
问题概述
在Cloud Functions中实现Firestore递归删除功能时,函数返回错误:[firebase_functions/internal] FirebaseError: Deletion failed. Errors: Failed to fetch documents to delete >= 3 times。
部署函数时收到提示:[2022-12-08T22:01:07.693Z] Could not find image for function projects/onama-produce/locations/australia-southeast1/functions/recursiveDelete,但函数可正常部署完成。
已尝试的操作:
- 更新
firebase-tools版本 - 重新生成并配置
login:citoken - 设置
process.env.DEBUG = "true"但未获取到有效调试日志(不确定日志位置) - 命令行执行
firebase firestore:delete users/iALet1LOthcw0Nx6FMgP6nOa3N83可成功完成删除,说明权限和路径无问题
代码详情
Cloud Functions(TypeScript)
/* eslint @typescript-eslint/no-var-requires: "off" */ import * as functions from "firebase-functions"; process.env.DEBUG = "true"; const firebaseTools = require("firebase-tools"); /** * Initiate a recursive delete of documents at a given path. * * The calling user must be authenticated. * * This delete is NOT an atomic operation and it's possible * that it may fail after only deleting some documents. * * @param {string} data.path the document or collection path to delete. */ exports.recursiveDelete = functions .region("australia-southeast1") .runWith({ timeoutSeconds: 540, memory: "2GB", }) .https.onCall(async (data, context) => { // Only allow authorised users to execute this function. if (!(context.auth && context.auth.token)) { throw new functions.https.HttpsError( "permission-denied", "Must be an administrative user to initiate delete." ); } const path = data.path; console.log( `User ${context.auth.uid} has requested to delete path ${path}` ); // Run a recursive delete on the given document or collection path. // The 'token' must be set in the functions config, and can be generated // at the command line by running 'firebase login:ci'. try { await firebaseTools.firestore .delete(path, { project: process.env.GCP_PROJECT, recursive: true, force: true, token: process.env.TOKEN, }); return { path: path, }; } catch (err) { throw new functions.https.HttpsError("internal", String(err)); } });
Flutter客户端代码
/// Call the 'recursiveDelete' callable function with a path to initiate /// a server-side delete. void deleteAtPath(String path) async { print("delete: deleting at path " + path); try { final HttpsCallableResult deleteFnResult = await functions .httpsCallable('recursiveDelete') .call<String, String>({ "path": path, }); print(deleteFnResult.toString()); } on FirebaseFunctionsException catch (e) { print("firebase error: " + e.toString() + " for deleting path " + path); } catch (e) { print("path delete error " + e.toString() + " for deleting path " + path); } }
deleteUserProfile(String _uid) async { deleteAtPath(_userCollection.doc(_uid).path); deleteAtPath(_locationsCollection.doc(_uid).path); deleteEachProduct(productsFromUserProfileQuery(_uid)); return; }
package.json
{ "name": "functions", "scripts": { "lint": "eslint --ext .js,.ts .", "build": "tsc", "build:watch": "tsc --watch", "serve": "npm run build && firebase emulators:start --only functions", "shell": "npm run build && firebase functions:shell", "start": "npm run shell", "deploy": "firebase deploy --only functions", "logs": "firebase functions:log" }, "engines": { "node": "16" }, "main": "lib/index.js", "dependencies": { "firebase-admin": "^11.3.0", "firebase-functions": "^4.1.0", "firebase-tools": "^11.17.0" }, "devDependencies": { "@typescript-eslint/eslint-plugin": "^5.12.0", "@typescript-eslint/parser": "^5.12.0", "eslint": "^8.9.0", "eslint-config-google": "^0.14.0", "eslint-plugin-import": "^2.25.4", "firebase-functions-test": "^0.2.0", "typescript": "^4.5.4" }, "private": true }
解决方案
1. 验证TOKEN环境变量配置
确保process.env.TOKEN已正确配置:
- 重新执行
firebase login:ci生成新token - 运行
firebase functions:config:set token="YOUR_CI_TOKEN"设置环境变量 - 部署前添加日志验证token是否存在:
console.log('TOKEN配置状态:', !!process.env.TOKEN);
2. 升级firebase-tools版本
当前使用的11.17.0版本可能存在兼容问题,升级到最新稳定版:
npm install firebase-tools@latest --save
3. 调整递归删除的重试逻辑
在firebase-tools.firestore.delete中添加重试配置,或手动实现指数退避重试:
const maxRetries = 5; let retryCount = 0; let deletionSuccess = false; while (retryCount < maxRetries && !deletionSuccess) { try { await firebaseTools.firestore.delete(path, { project: process.env.GCP_PROJECT, recursive: true, force: true, token: process.env.TOKEN, }); deletionSuccess = true; } catch (err) { retryCount++; if (retryCount === maxRetries) throw err; // 指数退避等待 await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, retryCount))); } }
4. 检查函数权限与区域一致性
- 确保Cloud Functions所在区域(australia-southeast1)与Firestore数据库区域一致,避免跨区域网络延迟
- 在GCP控制台的IAM页面,验证函数服务账号(格式:
PROJECT_ID@appspot.gserviceaccount.com)拥有Cloud Datastore Owner或Firebase Admin角色
5. 获取详细调试日志
- 运行
firebase functions:log --tail实时查看函数执行日志 - 在错误捕获块中添加详细日志:
catch (err) { console.error('删除错误详情:', JSON.stringify(err)); throw new functions.https.HttpsError("internal", String(err)); }
内容的提问来源于stack exchange,提问作者kale
相关产品推荐
相关产品推荐

