2012-05-11 24 views
17

Tengo una base de datos de usuario en mongodb que me gustaría exportar a través de una interfaz REST en JSON. El problema es que, en el peor de los casos, la cantidad de filas devueltas supera ampliamente los 2 millones.¿Cómo devolver una gran cantidad de filas de mongodb utilizando el servidor http nodeNo.js?

Primero probé este

var mongo = require('mongodb'), 
    Server = mongo.Server, 
    Db = mongo.Db; 
var server = new Server('localhost', 27017, {auto_reconnect: true}); 
var db = new Db('tracking', server); 
var http = require('http'); 

http.createServer(function (request, response) { 
    db.collection('users', function(err, collection) { 
    collection.find({}, function(err, cursor){ 
     cursor.toArray(function(err, items) { 
     output = '{"users" : ' + JSON.stringify(items) + '}'; 

     response.setHeader("Content-Type", "application/json"); 
     response.end(output); 
     }); 
    }); 
    }); 
}).listen(8008); 
console.log('Server running at localhost:8008'); 

el que falla cuando se ejecuta fuera de la memoria. El ejemplo usa el controlador node-mongodb-native y el paquete http básico.

FATAL ERROR: CALL_AND_RETRY_2 Allocation failed - process out of memory

(Nótese que en escenario real utilizo parámetros que limitan los resultados según sea necesario, pero este ejemplo les consulta todos, que es el peor de los casos, independientemente)

Los datos en sí es simple, como

{ "_id" : ObjectId("4f993d1c5656d3320851aadb"), "userid" : "80ec39f7-37e2-4b13-b442-6bea57472537", "user-agent" : "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322)", "ip" : "127.0.0.1", "lastupdate" : 1335442716 }

también probé algo así como

while(cursor != null) 
{ 
    cursor.nextObject(function(err, item) { 
    response.write(JSON.stringify(item)); 
    }); 
} 

pero que se quedó sin memoria también

¿Cómo debo proceder? Debería haber una manera de transmitir los datos fila por fila, pero no he podido encontrar un ejemplo adecuado para ello. Paginar los datos está fuera de cuestión debido a los requisitos de aplicaciones externas. Pensé en escribir los datos en un archivo y luego publicarlo, pero eso lleva a IO no deseado.

Respuesta

15

El método de la MongoDB controlador nativo cursor.streamRecords() está en desuso, el método stream() es más rápido.

he analizado un documento 40000000 fila de acatalog sin problemas con Mongodb + stream() + process.nextTick()

+1

Encontré que 'cursor.stream()' realiza exactamente lo mismo que 'cursor.each()'. – Meekohi

+0

Asegúrese de especificar un valor para 'batchSize' para miles o millones de filas – alexishacks

+3

Puede pegar el código completo aquí – parkerproject

2

Bueno, ya no uso mongodb driver javascript nativo, pero en mongoose hay bastante buena implementación de streams.

La sintaxis de los dos controladores es bastante similar. Usted puede hacer esto con mangosta:

response.setHeader("Content-Type", "application/json"); 
var stream = collection.find().stream(); 
stream.on('data', function(doc) { 
    response.write(doc); 
}); 
stream.on('close', function() { 
    response.end(); 
}); 
+1

Mongoose sería una mejor manera de abordar el almacenamiento de datos por completo. Su respuesta me condujo a la dirección correcta al usar solo este controlador y descubrí que node-mongodb-native también tiene una opción de transmisión en Cursor llamada 'streamResults'. Voy a publicar una respuesta completa sobre mi problema usando el nodo-mongodb-native más adelante. – Timo

4

Algo así debe trabajo. Si no lo hace probablemente deberías abrir un problema en el mongodb-native bug tracker.

http.createServer(function (request, response) { 
    db.collection('users', function(err, collection) { 
    collection.find({}, function(err, cursor){ 
     response.setHeader("Content-Type", "application/json"); 
     cursor.each(function(err, item) { 
     if (item) { 
      response.write(JSON.stringify(item)); 
     } else { 
      response.end(); 
     } 
     }); 
    }); 
    }); 
}).listen(8008); 

PD: es sólo un esbozo, quiero decir que no recuerdo la sintaxis exacta, pero es each función que está buscando.

+0

En realidad lo intenté también, pero parece que la función 'toArray' en mi pregunta original en realidad envuelve/usa esa función' each', por lo que falló cuando el script se quedó sin memoria también. – Timo

+0

Sí, toArray necesita almacenar en búfer toda la matriz, por lo que no será de ayuda, pero cursor.each funcionará. Solo necesitas rodearlo con corchetes. – danmactough

+0

Ahora que probé esto de nuevo también funciona. Por alguna razón falló antes y tengo que retroceder y verificar lo que hice mal. – Timo

8

Descubrí que el objeto Cursor nativo node-mongodb tiene una opción de transmisión (utilizada con collection.find().streamRecords()) para los registros también, incluso si no se menciona en el github page of the driver. Vea el Cursor source code y busque "streamRecords".

Al final del código terminó así:

db.collection('users', function(err, collection) { 
    var first = true; 

    response.setHeader("Content-Type", "application/json"); 
    response.write('{"users" : ['); 

    var stream = collection.find().streamRecords(); 

    stream.on('data', function(item) { 
    var prefix = first ? '' : ', '; 
    response.write(prefix + JSON.stringify(item)); 
    first = false; 
    }); 
    stream.on('end', function() { 
    response.write(']}'); 
    response.end(); 
    }); 
}); 
+0

¡Gracias a Timo por compartir tu solución! – asuciu

1

Un pequeño módulo para hacer que el uso de la clase de nodo stream.Transform:

var stream = require('stream'); 

function createCursorStream(){ 

    var cursorStream = new stream.Transform({objectMode:true}); 

    cursorStream._transform = function(chunk,encoding,done){ 
     if(cursorStream.started){ 
      cursorStream.push(', ' + JSON.stringify(chunk)); 
     }else{ 
      cursorStream.push('[' + JSON.stringify(chunk)); 
      cursorStream.started = true; 
     } 
     done(); 
    }; 

    cursorStream._flush = function(done){ 
     cursorStream.push(']'); 
     done(); 
    }; 

    return cursorStream; 
} 

module.exports.streamCursorToResponse = function(cursor,response){ 
    cursor.stream().pipe(createCursorStream()).pipe(response); 
}; 

Usted puede alterar JSON.Stringify piezas que hacer cualquier otro tipo de "sobre la marcha" se transforma en los objetos que provienen del cursor mongodb, y guarda algo de memoria.

Cuestiones relacionadas