【问题标题】:"JavaScript heap out of memory" while streaming large file流式传输大文件时出现“JavaScript 堆内存不足”
【发布时间】:2018-01-13 14:43:17
【问题描述】:

我正在我的服务器上尝试 XML -> JSON -> MongoDB。我有一个 NodeJS 应用程序,它流式传输 XML,将其转换为 JSON,然后以 1000 块为单位将其添加到 MongoDB 服务器。然而,在大约 75000 条记录之后,我的 Macbook 的风扇开始转得更快,处理过程变得非常缓慢。几分钟后,我收到此错误:

[30517:0x102801600] 698057 毫秒:标记扫描 1408.2 (1702.9) -> 1408.1 (1667.4) MB,800.3 / 0.0 毫秒(+ 0.0 毫秒,自标记开始以来的 0 步,最大步长 0.0 毫秒,自开始以来的墙时间标记 803 毫秒)最后的手段 [30517:0x102801600] 698940 ms:Mark-sweep 1408.1 (1667.4) -> 1408.1 (1667.4) MB,882.2 / 0.0 ms 最后一招

最后在 JS 堆栈跟踪中:

致命错误:CALL_AND_RETRY_LAST 分配失败 - JavaScript 堆内存不足

我感觉我的内存快用完了,但是当文件超过 70 GB 并且我只有 16 GB 的 RAM 时,使用 --max-old-space-size(或其他)增加允许的内存不起作用。

这是我正在尝试做的代码:

var fs = require('fs'),
    path = require('path'),
    XmlStream = require('xml-stream'),
    MongoClient = require('mongodb').MongoClient,
    url = 'mongodb://username:password@my.server:27017/mydatabase',
    amount = 0;

MongoClient.connect(url, function(err, db) {

    var stream = fs.createReadStream(path.join(__dirname, 'motor.xml'));
    var xml = new XmlStream(stream);

    var docs = [];
    xml.collect('ns:Statistik');

    // This is your event for the element matches
    xml.on('endElement: ns:Statistik', function(item) {
        docs.push(item);           // collect to array for insertMany
        amount++;

        if ( amount % 1000 === 0 ) { 
          xml.pause();             // pause the stream events
          db.collection('vehicles').insertMany(docs, function(err, result) {
            if (err) throw err;
            docs = [];             // clear the array
            xml.resume();          // resume the stream events
          });
        }
    });

    // End stream handler - insert remaining and close connection
    xml.on("end",function() {
      if ( amount % 1000 !== 0 ) {
        db.collection('vehicles').insertMany(docs, function(err, result) {
          if (err) throw err;
          db.close();
        });
      } else {
        db.close();
      }
    });

});

我的问题类似于:我有内存泄漏吗?为什么 Node 允许代码像那样构建内存?除了为我的 PC 购买 70+ GB 的 RAM 之外,还有其他解决方法吗?

【问题讨论】:

  • 很有可能1000 的批处理大小实际上太大而无法将内容存储在数组中,但不太可能,因为 BSON 限制为 16MB,如果提供数组内容,insertMany() 将简单地失败超过了那个尺寸。如果有任何“泄漏”,则必须在 XML 流本身中。
  • 我要告诉你的是,由于 BSON 限制,它可能不会有任何区别。 “流”意味着只处理匹配的每个项目。这实际上会在每个异步调用上“暂停”和“恢复”,因此应该只有活动的批处理集合,没有其他正在处理的项目。但是,如果 XML 流中存在错误,则它不会从先前解析的项目中释放内存。与数据库或操作无关。
  • 我认为这是您对.collect 的使用我认为它只需要收集子项目并且您可能不需要调用它,因为我可以看到您已经在处理的名称反正元素。很难说没有看到你的结构。即使您正在流式传输它们,collect 也会强制将它们保存在内存中。
  • 刚刚浏览了 xml-stream 文档和示例,collect 的使用在这里似乎不正确。看起来您的实际事件处理应该是 xml.on("updateElement: ns:Statistik", ... 或者可能只是 endElement 而根本不使用 collect。它的预期目的是“收集子节点”,而您想要的是收集特定节点匹配的所有数据。见github.com/assistunion/xml-stream/blob/master/examples/…。我的猜测是“收集”是“坚持”你真正想丢弃的物品。
  • 感谢@Strelok。我只是查看了文档并得出了结论,当我在这里查看时,他们刚刚发布了相同的结论。我只是添加指出使用示例。

标签: javascript node.js mongodb memory


【解决方案1】:

发布我的评论作为答案,因为它解决了问题,并且可能对其他难以以这种方式使用 xml-stream 包的人有用。

有问题的是,collect 方法会导致问题,因为它强制解析器在解析时将所有已处理节点的实例收集到一个数组中。 collect 只能用于从每个正在解析的节点中收集特定类型的子项。默认行为是不这样做(由于解析器的流式特性,它可以让您轻松处理数 GB 的文件)。

所以解决方案是删除那行代码,只使用endElement 事件。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-04
    • 1970-01-01
    • 2021-04-23
    • 2017-11-16
    相关资源
    最近更新 更多