Vic*_*nto 5 database scalability data-modeling firebase google-cloud-firestore
来自文档:
这也可能导致单个事件的多个调用,因此对于最高质量的函数,确保函数被写为幂等的.
因此,如果Firestore没有提供计算集合中子文档数量的方法,我需要创建一个云函数来聚合节点上的这些信息/counters/{type}/count.
如果我执行写入触发器并递增值,则我的计数器可能无法反映实际的文档计数,对吧?
如何编写一个函数来完美地计算集合中的文档(不要太昂贵 - 假设我不想在每次写入时读取整个集合)?
这个问题的答案将取决于您如何使用该集合的不同方面,以及"完美计数"对您意味着什么.
首先,由于Cloud Function调用与write异步,因此计数器会略微落后于集合的真实计数.我假设这没关系.
即使您通过阅读每个文档来计算集合,计数仍可能是陈旧的,因为在您计数时可能已插入或删除了文档.
你提到"没有太贵".在这里,我们需要了解您阅读计数的频率与添加或删除文档的频率.要维护一个单独的计数器,您将为每个文档计数更改进行读/写.由于写入是读取价格的3倍,这意味着您需要将每个文档计数4次或更多次以恢复保持计数的成本.这里有一个公式,考虑到文档生命周期内的平均计数,但我会将其作为读者的练习.
这是一个有趣的问题,这是分布式系统的另一个常见问题.如果客户端请求添加+1计数器,并且请求超时(服务器永不响应) - 再次请求是否安全?如果服务器确实应用了增量但后来遇到网络问题怎么办?如果没有怎么办?
下面我将回答一些处理这种情况的方法.
解决此问题的一种方法是使用增量请求发送唯一的事务ID(txid).如果服务器之前已经处理过txid,它知道它是一个重复的请求,并且可以响应它已经完成它.
在您的用例中,如果您从不删除文档,则可以使用文档ID作为txid.在计数器中,当您+1时,将文档ID添加到已处理的增量数组中.在执行此操作之前,请检查数组中是否已存在(表明它已被处理).
上面的一个明显问题是阵列将继续增长,最终变得太大.因此,我们希望限制我们跟踪旧ID的时间.您可以使用时间戳并删除早于"X"的所有内容,或者您可以简单地将数组视为循环缓冲区以使其保持固定的最大大小.
这两种方法对于慢写入速率都是合理的,但不足以加快写入速度.例如,在1000次写入/秒时,这将是5000个文档ID,仅覆盖5秒(我们在限制文档中提到函数可能需要超过5秒才能执行).
输入Forgetful Bloom Filters
这种方法为您提供了更高的写入速率支持,以换取您认为之前已经看过文档ID的极小可能性.
我不会在这里详细介绍这个实现,但是在这个博客中有一个很好的概述:计数器,幂等和忘记的绽放过滤器
另一个复杂性是处理删除.如果您使用唯一ID并确定它不会被重用(例如我们的原生Auto id支持),那么添加起来并不难.只需重复您对添加的操作,但在单独的列表/字段中,并确保检查两个列表.
需要考虑的一件小事是Cloud Functions没有保证执行顺序.这意味着如果插入完全靠近,您可能会在插入之前看到删除.
我的建议是,如果你在插入之前看到删除,请提前减少计数器,知道它将很快被修复,如果你在删除后看到插入,则执行增量.这是因为您只保留了这么多历史记录,因此您无法判断插入和删除是否有序,或者插入后是否删除了太多.
根据集合大小,需要的准确程度以及计数的使用频率,您可以定期调用云函数来计算计数并将其存储在文档中.您可以根据集合的大小动态调整此值,以最大限度地减少延迟.对于非常小的集合经常这样做,对于较大的集合来说,它很少.
如果您有一种机制来确定您已经计算过的文档,那么您也可以在此处应用成本优化(因此您只需要计算新的文档).如果删除很少,您可以添加一个事件来减少删除时的计数器.