Firebase 工具:使用大 JSON 文件设置数据库时如何避免 "out of memory" 错误?如何通过 firebase-tools 流式传输 JSON?

Firebase Tools: How to avoid "out of memory" errors when setting database with a big JSON file? How to stream JSON via firebase-tools?

我们有一个 GitHub 操作,使用 firebase-tools 将数据从一个 Firebase 项目复制到另一个项目(我们使用的是最新版本,9.11.0) package:

firebase use fromProject && firebase database:get / -o export.json
firebase use toProject && firebase database:set -y / export.json

这在我们的数据变大之前一直运行良好,现在我们收到以下错误:

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory

作为临时修复,我们也可以应用节点 --max-old-space-size 标志,这只会增加节点进程可用的内存:

node --max-old-space-size=4096 /home/runner/work/foo/foo/node_modules/firebase database:set -y / export.json

考虑到我们的数据会不断增长,我们想实施一个适当的修复,在我的理解中是通过 streaming 和 JSON 来设置数据。但是,我不确定 firebase-tools 是否允许这样做。搜索 Github 个问题没有找到任何有用的信息。

也许除了流式传输之外,还有另一种有用的方法可以在设置之前将巨大的 JSON 文件分成块?

谢谢!

我们使用 Firebase HTTP 的 HTTP 流 API 来克服保存、本地修改和上传巨大(超过 256mb)JSON 文件的麻烦。

我们构建了一个函数,用于将数据流从一个项目的数据库传输到另一个项目的数据库:

async function copyFirebasePath(path, from, to) {
  // getAccessToken from https://firebase.google.com/docs/database/rest/auth
  const fromAccessToken = await getAccessToken(from.key)
  const toAccessToken = await getAccessToken(to.key)

  return new Promise((resolve, reject) => {
    let toRequest
    // create write request, but don’t start writing to it
    // we’ll pipe the read request as the data to write
    try {
      toRequest = https.request(
        `${to.databaseUrl}/${path}.json?print=silent`,
        {
          method: 'PUT',
          headers: {
            Authorization: `Bearer ${toAccessToken}`
          }
        },
        (/* res */) => {
          resolve()
        }
      )
    } catch (writeError) {
      reject(writeError)
    }

    try {
      https
        .request(
          `${from.databaseUrl}/${path}.json`,
          {
            method: 'GET',
            headers: {
              Authorization: `Bearer ${fromAccessToken}`
            }
          },
          res => {
            res.pipe(toRequest)
            res.on('end', () => {
              toRequest.end()
            })
          }
        )
        .end()
    } catch (readError) {
      reject(readError)
    }
  })
}

我们这样使用它:

// get Object.keys from remote db
const shallowDB = await request({
  method: 'get',
  // note ?shallow=true here – prevents loading the whole db!
  url: `${from.databaseUrl}/.json?shallow=true`,
  options: {
    headers: {
      Authorization: `Bearer ${fromAccessToken}`
    }
  }
})
const dbKeys = Object.keys(shallowDB)
const keysToOmit = ['foo', 'bar', 'baz']

try {
  await Promise.all(
    dbKeys
      .filter(dbKey => !keysToOmit.includes(dbKey))
      .map(key => copyFirebasePath(key, from, to))
  )
} catch (copyError) {
  console.log(copyError)
  throw new Error(copyError)
}