【问题标题】:Memory allocation failed - JavaScript heap out of memory内存分配失败 - JavaScript 堆内存不足
【发布时间】:2020-06-25 01:43:56
【问题描述】:

我的 Node.js 代码存在内存泄漏问题。我正在尝试流式读取具有 100k 行的 CSV(链接中的示例文件)文件并处理文件中的每个条目。该过程在一段时间后出现内存分配错误。

“致命错误:接近堆限制的无效标记压缩分配失败 - JavaScript 堆内存不足”

sample csv:

我的代码示例

const fs = require('fs');
const config = require('../config/config');
const csv = require('csv-parser');
const tls = require('../services/tls');

processCSV('crawler', 'sample-csv.csv');

包含 100k 条目的流式处理 csv 文件

async function processCSV (jobName, fileName) {
  return new Promise((resolve, reject) => {
    let filePath = config.api.basePath + fileName;
    fs.createReadStream(filePath)
        .on('error', () => {
          // handle error
          console.log('error processing csv');
          reject();

        })
        .pipe(csv())
        .on('data', (row) => {
          createJob(jobName, row);
        })
        .on('end', () => {
          // handle end of CSV
          console.log('Finished processing csv');
          resolve(filePath);
        })
  });
}

验证 csv 文件中的每个 url

async function createJob (name, data) {
  let {hostname, port, ip} = data;
  let protocol = 'https';
  if (port === 80) {
    protocol = 'http';
  }
  let url = protocol + '://' + hostname;
  try {
    await tls.getHostData(url); // call an external api to get details of hostname
    return url;
  } catch (error) {
    return error;
  }
}

我不知道哪个函数导致了内存泄漏。

【问题讨论】:

  • 世界其他地区不使用“lakh”和“crore”等印度词。请不要在这里使用它们。

标签: node.js csv memory-leaks stream


【解决方案1】:

在我看来,您正在为 CSV 文件中的每一行调用 createJob(),并且您可能会导致这些作业中的每一个都同时在进程中和内存中。这可能会耗尽系统资源,尤其是在文件中有很多行的情况下。

解决这个问题的一个想法是调整代码,使只有 N 个createJob() 操作同时“进行中”。这是一种方法,当您同时达到最大请求数时暂停流,然后在有更多空间时恢复它:

async function processCSV (jobName, fileName) {
  return new Promise((resolve, reject) => {
    let filePath = config.api.basePath + fileName;
    let numConcurrent = 0;
    let paused = false;
    const maxConcurrent = 10;
    let stream = fs.createReadStream(filePath)
        .on('error', (err) => {
          // handle error
          console.log('error processing csv');
          reject(err);

        })
        .pipe(csv())
        .on('data', (row) => {

          function checkResume() {
              --numConcurrent;
              if (paused && numConcurrent < maxConcurrent) {
                  // restart the stream, there's room for more
                  paused = false;
                  stream.resume();
              }
          }
          ++numConcurrent;
          createJob(jobName, row).then(checkResume, checkResume);
          if (numConcurrent >= maxConcurrent) {
              // pause the stream because we have max number of operations going
              stream.pause();
              paused = true;
          }
        })
        .on('end', () => {
          // handle end of CSV
          console.log('Finished processing csv');
          resolve(filePath);
        })
  });
}


async function createJob (name, data) {
  let {hostname, port, ip} = data;
  let protocol = 'https';
  if (port === 80) {
    protocol = 'http';
  }
  let url = protocol + '://' + hostname;
  try {
    await tls.getHostData(url); // call an external api to get details of hostname
    return url;
  } catch (error) {
    // make sure returned promise is rejected
    throw error;
  }
}

注意:如果在处理给定行时出现错误,此实现(就像您在问题中显示的那样)会继续运行。这种行为可以根据需要改变。

【讨论】:

  • 我尝试了您的建议,但程序在处理 csv 中的 10 行后退出。请检查一下。
  • 我需要在任何地方将变量“paused = false”的状态更改为“true”吗?
  • 它对我有用。需要运行完整的 csv。谢谢
  • 还有一个疑问,我能给出的最大并发值是多少?为“maxConcurrent”赋予动态值的任何方式。 @jfriend00
  • @Dibish - 您必须使用不同的 maxConcurrent 值运行一些实验,并测量运行到完成的总时间和峰值内存使用量。这在很大程度上取决于目标主机以及它实际上可以处理多少并发请求。您希望将maxConcurrent 设置得尽可能低(这会降低峰值内存使用量),这不会使总运行时间更长。我选择默认值 10 作为目标主机可以有效地处理一些并行请求的猜测,但一次可能不超过 10 个。你可以自己调。
猜你喜欢
  • 2018-12-05
  • 2019-05-05
  • 2018-09-29
  • 1970-01-01
  • 2020-03-20
  • 2016-12-19
  • 1970-01-01
  • 2018-09-25
  • 1970-01-01
相关资源
最近更新 更多