批量并发写入Firestore

Write data to Firestore Concurrently in Bulk

我有一个用例,我需要生成一个预先计算的文档并将其保存到 firestore 集合中。

方法是创建一个tasks集合,每个文档tasks将用于触发云函数中的onCreate事件。

每个 onCreate 事件需要大约 40 秒才能完成,然后写入 samples 集合。

需要写入samples集合的任务数为244 * 26 = 6344个文档。

以下是云函数触发的步骤。

步骤 1:在 tasks 中创建 244 个文档(运行 每 1 小时)

第 2 步onCreate 将监听事件 -> 生成文档,每个文档大约需要 40 秒。这意味着我们有 244 个函数并发 运行 将 26 个文档文档写入 samples 集合。

我用来写数据的函数

export const generateData = async () => {
  const promises = []
  for (const sample of samples) {
    // some logics
    promises.push(sampleRef.set(sampleData))
  }
  await Promise.all(promises)
  return
}

这是我得到的错误:

Error: 4 DEADLINE_EXCEEDED: Deadline exceeded
    at Object.callErrorFromStatus (/workspace/node_modules/@grpc/grpc-js/build/src/call.js:31:26)
    at Object.onReceiveStatus (/workspace/node_modules/@grpc/grpc-js/build/src/client.js:179:52)
    at Object.onReceiveStatus (/workspace/node_modules/@grpc/grpc-js/build/src/client-interceptors.js:336:141)
    at Object.onReceiveStatus (/workspace/node_modules/@grpc/grpc-js/build/src/client-interceptors.js:299:181)
    at /workspace/node_modules/@grpc/grpc-js/build/src/call-stream.js:145:78
    at processTicksAndRejections (internal/process/task_queues.js:77:11) 

对发生的事情有什么想法或有其他方法吗?谢谢,

这个错误可能有很多原因:

  1. 如错误提示,可能与@grpc/grpc-js有关。许多 @google-具有@grpc/grpc-js 依赖项的云库有一个开放的 与并发相关的问题。我读过的唯一有帮助的东西 人们似乎是:

       Upgrading your Node version to v13( assuming you are using node as the runtime). 
    

    有关详细信息,请参阅 this

  2. 这可能是同时分派多个任务的问题。 临时解决方案是:顺序提交任务。逐个。 可能的解决方法:

  • 设置标志回退:创建任务时为真已解决 一些人的问题。

     const {CloudTasksClient} = require('@google-cloud/tasks');
     const client = new CloudTasksClient({ fallback: true })
    

    将 fallback 设置为 true 会启用不同的传输(之前的传输) 最初假设用于浏览器) - 而不是使用 gRPC,它 序列化您的请求并通过常规 HTTP/1 连接发送它们 使用节点获取到不同的端点。当您启用回退时,您 根本不执行任何 gRPC 请求 - 它使用完全不同的堆栈。 如果您同时安排许多任务并且执行 Promise.all有大量任务,可以运行争上 资源,这可能会导致 DEADLINE_EXCEEDED.

    等问题
  • 如果你有很多任务要排队,我采用的方法如下:

      const tasksToCreate = [...]; // an array with a large number of tasks to enqueue.
      const WORK_SIZE = 32; // some size of work to perform.
      while (work.length > 0) {
      const work = tasksToCreate.slice(0, WORK_SIZE).map(() => {
      return createTaskPromise();
      });
      await Promise.all(work);
    
      }
    
  1. 请记住 Firestore 有限制,可能“已超过最后期限” 也可能因为它的局限性而发生。 Maximum write rate to a document is 1 per second. See this。执行太多写入操作太快的人可能会遇到这个问题。现在,当您点击此按钮时,可能会重试写入并可能会降低执行写入的速度可能会有所帮助。

    查看here导致此错误的所有其他可能原因

解法:

在 Firestore 上执行大量写入操作的常用方法有 3 种。

  • 按顺序执行每个单独的写入操作。
  • 使用批量写入操作。
  • 并行执行单独的写入操作。

在 Firestore 上执行批量数据写入的最快、最有效的方法是执行并行的单独写入操作。对于批量数据输入,使用 server client library 和并行化的单独写入。您应该使用服务器客户端库进行批量数据操作,而不是 mobile/web SDK。

批量写入的性能优于序列化写入,但不优于并行写入。批量写入通过调用 batch() 创建一个 BatchedWrite 对象,直到它的最大容量为 500 个文档,然后将其写入 Firestore。该解决方案计算对批次进行的每个操作,并在达到限制后创建一个新批次并将其推送到 batchArray。 完成所有更新后,代码循环遍历 batchArray 并提交数组内的每个批次。 重要的是要计算对批处理进行的每个操作 set()、update()、delete(),因为它们都计入 500 个操作限制。

看看这个Whosebug线程对三个写操作的详细分析。