【问题标题】:Count with Firestore Cloud Functions使用 Firestore 云函数计数
【发布时间】:2019-02-21 13:25:46
【问题描述】:

我喜欢使用 Firestore 云功能计算子集合中的文档数量。

我的数据库如下所示:groups/{groupId}/members/{memberId}

我喜欢计算每个组的成员数 (memberId)。这意味着每个组可以有不同数量的成员,并且可以灵活地增加或减少。

会对您的想法感到高兴:-)。

【问题讨论】:

  • 您可以查看以下 SO 问题/答案。它不能完全回答您的问题,但非常接近它。 stackoverflow.com/questions/54653836/…
  • @RenaudTarnec 谢谢,但无济于事,因为我需要无限且​​准确的文档数量。也许你有别的想法?谢谢
  • 只保留另一个带有计数的节点。添加节点时,计数器递增,删除节点时,计数器递减。这是少量数据,拥有那个“计数器”节点不会真正影响任何事情。

标签: firebase google-cloud-firestore google-cloud-functions


【解决方案1】:

我花了一些时间才让这个工作,所以我想我会分享给其他人使用:

'use strict';

const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();

exports.countDocumentsChange = functions.firestore.document('library/{categoryId}/documents/{documentId}').onWrite((change, context) => {

    const categoryId = context.params.categoryId;
    const categoryRef = db.collection('library').doc(categoryId)
    let FieldValue = require('firebase-admin').firestore.FieldValue;

    if (!change.before.exists) {

        // new document created : add one to count
        categoryRef.update({numberOfDocs: FieldValue.increment(1)});
        console.log("%s numberOfDocs incremented by 1", categoryId);

    } else if (change.before.exists && change.after.exists) {

        // updating existing document : Do nothing

    } else if (!change.after.exists) {

        // deleting document : subtract one from count
        categoryRef.update({numberOfDocs: FieldValue.increment(-1)});
        console.log("%s numberOfDocs decremented by 1", categoryId);

    }

    return 0;
});

【讨论】:

    【解决方案2】:

    我考虑了两种可能的方法。

    1.直接统计集合的文档

    您可以使用QuerySnapshotsize 属性,就像

    admin.firestore().collection('groups/{groupId}/members/{memberId}')
        .get()
        .then(querySnapshot => {
            console.log(querySnapshot.size);
            //....
            return null;
        });
    

    这里的主要问题是成本,如果子集合包含大量文档:通过执行此查询,您将为每个文档收取一次阅读费用的子集合。

    2。另一种方法是为每个子集合维护一些计数器

    您将编写两个云函数,基于分布式计数器,如以下 Firebase 文档项中所述:https://firebase.google.com/docs/firestore/solutions/counters。我们在下面的例子中使用了 3 个分片。

    首先,当将新文档添加到 subCollec 子集合时,云函数会增加计数器:

    //....
    const num_shards = 3;
    //....
    
    exports.incrementSubCollecCounter = functions
      .firestore.document('groups/{groupId}/members/{memberId}')
      .onCreate((snap, context) => {
    
        const groupId = context.params.groupId;
    
        const shard_id = Math.floor(Math.random() * num_shards).toString();
        const shard_ref = admin
          .firestore()
          .collection('shards' + groupId)
          .doc(shard_id);
    
        if (!snap.data().counterIncremented) {
          return admin.firestore().runTransaction(t => {
            return t
              .get(shard_ref)
              .then(doc => {
                if (!doc.exists) {
                  throw new Error(
                    'Shard doc #' +
                      shard_id +
                      ' does not exist.'
                  );
                } else {
                  const new_count = doc.data().count + 1;
                  return t.update(shard_ref, { count: new_count });
                }
              })
              .then(() => {
                return t.update(snap.ref, {
                  counterIncremented: true    //This is important to have the Function idempotent, see https://cloud.google.com/functions/docs/bestpractices/tips#write_idempotent_functions
                });
              });
          });
        } else {
          console.log('counterIncremented NOT NULL');
          return null;
        }
      });
    

    然后,当从 subCollec 子集合中删除文档时,第二个 Cloud Function 会减少计数器:

    exports.decrementSubCollecCounter = functions
      .firestore.document('groups/{groupId}/members/{memberId}')
      .onDelete((snap, context) => {
    
        const groupId = context.params.groupId;
    
        const shard_id = Math.floor(Math.random() * num_shards).toString();
        const shard_ref = admin
          .firestore()
          .collection('shards' + groupId)
          .doc(shard_id);
    
        return admin.firestore().runTransaction(t => {
          return t.get(shard_ref).then(doc => {
            if (!doc.exists) {
              throw new Error(
                'Shard doc #' +
                  shard_id +
                  ' does not exist.'
              );
            } else {
              const new_count = doc.data().count - 1;
              return t.update(shard_ref, { count: new_count });
            }
          });
        });
      });
    

    这里,相比方案1,由于我们有3个分片,当你想知道subCollec子集合中的文档数时,你只需要读取3个文档。

    有关如何初始化分布式计数器的详细信息,请查看文档。您必须为每个groupId 集合初始化一次(即admin.firestore().collection('shards' + groupId)

    【讨论】:

    • 谢谢,尝试过但收到错误消息:“ReferenceError: groupId is not defined” for line “.collection('shards' + groupId)”。有什么想法吗?
    • 您应该在 const groupId = context.params.groupId; 行之后验证 groupId 的值是否正确。通过执行 console.log(groupId) 并查看 Cloud Function 日志中的输出。
    • 总是收到以下错误 - 不知道为什么。错误:分片文档#0 不存在。在 t.get.then.doc (/user_code/index.js:119:21) 在 process._tickDomainCallback (internal/process/next_tick.js:135:7)
    • 是的,正如我在答案底部所说:«查看文档以了解有关如何初始化分布式计数器的详细信息。您必须为每个 groupId 集合初始化一次(即 admin.firestore().collection('shards' + groupId) »。您必须学习并完全理解此文档firebase.google.com/docs/firestore/solutions/counters
    • 在这种情况下使用分片实际上是个坏主意。阅读文档的成本更高,并且每秒不会超过 1 次写入。在这里查看我的答案:stackoverflow.com/questions/46554091/…
    猜你喜欢
    • 1970-01-01
    • 2020-03-04
    • 1970-01-01
    • 2019-08-28
    • 2021-02-13
    • 2021-04-11
    • 1970-01-01
    • 1970-01-01
    • 2018-03-28
    相关资源
    最近更新 更多