如何使用大型数据集填充mongoose

时间:2013-01-19 19:04:28

标签: node.js mongodb mongoose

我正在尝试使用Node.js(0.8.18)和Mongoose(3.5.4)将商店目录加载到MongoDb(2.2.2) - 所有这些都在Windows 7 64位上。该数据集包含大约12,500条记录。每个数据记录都是JSON字符串。

我最近的尝试是这样的:

var fs = require('fs');
var odir = process.cwd() + '/file_data/output_data/';
var mongoose = require('mongoose');
var Catalog = require('./models').Catalog;

var conn = mongoose.connect('mongodb://127.0.0.1:27017/sc_store');

exports.main = function(callback){
    var catalogArray = fs.readFileSync(odir + 'pc-out.json','utf8').split('\n');
    var i = 0;

    Catalog.remove({}, function(err){
        while(i < catalogArray.length){
            new Catalog(JSON.parse(catalogArray[i])).save(function(err, doc){
                if(err){
                    console.log(err);
                } else {
                    i++;                    
                }
            });
            if(i === catalogArray.length -1) return callback('database populated');
        }
    });
};

我在尝试填充数据库时遇到了很多问题。在以前的场景(和这个场景)中,节点固定处理器并最终耗尽内存。请注意,在这种情况下,我试图允许Mongoose保存记录,然后在记录保存后迭代到下一条记录。

但是Mongoose save函数内部的迭代器永远不会增加。此外,它永远不会抛出任何错误。但是,如果我把迭代器(i)放在异步调用之外的Mongoose,它会工作,只要我尝试加载的记录数量不是太大(我已成功加载2,000这种方式)。

所以我的问题是:为什么Mongoose保存调用中的迭代器不会增加?而且,更重要的是,使用Mongoose将大型数据集加载到MongoDb的最佳方法是什么?

罗布

2 个答案:

答案 0 :(得分:4)

i是您从catalogArray中提取输入数据的索引,但您也尝试使用它来跟踪已保存的数据,这是不可能的。尝试单独跟踪它们:

var i = 0;
var saved = 0;
Catalog.remove({}, function(err){
    while(i < catalogArray.length){
        new Catalog(JSON.parse(catalogArray[i])).save(function(err, doc){
            saved++;
            if(err){
                console.log(err);
            } else {
                if(saved === catalogArray.length) {
                    return callback('database populated');
                }
            }
        });
        i++;
    }
});

<强>更新

如果要为流程添加更严格的流控制,可以使用async模块的forEachLimit功能将未完成的save操作数限制为您指定的任何内容。例如,要一次将其限制为一个未完成的save

Catalog.remove({}, function(err){
    async.forEachLimit(catalogArray, 1, function (catalog, cb) {
        new Catalog(JSON.parse(catalog)).save(function (err, doc) {
            if (err) {
                console.log(err);
            }
            cb(err);
        });
    }, function (err) {
        callback('database populated');
    });
}

答案 1 :(得分:2)

罗布,

答案简短:

您创建了一个无限循环。你正在考虑同步和阻塞,Javascript函数是异步的,没有阻塞。你想要做的就是试着将饥饿感直接变成三明治。你不能。最接近的是你用饥饿的感觉来激励你去厨房做饭。不要试图使Javascript阻止。它不会起作用。现在,学习async.forEachLimit。它适用于你想要做的事情。

您应该检查异步设计模式,并在更深层次上理解它的含义。回调不仅仅是返回值的替代方法。它们在执行方式和时间方面根本不同。这是一本很好的入门书:http://cs.brown.edu/courses/csci1680/f12/handouts/async.pdf

答案很长:

这里存在一个潜在的问题,那就是你缺乏对非阻塞IO和异步意味着什么的理解。我不确定你是否打算进入节点开发,或者这只是一个一次性的项目,但如果你打算继续使用节点(或任何异步语言),那么理解同步和异步之间的区别是值得的。设计模式,以及它们的动机。所以,这就是为什么你有一个逻辑错误,将循环不变增量放在异步回调中,这会产生一个无限循环。

在非计算机科学中,这意味着你的增量永远不会发生。原因是因为Javascript在调用任何异步回调之前执行单个代码块。所以在你的代码中,你的循环会一遍又一遍地运行,而不会增加。并且,在后台,您将相同的文档一遍又一遍地存储在mongo中。循环的每次迭代开始将索引为0的文档发送到mongo,回调无法在循环结束之前触发,并且循环外的所有其他代码都会运行完成。因此,回调排队。但是,你的循环再次运行,因为i ++永远不会被执行(记住,回调排队直到你的代码完成),再次插入记录0,排队另一个回调以在你的循环完成后执行。这种情况一直持续到你的内存充满了回调,等待通知无限循环文件0被插入数百万次。

一般情况下,如果没有真正糟糕的事情,就无法进行Javascript阻止。例如,对于我在“简短回答”中谈到的三明治来煎炸一些鸡蛋,最重要的事情就是让你的厨房着火。

我的建议是利用像async这样的库。 https://github.com/caolan/async JohnnyHK在这里提到了,他这样做是对的。