我有一个包含超过200,000行的CSV文件。我需要将它保存到MongoDB。
如果我尝试for循环,Node将耗尽内存。
fs.readFile('data.txt', function(err, data) {
if (err) throw err;
data.split('\n');
for (var i = 0; i < data.length, i += 1) {
var row = data[i].split(',');
var obj = { /* The object to save */ }
var entry = new Entry(obj);
entry.save(function(err) {
if (err) throw err;
}
}
}
我怎样才能避免失去节制?
答案 0 :(得分:9)
欢迎使用流式传输。你真正想要的是一个“偶数流”,它可以处理你的输入“一次一个块”,当然理想情况下是一个常见的分隔符,例如你当前正在使用的“换行符”。
对于非常高效的东西,您可以添加MongoDB "Bulk API"插入的使用,以尽可能快地加载,而不会占用所有机器内存或CPU周期。
不提倡,因为有各种解决方案,但这里有一个列表,利用line-input-stream package使“行终止符”部分简单。
仅由“example”构成的模式定义:
var LineInputStream = require("line-input-stream"),
fs = require("fs"),
async = require("async"),
mongoose = require("mongoose"),
Schema = mongoose.Schema;
var entrySchema = new Schema({},{ strict: false })
var Entry = mongoose.model( "Schema", entrySchema );
var stream = LineInputStream(fs.createReadStream("data.txt",{ flags: "r" }));
stream.setDelimiter("\n");
mongoose.connection.on("open",function(err,conn) {
// lower level method, needs connection
var bulk = Entry.collection.initializeOrderedBulkOp();
var counter = 0;
stream.on("error",function(err) {
console.log(err); // or otherwise deal with it
});
stream.on("line",function(line) {
async.series(
[
function(callback) {
var row = line.split(","); // split the lines on delimiter
var obj = {};
// other manipulation
bulk.insert(obj); // Bulk is okay if you don't need schema
// defaults. Or can just set them.
counter++;
if ( counter % 1000 == 0 ) {
stream.pause();
bulk.execute(function(err,result) {
if (err) callback(err);
// possibly do something with result
bulk = Entry.collection.initializeOrderedBulkOp();
stream.resume();
callback();
});
} else {
callback();
}
}
],
function (err) {
// each iteration is done
}
);
});
stream.on("end",function() {
if ( counter % 1000 != 0 )
bulk.execute(function(err,result) {
if (err) throw err; // or something
// maybe look at result
});
});
});
因此,通常“流”接口“断开输入”以便一次处理“一行”。这会阻止你立刻加载所有内容。
主要部分是MongoDB的"Bulk Operations API"。这允许您在实际发送到服务器之前一次“排队”许多操作。因此,在这种情况下使用“模数”,只能处理每1000个条目的写入。你可以做任何高达16MB BSON限制的事情,但要保持可管理性。
除了批量处理的操作外,async库还有一个额外的“限制器”。它并不是真正需要的,但这确保了在任何时候基本上不超过文档的“模数限制”。除了内存之外,一般批处理“插入”没有IO成本,但“执行”调用意味着IO正在处理。所以我们等待而不是排队等待更多事情。
对于“流处理”CSV类型数据,您可以找到更好的解决方案。但总的来说,这为您提供了如何以内存有效的方式执行此操作的概念,而且不会占用CPU周期。
答案 1 :(得分:7)
接受的答案非常好,并试图涵盖这个问题的所有重要方面。
虽然前两个方面做得很好,但使用async.series()解决同步问题的方法将无法正常工作。
stream.on("line",function(line) {
async.series(
[
function(callback) {
var row = line.split(","); // split the lines on delimiter
var obj = {};
// other manipulation
bulk.insert(obj); // Bulk is okay if you don't need schema
// defaults. Or can just set them.
counter++;
if ( counter % 1000 == 0 ) {
bulk.execute(function(err,result) {
if (err) throw err; // or do something
// possibly do something with result
bulk = Entry.collection.initializeOrderedBulkOp();
callback();
});
} else {
callback();
}
}
],
function (err) {
// each iteration is done
}
);
});
这里的bulk.execute()是一个mongodb写操作,它是一个异步IO调用。这允许node.js在使用db编写和回调完成bulk.execute()之前继续执行事件循环。
因此它可以继续从流中接收更多'line'事件并排队更多文档bulk.insert(obj)
并且可以点击下一个模数以再次触发bulk.execute()。
让我们看看这个例子。
var async = require('async');
var bulk = {
execute: function(callback) {
setTimeout(callback, 1000);
}
};
async.series(
[
function (callback) {
bulk.execute(function() {
console.log('completed bulk.execute');
callback();
});
},
],
function(err) {
}
);
console.log("!!! proceeding to read more from stream");
这是输出
!!! proceeding to read more from stream
completed bulk.execute
为了确保我们在任何给定时间处理一批N个文档,我们需要使用stream.pause()
&amp;对文件流强制执行流控制。 stream.resume()
var LineInputStream = require("line-input-stream"),
fs = require("fs"),
mongoose = require("mongoose"),
Schema = mongoose.Schema;
var entrySchema = new Schema({},{ strict: false });
var Entry = mongoose.model( "Entry", entrySchema );
var stream = LineInputStream(fs.createReadStream("data.txt",{ flags: "r" }));
stream.setDelimiter("\n");
mongoose.connection.on("open",function(err,conn) {
// lower level method, needs connection
var bulk = Entry.collection.initializeOrderedBulkOp();
var counter = 0;
stream.on("error",function(err) {
console.log(err); // or otherwise deal with it
});
stream.on("line",function(line) {
var row = line.split(","); // split the lines on delimiter
var obj = {};
// other manipulation
bulk.insert(obj); // Bulk is okay if you don't need schema
// defaults. Or can just set them.
counter++;
if ( counter % 1000 === 0 ) {
stream.pause(); //lets stop reading from file until we finish writing this batch to db
bulk.execute(function(err,result) {
if (err) throw err; // or do something
// possibly do something with result
bulk = Entry.collection.initializeOrderedBulkOp();
stream.resume(); //continue to read from file
});
}
});
stream.on("end",function() {
if ( counter % 1000 != 0 ) {
bulk.execute(function(err,result) {
if (err) throw err; // or something
// maybe look at result
});
}
});
});