无法使用Node.js将大块数据填充到mongodb

时间:2015-09-03 21:59:54

标签: javascript node.js mongodb

我被要求输入从全市许多地方收集的大量天气数据。每个站点有1台计算机,每个文件夹有一个文件夹,每5分钟就会同步到一个中央服务器。每天都会创建一个新文件。所以,基本上结构是这样的。 一个txt文件的格式为csv文件,第一行为字段,其余为数字。

folder_on_server
            | __ site1 __ date1.txt
            | | __ date2.txt
            |
            | __ site2 __ date1.txt
                     | __ date2.txt
我写了一个小node.js应用程序来将这些数据填充到mongoDB上。但是,目前我们只有3个站点,但每个站点有近900个txt文件,每个文件包含24 * 20 = 288行(每5分钟记录一次数据)。我试图运行节点应用程序,但在阅读了第一个文件夹的大约100个文件后,程序崩溃时出现内存分配失败错误。

我尝试了很多方法来改善这个:

  1. 将nodejs的内存大小增加到8GB =>更好的,更多的文件读入但仍然无法移动到下一个文件夹。
  2. 在_.forEach循环的末尾将一些变量设置为null并且未定义(我使用下划线)=>没有帮助。
  3. 移动文件数组(使用fs.readdir),以便删除第一个元素=>也无济于事。
  4. 有没有办法强制js每次完成读取文件时清理内存? 感谢

    更新1:我最终一次在每个文件夹中添加100个文件。这似乎很乏味但是很有效,这就像一次性工作。但是,我仍然想为此找到解决方案。

1 个答案:

答案 0 :(得分:1)

尝试使用streams而不是将每个文件加载到内存中。

我已经向您发送了pull request,其中包含使用流和异步i / o的实现。

这是大部分内容:

var Async = require('async');
var Csv = require('csv-streamify');
var Es = require('event-stream');
var Fs = require('fs');
var Mapping = require('./folder2siteRef.json');
var MongoClient = require('mongodb').MongoClient;

var sourcePath = '/hnet/incoming/' + new Date().getFullYear();

Async.auto({
  db: function (callback) {
    console.log('opening db connection');
    MongoClient.connect('mongodb://localhost:27017/test3', callback);
  },
  subDirectory: function (callback) {
    // read the list of subfolder, which are sites
    Fs.readdir(sourcePath, callback);
  },
  loadData: ['db', 'subDirectory', function (callback, results) {
    Async.each(results.subDirectory, load(results.db), callback);
  }],
  cleanUp: ['db', 'loadData', function (callback, results) {
    console.log('closing db connection');
    results.db.close(callback);
  }]
}, function (err) {
  console.log(err || 'Done');
});

var load = function (db) {
  return function (directory, callback) {
    var basePath = sourcePath + '/' + directory;
    Async.waterfall([
      function (callback) {
        Fs.readdir(basePath, callback); // array of files in a directory
      },
      function (files, callback) {
        console.log('loading ' + files.length + ' files from ' + directory);
        Async.each(files, function (file, callback) {
          Fs.createReadStream(basePath + '/' + file)
            .pipe(Csv({objectMode: true, columns: true}))
            .pipe(transform(directory))
            .pipe(batch(200))
            .pipe(insert(db).on('end', callback));
        }, callback);
      }
    ], callback);
  };
};

var transform = function (directory) {
  return Es.map(function (data, callback) {
    data.siteRef = Mapping[directory];
    data.epoch = parseInt((data.TheTime - 25569) * 86400) + 6 * 3600;
    callback(null, data);
  });
};

var insert = function (db) {
  return Es.map(
    function (data, callback) {
      if (data.length) {
        var bulk = db.collection('hnet').initializeUnorderedBulkOp();
        data.forEach(function (doc) {
          bulk.insert(doc);
        });
        bulk.execute(callback);
      } else {
        callback();
      }
    }
  );
};

var batch = function (batchSize) {
  batchSize = batchSize || 1000;
  var batch = [];

  return Es.through(
    function write (data) {
      batch.push(data);
      if (batch.length === batchSize) {
        this.emit('data', batch);
        batch = [];
      }
    },
    function end () {
      if (batch.length) {
        this.emit('data', batch);
        batch = [];
      }
      this.emit('end');
    }
  );
};

我已使用流更新了您的tomongo.js脚本。我还将其更改为使用async而不是同步其文件i / o。

我使用小数据集对你的代码中定义的结构进行了测试,结果非常好。我对900xfiles和288xlines进行了3xdirs的有限测试。我不确定你的每一行数据有多大,所以我扔了一些随机属性。它非常快。了解它与您的数据有何关系。如果它导致问题,您可以尝试在执行批量插入操作时使用不同的写入问题限制它。

还可以查看其中一些链接,了解有关node.js中的流的更多信息:

http://nodestreams.com - 一个包含许多流示例的工具written by John Resig

event-stream是一个非常有用的流模块。