【问题标题】:Delete old data with Firebase Cloud Functions使用 Firebase Cloud Functions 删除旧数据
【发布时间】:2021-04-12 13:50:28
【问题描述】:

我需要定期从 Firebase 中删除旧数据。我找到了this solution,但未能成功。

终端告诉我functions[deleteOldItems(us-central1)]: Successful update operation.但是数据没有被删除。

这就是我所做的:

const functions = require('firebase-functions');

exports.deleteOldItems = functions.database.ref('/nodeThatContainsDataToBeDeleted')
.onWrite((change, context) => {
  var ref = change.after.ref.parent; // reference to the items
  var now = Date.now();
  var cutoff = now - 2 * 60 * 60 * 1000;
  var oldItemsQuery = ref.orderByChild('timestamp').endAt(cutoff);
  return oldItemsQuery.once('value', function(snapshot) {
    // create a map with all children that need to be removed
    var updates = {};
    snapshot.forEach(function(child) {
      updates[child.key] = null
    });
    // execute all updates in one go and return the result to end the function
    return ref.update(updates);
  });
});

timestamp639915248.124176(来自 Swift)

数据库结构:

root : {
         "nodeThatContainsDataToBeDeleted" : {
            "-r5tyfX9FGC0glhgf78Ia" : {
              "company" : "CompanyCo",
              "name" : "Sam",
              "phone" : "1212",
              "imageURL" : "https://firebasestorage.googleapis.com/imageurl",
              "timestamp" : 6.39915248124176E8
            }
          },
    }

【问题讨论】:

  • 该终端消息只是说明名为deleteOldItems 的云功能已成功部署。它与您的代码无关。
  • 请在您的问题中包含您的数据库结构。
  • @samthecodingman 包括数据库结构。 nodeThatContainsDataToBeDeleted 直接在根目录下
  • 您所指的SO问题说:“只要在/path/to/items下写入数据,此函数就会触发,因此只有在修改数据时才会删除子节点。”你是如何触发这个功能的?

标签: node.js firebase firebase-realtime-database google-cloud-functions


【解决方案1】:

当您在后台触发的 Cloud Function(在您的情况下为 once()update() 方法)中执行异步操作时,您必须返回一个 Promise,这样 Cloud Function 会等待该 Promise 解决,以便终止。标题为“学习 JavaScript Promises”的三个 official videos 中的更多信息(第 2 部分和第 3 部分特别关注背景触发的云函数,但之前确实值得观看第 1 部分)。

通过执行oldItemsQuery.once('value', function(snapshot) {...}),您实际上是在使用once() 方法的回调 版本。

为了正确链接不同的promise并在回调中返回这个链,需要使用promise版本,如下:

exports.deleteOldItems = functions.database.ref('/nodeThatContainsDataToBeDeleted')
    .onWrite((change, context) => {
        var ref = change.after.ref.parent; // reference to the items
        var now = Date.now();
        var cutoff = now - 2 * 60 * 60 * 1000;
        var oldItemsQuery = ref.orderByChild('timestamp').endAt(cutoff);
        return oldItemsQuery.once('value')
            .then(snapshot => {
                // create a map with all children that need to be removed
                var updates = {};
                snapshot.forEach(function (child) {
                    updates[child.key] = null
                });
                // execute all updates in one go and return the result to end the function
                return ref.update(updates);
            });
    });

【讨论】:

    【解决方案2】:

    此答案基于@RenaudTarnecanswer

    自 2015 年以来,JavaScript 和 Firebase SDK 已经取得了长足的进步,因此在复制/使用 * 上的旧代码示例时要小心这一点。 solution you linked 还链接到 functions-samples repo,其中 example code 已与现代功能保持同步。

    正如 Renaud 所述,您需要切换到专门使用 Promise 版本的 once() 侦听器,而不是使用回调。虽然您的代码确实按预期返回了 Promise,但当前回调未正确插入到 Promise 链中,这意味着您的云函数不会等待回调中的代码完成。 linked example 是在 2015 年编写的,它曾经可以工作,但现在已经不是这样了。

    这些行:

    return oldItemsQuery.once('value', function(snapshot) {
        /* ... use snapshot ... */
      });
    

    应该是:

    return oldItemsQuery.once('value')
      .then((snapshot) => {
        /* ... use snapshot ... */
      });
    

    或:

    const snapshot = await oldItemsQuery.once('value');
    /* ... use snapshot ... */
    

    接下来,您已将 onWrite 侦听器配置为侦听 /nodeThatContainsDataToBeDeleted 上的更改。虽然这有效,但效率非常低。对于/nodeThatContainsDataToBeDeleted(例如/nodeThatContainsDataToBeDeleted/somePushId/someBoolean = true)下某个时刻数据库中的每一个小变化,您的函数都会下载嵌套在该节点下的所有数据。在此节点上只有几个条目,这无关紧要,但当您接近数千个条目时,您可能会读取数兆字节的数据。

    如果您查看链接的示例,则侦听器附加到 /path/to/items/{pushId}(单个书面条目)而不是 /path/to/items(所有条目)。 {pushId} 过去是 named wildcard,它捕获路径的最后一部分(如 -r5tyfX9FGC0glhgf78Ia)。

    这一行:

    functions.database.ref('/nodeThatContainsDataToBeDeleted') // fetches all entries
    

    应该是:

    functions.database.ref('/nodeThatContainsDataToBeDeleted/{pushId}') // fetches only the entry that changed
    

    注意:此功能的一个“错误”是它会重新触发自身。对于它删除的每个条目,deleteOldItems 将再次被触发。您可能希望使用.onCreate().onUpdate() 而不是.onWrite() - 请参阅docs 了解更多信息。

    结合这些变化给出(在撰写本文时基于the latest sample code):

    // Copyright 2017 Google Inc., Apache 2.0
    // Sourced at https://github.com/firebase/functions-samples/blob/master/delete-old-child-nodes/functions/index.js
    
    // Cut off time. Child nodes older than this will be deleted.
    const CUT_OFF_TIME = 2 * 60 * 60 * 1000; // 2 Hours in milliseconds.
    
    /**
     * This database triggered function will check for child nodes that are older than the
     * cut-off time. Each child needs to have a `timestamp` attribute.
     */
    exports.deleteOldItems = functions.database.ref('/nodeThatContainsDataToBeDeleted/{pushId}').onWrite(async (change) => {
      const ref = change.after.ref.parent; // reference to the parent
      const now = Date.now();
      const cutoff = now - CUT_OFF_TIME;
      const oldItemsQuery = ref.orderByChild('timestamp').endAt(cutoff);
      const snapshot = await oldItemsQuery.once('value');
      // create a map with all children that need to be removed
      const updates = {};
      snapshot.forEach(child => {
        updates[child.key] = null;
      });
      // execute all updates in one go and return the result to end the function
      return ref.update(updates);
    });
    

    【讨论】: