Google cloud firestore 用于大型文档和内部阵列的竞争友好型数据库体系结构 上下文

Google cloud firestore 用于大型文档和内部阵列的竞争友好型数据库体系结构 上下文,google-cloud-firestore,angularfire2,Google Cloud Firestore,Angularfire2,我有一个数据库,其中包含使用此模式的文档集合(缩短模式,因为某些数据与我的问题无关): 这些文档很容易达到100或200 kB,这取决于它们所持有的项目和最终文件的数量。同样非常重要的是,它们要以尽可能小的带宽使用率尽可能快地更新 这是在web应用程序上下文中,使用Angular 9和@Angular/fire6.0.0 问题 当最终用户在对象的项数组中编辑一项时,就像编辑一个属性一样,反映在数据库中需要我发送整个对象,因为firestore的更新方法不支持字段路径中的数组索引,只能对数组执行的

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

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

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

问题 当最终用户在对象的
数组中编辑一项时,就像编辑一个属性一样,反映在数据库中需要我发送整个对象,因为firestore的
更新
方法不支持字段路径中的数组索引,只能对数组执行的操作是添加或删除元素

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

第二个问题是,在我的例子中,将所有内容实时放在一个文档中会使协作变得困难,因为其中一些元素可以由多个用户同时编辑,这会产生两个问题:

  • 如果在同一秒钟内进行两次更新,某些写入操作可能会由于文档上的争用过多而失败
  • 更新不是原子性的,因为我们一次发送整个文档,因为它不使用事务来避免更多地使用带宽
我已经试过了 子集合 描述 这是一个非常简单的解决方案:为
项目
最终文件
修改历史
数组创建一个子集合,使它们易于编辑,因为它们现在有自己的ID,所以很容易找到它们来更新它们

为什么不起作用 如果列表中有10个
最终文件
,30个
项目
,以及
修改历史记录中的50个条目,则意味着我需要总共打开4个侦听器才能完全侦听一个元素。考虑到一个用户可以同时打开其中许多元素,监听几十个文档会造成同样糟糕的性能状况,在完整的用户情况下可能更糟

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

云函数来更新文档
const{
applyPatch
}=require('fast-json-patch');
函数应用偏移(数据、条目){
entries.forEach(customEntry=>{
const explodedPath=customEntry.path.split('/');
explodedPath.shift();
让指针=数据;
for(让explodedPath.slice的片段(0,-1)){
指针=指针[片段];
}
指针[explodedPath[explodedPath.length-1]+=customEntry.offset;
});
返回数据;
}
exports.updateList=functions.runWith(runtimeOpts).https.onCall((数据,上下文)=>{
const listRef=firestore.collection('lists').doc(data.uid);
返回firestore.runTransaction(事务=>{
return transaction.get(listRef).then(listDoc=>{
const list=listDoc.data();
试一试{
const[standard,custom]=JSON.parse(data.diff).reduce((acc,entry)=>{
if(entry.custom){
acc[1]。推送(进入);
}否则{
acc[0]。推送(进入);
}
返回acc;
}, [
[],
[]
]);
applyPatch(列表、标准);
应用偏移(列表、自定义);
事务.set(listRef,list);
}捕获(e){
console.log(data.diff);
}
});
});

});除了细节之外,您的差异方法似乎最为合理

您应该将
内联存储,但将
修改历史记录
延迟到子集合中。对于整个根文档,记录
modificationsHistory
的哪些元素已合并(按时间戳即可),以及尚未合并的所有元素,您必须在每个客户端上分别重新应用,并使用上述时间戳进行查询

modificationsHistory
中的每个条目不应描述单个差异,而应尽可能描述一组差异

修改历史记录
集合中的更改批量应用到
项目
,通过GCF延迟。您可以任意延迟此操作,并且您可能希望排除仅在最后几秒钟内执行的修改,以说明Firestore中未建立的一致性。那样的话,就没有争论的风险了

必须进一步推迟从
modificationsHistory
集合中进行清理,直到您可以确保没有客户端仍然可以访问根文档的旧版本。特别是如果您认为在侦听器被触发时,客户机不需要严格地更新根文档。 如果由于最终的一致性约束而导致
modificationsHistory
以意外方式更改,则可能需要在客户端重建修补程序堆栈。例如,如果在补丁集合中有一个总的顺序,那么如果集合突然包含客户端以前未知的“较旧”补丁,则需要从基本映像重新应用补丁堆栈

总而言之,您应该能够避免频繁更新,并将其仅限于插入到to
modificationsHistory
子集合中。带宽要求不超过一次获取整个文档的成本,加上流式传输尚未应用的修补程序集合。预计不会发生争用

您可以调整客户端可以忽略多长时间
{
    title: string;
    order: number;
    ...
    ...
    ...
    modificationsHistory: HistoryEntry[];
    items: ListRow[];
    finalItems: ListRow[];
    ...
    ...
    ...
}