【问题标题】:Firebase Function timeout when calling set many times on realtime db在实时数据库上多次调用 set 时 Firebase 函数超时
【发布时间】:2020-02-19 15:49:18
【问题描述】:

简而言之:我有一个预定的 firebase 函数,它可以下载文件,将其转换为 JSON 块并将其导入实时数据库。我给了函数 2GB 内存和 540 秒(最长 9 分钟)来完成它的工作,但它仍然在大约 50% 的时间以超时结束。肯定有漏,不过好像在我的盲区。

详细说明: 我有以下预定功能,每小时运行一次。该函数调用方法updateDatabase,然后从外部服务器下载一个大小约为35MB的文件(使用方法getNewData。这个文件是一个空格分隔的(别开玩笑)文件,包含2500行和2500行数据列。我需要数据库中的每个数据点以便稍后快速读取它们。

起初我只是简单地将其转换为 json 并尝试将其导入数据库,因为 35MB 似乎没什么大不了的。然而,这使得函数每次都耗尽内存。所以我决定把它分成更小的部分。

所以当文件被下载时,我首先将它分成几行。然后我遍历行,将其拆分为列,并在参考/grid/[currentrow] 上的实时数据库上进行设置。这意味着我每次调用 set 2500 次。我尝试对每一行使用await 并使用Promise.all()(当前版本,但有时两者似乎都在某个时候挂起。我在日志中没有任何错误,只是在 540 时超时秒过去了。

预定功能:

exports.scheduledDataUpdate = functions
  .region("europe-west1")
  .runWith({
    timeoutSeconds: 540,
    memory: '2GB'
  })
  .pubsub.schedule("0 * * * *")
  .onRun(async () => {
    try {
      await updateDatabase();
      console.log('Database updated');
      return true;
    } catch(error) {
      console.error(error);
      return false;
    }
  });

调用方法updateDatabase:

async function updateDatabase() {
  let data;
  try {
    data = await getNewData().catch(err => console.error(err));
  } catch(error) {
    console.error(error);
    return null;
  }
  console.log('Data download complete');

  const lines = data.split("\n"); // split the data into rows (2500)

  lines.forEach((line, r) => {
    const cols = line.split(/\s+/); // split the row into columns (2500)
    dbUpdates.push(admin.database().ref(`/grid/${r}`).set(cols).then(() => {
      if(r > 1 && r % 500 === 0) {
        console.log(`updated row ${r}`); // just to get some info in logs whether some rows were processed
      }
      return true;
    }).catch((error) => {
      console.error(`Error updating row ${r} -- ${error}`);
    }));
    return true;
  });

  return await Promise.all(dbUpdates);

如果你好奇,方法getNewData

const dataUrl = 'https://some.server/somefile'; // I guess you guess this is different in my code (it is)
async function getNewData() {
  console.log('Start download of data');
  return await new Promise((resolve, reject) => {
    https.get(dataUrl, response => {
      if (response.statusCode === 200) {
        let data = "";
        return response
          .on("data", chunk => {
            data += chunk;
          })
          .on("end", () => {
            resolve(data);
          })
          .on("error", error => {
            reject(error);
            console.error(`error while downloading data: ${data}`);
          });
      } else {
        switch (response.statusCode) {
          case 301:
          case 302:
          case 303:
          case 307:
            console.warning(`Redirected to ${response.headers.location} [${response.statusCode}]`)
            return getNewData(response.headers.location);
          default:
            return reject(new Error(`Could not download new data (error ${response.statusCode})`));
        }
      }
    })
  });
}

当我查看 firebase 提供的配额时(我在 Blaze 上运行,即用即付订阅),这真的不应该是一个太大的问题。显然我错过了什么或犯了一些愚蠢的错误,但对于我的生活,我看不到它。

更新 1 超时日志示例(@FrankvanPuffelen 提问)

7:15:03.224 p.m.     scheduledDataUpdate      Function execution started
7:15:03.546 p.m.     scheduledDataUpdate      Start download of data
7:15:08.035 p.m.     scheduledDataUpdate      Data download complete
7:22:28.390 p.m.     scheduledDataUpdate      updated row 500
7:24:03.244 p.m.     scheduledDataUpdate      Function execution took 540021 ms, finished with status: 'timeout'

【问题讨论】:

  • 您能否编辑问题以包括其中一个超时调用的日志输出?您可能还想添加一些额外的日志记录,以查看在这种情况下它通过代码的路径。
  • 感谢@FrankvanPuffelen 的建议,我已经更新了问题。我尝试添加更多日志记录,但在日志中看不到它。例如,我尝试在所有sets 之间等待,它只是在某个时候停止。没有错误,只是超时。此外,并不是说“更新的第 500 行”是在下载完成 7 分钟后出现的。
  • 如果它在 7 分钟后出现,我立即怀疑你在某处遗漏了一个承诺,它只是在函数超时时发生。但我看不到它在哪里,所以其他人可以通过您添加的附加信息找到它。
  • 我也是这么想的,我一直在乱码代码尝试不同的东西,但似乎我总是在某个地方犯同样的错误。无论如何,感谢您调查它@FrankvanPuffelen
  • 我已启用数据库日志记录以查看会发生什么。我只能看到set 有时需要很长时间。有时需要一秒钟以上的时间。所以这样做 2500 次会超时。应该不会花那么长时间吧?这很奇怪,因为在回来时,这不是问题,整个运行将在 2 分钟内完成。我可以尝试将其拆分,但我不喜欢每次都重新下载,而且我无法将其存储在本地(或者我可以吗?)。

标签: node.js firebase firebase-realtime-database google-cloud-functions


【解决方案1】:

正如您在 cmets 中看到的原始问题 Frank van Puffelen 建议的那样,这个用例不仅仅是函数环境可以处理的范围。我做了很多分析,我只能得出结论,情况确实如此。所以我尝试了以下方法:

  1. 我没有尝试每小时更新 2500 行,每 2500 个项目(因此 2500 个集合,对象包含 2500 个项目),而是每 12 分钟将其拆分为 500 行的块。这在一定程度上有所帮助,因为现在该函数能够每小时更新整个数据库,尽管某些行有一些延迟(在我的情况下这很好);没有超时。不幸的是,每次运行都需要大约 7 分钟才能完成。这加起来相当多的 CPU 秒数。我们在这里谈的不是巨额资金,但加起来可以说是每月 40-50 美元。所以我想尝试更快地完成任务。
  2. 我意识到相当多的字段(结果约为 35%)包含值 -1.00,这基本上意味着“无数据”。 (顺便说一下,每个数据项都是带两位小数的正数)。我想我可以在稍后请求数据的最后解决这个问题,而不是将这些添加到数据库中。如果数据库中不存在该值,则意味着我们从 -1.00 值开始。所以对于每一行(或者如果你愿意的话),我创建一个对象,其中只包含那些具有正值的字段。然后我在所有行上运行了一次,所以空字段被删除了。低,看,在那之后,事情开始快速发展,我的意思是非常快。现在我们在不到 10 秒的时间内完成,而不是每 50 行需要 7 分钟。

是的,数据的大小对性能有很大影响,再次感谢 @frankvanpuffelen 提供正确方向的提示!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-06-05
    • 2020-08-04
    • 1970-01-01
    • 2020-03-31
    • 2020-12-02
    • 2018-04-10
    • 2020-06-28
    • 1970-01-01
    相关资源
    最近更新 更多