gpt4 book ai didi

google-cloud-firestore - 用于大型文档和内部数组的争用友好型数据库架构

转载 作者:行者123 更新时间:2023-12-03 19:12:23 25 4
gpt4 key购买 nike

语境

我有一个包含使用此架构的文档集合的数据库(缩短架构,因为某些数据与我的问题无关):

{
title: string;
order: number;
...
...
...
modificationsHistory: HistoryEntry[];
items: ListRow[];
finalItems: ListRow[];
...
...
...
}

这些文档很容易达到 100 或 200 kB,具体取决于它们持有的项目和最终项目的数量。以尽可能少的带宽使用尽可能快地更新它们也非常重要。

这是在 Web 应用程序上下文中,使用 Angular 9 和 @angular/fire 6.0.0。

问题

当最终用户编辑对象的 item 中的一项时数组,就像只编辑一个属性,反射(reflect)在数据库里面需要我发送整个对象,因为firestore的 update方法不支持字段路径内的数组索引,唯一可以对数组进行的操作是添加或删除元素 as described inside documentation .

但是,更新 items 的一个元素通过发送整个文档来创建数组会给没有良好连接的任何人带来糟糕的性能,我的许多用户都是这种情况。

第二个问题是,在我的情况下,将所有内容实时保存在一个文档中会使协作变得困难,因为其中一些元素可以由多个用户同时编辑,这会产生两个问题:
  • 如果在同一秒内进行两次更新,则某些写入操作可能会因文档争用过多而失败。
  • 更新不是原子性的,因为我们一次发送整个文档,因为它不使用事务来避免更多地使用带宽。

  • 我已经尝试过的解决方案

    子集

    描述

    这是一个非常简单的解决方案:为 items 创建一个子集合, finalItemsmodificationsHistory数组,使它们易于编辑,因为它们现在拥有自己的 ID,因此很容易联系到它们来更新它们。

    为什么它不起作用

    有一个包含 10 finalItems 的列表, 30 items和 50 个条目 modificationsHistory意味着我需要总共打开 4 个监听器才能完全监听一个元素。考虑到用户可以同时打开许多这样的元素,有几十个文档被收听会产生同样糟糕的性能情况,在完整的用户案例中可能更糟。

    这也意味着,如果我想用 100 个项目更新一个大元素,并且我想更新其中的一半,那么每个项目将花费我一次写入操作,更不用说检查权限所需的读取操作量等,每次写入可能 3 次,所以 150 次读取 + 50 次写入只是为了更新数组中的 50 个项目。

    用于更新文档的云功能

    const {
    applyPatch
    } = require('fast-json-patch');

    function applyOffsets(data, entries) {
    entries.forEach(customEntry => {
    const explodedPath = customEntry.path.split('/');
    explodedPath.shift();
    let pointer = data;
    for (let fragment of explodedPath.slice(0, -1)) {
    pointer = pointer[fragment];
    }
    pointer[explodedPath[explodedPath.length - 1]] += customEntry.offset;
    });
    return data;
    }

    exports.updateList = functions.runWith(runtimeOpts).https.onCall((data, context) => {
    const listRef = firestore.collection('lists').doc(data.uid);
    return firestore.runTransaction(transaction => {
    return transaction.get(listRef).then(listDoc => {
    const list = listDoc.data();
    try {
    const [standard, custom] = JSON.parse(data.diff).reduce((acc, entry) => {
    if (entry.custom) {
    acc[1].push(entry);
    } else {
    acc[0].push(entry);
    }
    return acc;
    }, [
    [],
    []
    ]);
    applyPatch(list, standard);
    applyOffsets(list, custom);
    transaction.set(listRef, list);
    } catch (e) {
    console.log(data.diff);
    }
    });
    });
    });


    描述

    使用 diff 库,我在以前的文档和新更新的文档之间进行了比较,并将此差异发送到使用事务 API 操作更新的 GCF。

    这种方法的好处是,由于事务发生在 GCF 内部,因此速度非常快并且不会消耗太多带宽,而且更新只需要发送一个差异,而不是整个文档。

    为什么它不起作用

    实际上,云功能真的很慢,有些更新需要 2 秒以上才能完成,它们也可能由于争用而失败,而 Firestore 连接器不知道它,因此在这种情况下无法确保数据完整性。

    如果我找到其他要尝试的东西,我将相应地进行编辑以添加更多解决方案



    我觉得我错过了一些东西,比如如果 firestore 有一些我根本不知道的东西可以解决我的用例,但我无法弄清楚它是什么,也许我以前测试过的解决方案实现得很糟糕,或者我错过了重要的事情。我错过了什么?甚至有可能实现我想做的事情吗?我对数据重构、查询更改等任何事情都持开放态度,因为它主要用于学习目的。

    最佳答案

    通过使用 Maps 而不是 Arrays 来存储数据,您应该能够减少更新文档所需的带宽。这将允许您仅发送使用其 key 更新的项目。
    我不知道这对你来说改变会有多大影响,但这听起来比其他选项少。
    您说您的文档单个达到200kb并非不可能。最好记住 Firestore 将文档大小限制为 1mb。如果您计划除此之外的支持文档,您将需要找到一种方法来分割数据。
    关于您的争用问题... 您可能会考虑使用“锁定”文档并防止它在其他用户尝试保存时接收更新的系统。您可以使用使用 websockets 或 Firebase FCM 构建的简单消息系统来执行此操作。客户端将订阅文档的 channel ,并在他们尝试更新时发布。其他客户端随后会收到文档正在更新的通知,并且必须等待才能保存自己的更改。
    另外,我不知道 modifyHistory 的内容是什么样的,但在我看来,这听起来像是您可能会保留在子集合中的数据类型。
    在您尝试过的解决方案中,子集合对我来说似乎是最具可扩展性的。您可以研究不使用 onSnapshot 监听器的可能性,而是创建自己的事件系统来通知客户端更改。我想它可以像我上面提到的“锁定”系统一样工作。客户端在更新属于文档的项目时发送事件。订阅该文档 channel 的其他客户端将知道检查数据库以获取最新版本。

    关于google-cloud-firestore - 用于大型文档和内部数组的争用友好型数据库架构,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61660933/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com