You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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:ci token
  • 设置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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 10:35:25