Esta mañana, recorriendo documentos de la base de datos con Node y MongoDB he tropezado con este problema. Por poneros un poco en contexto:

Tengo una colección de unos pocos millones de resultados de partidas de Dota 2 guardadas en MongoDB.
El caso es que si quiero ver por ejemplo los equipos (que no son más que combinaciones de los más de 100 héroes de Dota) con más éxito, o con más derrotas, tengo que coger esos millones de partidas y agruparlos por equipo, usando map reduce.
Inviable, tarda del orden de media hora y creciendo.
Así que la idea es que, en adelante, para cada partida nueva que recoja del API además de guardar el resultado (básicamente equipo ganador y equipo perdedor), actualizar la colección de equipos, sumando una victoria al equipo ganador y una derrota al perdedor. De esta forma obtener los 10 equipos con más victorias sería tan rápido y sencillo como consultar esa nueva colección.
El problema: los millones de partidas que ya tengo guardados, que habría que recorrer para hacer ese recuento antes de empezar con el nuevo sistema.

Vale, pues en eso andaba yo, recorriendo todos los documentos de la colección con un pequeño script en Node. Alguno dirá que por qué un script y no Map Reduce. Pues sí, es lo que intenté, pero como la clave en mi caso es un array de héroes, y a Map Reduce no le gustan los arrays como claves he tenido que cambiar de idea. Quizá haya una forma de hacerlo con Map Reduce pero al ver que no era tan inmediato he optado por un script propio.

Bien, he aquí el código del script, al menos la parte clave:

var stream = MatchResult.find().stream();

stream.on('data', function (matchResult) {
    this.pause();
    //do something with this doc
    //...
    this.resume();
});

stream.on('error', function (err) {
    console.log('ON ERROR');
    console.log(err);
});

stream.on('close', function () {
    console.log('ON CLOSE');
});

Tal como recomienda la docu de mongoose uso la API Stream de MongoDB para recorrer todos los documentos de la query. Pero revienta a los ~49k, saltando al ‘on error’ con el error:

MongoError: cursor killed or timed out

Acudo al manual de instrucciones (aka Google) pero no doy con la solución (aka StackOverflow). Vaya. Lo más que veo son respuestas de que el Cursor se cierra si pasan 10m de inactividad, que no es el caso ni de lejos. Así que hago un nuevo intento, poniendo unos cuantos Dates para ver si de casualidad se hubiese quedado atascado en algún lado. Y esta vez me quedo pendiente.

Efectivamente vuelve a fallar, exactamente igual, y no por inactividad. Busco en los logs y veo que en el de MongoDB aparece lo siguiente:

getMore: cursorid not found xxxxxxxxx

Vaya, pues si Mongo no encuentra el cursor, ¿quién se lo está cepillando? Pruebo a buscar este nuevo mensaje, salen bastantes más resultados pero insisten en que el Cursor se cierra si está inactivo. No es mi caso no? En fin, a falta de algo mejor, pruebo a buscar una opción que permita sobrescribir ese valor de 10m o el que sea. Mongoose parece que permite pasarle opciones a Mongo fácilmente:

var stream = MatchResult.find({}, {}, { timeout: false }).stream();

Y efectivamente, ya no peta. ¿Por qué el timeout se estaba Cargando el cursor si no hago más que sacar documentos sin parar? ¿Puede que el driver por dentro saque los documentos de muchos en muchos y por eso se alcance el timeout?

Si alguno tiene respuestas, bienvenidas son 🙂

If you think my content is worth it you can Buy me a Coffee at ko-fi.com Buy me a Ko-fi