在实时数据库上多次调用set时,Firebase函数超时

时间:2019-10-23 11:45:40

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

简而言之:我有一个计划的firebase函数,该函数下载一个文件,将其转换为JSON块,然后将其导入实时数据库。我已经给函数提供了2GB的内存和540秒(最多9分钟)的功能来完成其工作,但它仍会在大约50%的超时时间内结束。一定有泄漏,但这似乎是我的盲点。

详细信息: 我有以下预定功能,每小时运行一次。该函数调用方法updateDatabase,该方法依次从外部服务器下载一个大小约为35MB的文件(使用方法getNewData。此文件是一个由空格分隔(不要让您)的文件,其中包含2500行和2500列数据。我需要数据库中的每个数据点,以便稍后快速读取。

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

因此,下载文件时,我首先将其分成几行。然后,我遍历行,将其拆分为列,并在引用/grid/[currentrow]上的实时数据库上进行设置。这意味着我每次都呼叫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'

1 个答案:

答案 0 :(得分:0)

正如您在对原始问题的评论中看到的那样,Frank van Puffelen提出了这种用例不仅仅是功能环境可以处理的。我做了很多分析,只能得出结论确实如此。因此,我尝试了以下操作:

  1. 我没有尝试每小时每小时更新2500行,每行2500个项目(因此2500个对象集为2500个项目),我每12分钟将其分成500行。这有所帮助,因为该函数现在可以每小时更新整个数据库,尽管某些行会有所延迟(在我的情况下很好);没有超时。不幸的是,每个运行大约需要7分钟才能完成。这相当长地占用了CPU秒数。我们在这里不是在谈论巨额资金,但每月总计40至50美元。所以我想尝试更快地解决问题。
  2. 我意识到很多字段(结果约为35%)包含值-1.00,这基本上意味着“无数据”。 (顺便说一下,每个数据项都是带有两个小数的正数)。我认为没有将这些添加到数据库中,而是可以在稍后再请求数据时解决此问题。如果该值在数据库中不存在,则意味着我们以-1.00开头。因此,对于每一行(或一行,如果有的话),我将创建一个仅包含具有正值的字段的对象。然后,我在所有行上都运行了一次,因此删除了空白字段。而且低落,然后看,事情开始快速运行,我的意思是非常快。现在我们只需不到10秒即可完成操作,而不是每50行7分钟。

是的,您的数据大小对性能有重要影响,再次感谢@frankvanpuffelen指出了正确的方向!