node.js mongodb mongodb-query cursor

node.js - MongoDB-Error: error del comando getMore: cursor no encontrado



mongodb-query (4)

EDITAR - Rendimiento de consulta:

Como @NeilLunn señaló en sus comentarios, no debería filtrar los documentos manualmente, sino usar .find(...) para eso:

db.snapshots.find({ roundedDate: { $exists: true }, stream: { $exists: true }, sid: { $exists: false } })

Además, usar .bulkWrite() , disponible a partir de MongoDB 3.2 , será mucho más eficiente que realizar actualizaciones individuales.

Es posible que, con eso, pueda ejecutar su consulta dentro de los 10 minutos de vida útil del cursor. Si aún toma más que eso, su cursor caducará y tendrá el mismo problema de todos modos, lo cual se explica a continuación:

Que esta pasando aqui:

Error: getMore command failed puede deberse a un tiempo de espera del cursor, que está relacionado con dos atributos del cursor:

  • Límite de tiempo de espera, que es de 10 minutos de forma predeterminada. De los documentos :

    Por defecto, el servidor cerrará automáticamente el cursor después de 10 minutos de inactividad, o si el cliente ha agotado el cursor.

  • Tamaño de lote, que es 101 documentos o 16 MB para el primer lote, y 16 MB, independientemente del número de documentos, para lotes posteriores (a partir de MongoDB 3.4 ). De los documentos :

    find() operaciones find() y aggregate() tienen un tamaño de lote inicial de 101 documentos por defecto. Las operaciones getMore posteriores emitidas contra el cursor resultante no tienen un tamaño de lote predeterminado, por lo que están limitadas solo por el tamaño del mensaje de 16 megabytes.

Probablemente esté consumiendo esos 101 documentos iniciales y luego obtenga un lote de 16 MB, que es el máximo, con muchos más documentos. Como se tarda más de 10 minutos en procesarlos, el cursor en el servidor agota el tiempo de espera y, cuando termine de procesar los documentos en el segundo lote y solicite uno nuevo , el cursor ya está cerrado:

A medida que recorre el cursor y llega al final del lote devuelto, si hay más resultados, cursor.next () realizará una operación getMore para recuperar el siguiente lote.

Soluciones posibles:

Veo 5 formas posibles de resolver esto, 3 buenas, con sus pros y sus contras, y 2 malas:

  1. 👍 Reducir el tamaño del lote para mantener vivo el cursor.

  2. 👍 Eliminar el tiempo de espera del cursor.

  3. 👍 Vuelva a intentar cuando el cursor caduque.

  4. 👎 Consultar los resultados en lotes manualmente.

  5. 👎 Obtenga todos los documentos antes de que caduque el cursor.

Tenga en cuenta que no están numerados siguiendo ningún criterio específico. Léalos y decida cuál funciona mejor para su caso particular.

1. 👍 Reducir el tamaño del lote para mantener vivo el cursor

Una forma de resolver eso es usar cursor.bacthSize para establecer el tamaño del lote en el cursor devuelto por su consulta de búsqueda para que coincida con los que puede procesar en esos 10 minutos:

const cursor = db.collection.find() .batchSize(NUMBER_OF_DOCUMENTS_IN_BATCH);

Sin embargo, tenga en cuenta que establecer un tamaño de lote muy pequeño (conservador) probablemente funcionará, pero también será más lento, ya que ahora necesita acceder al servidor más veces.

Por otro lado, establecerlo en un valor demasiado cercano al número de documentos que puede procesar en 10 minutos significa que es posible que si algunas iteraciones tardan un poco más en procesarse por cualquier motivo (otros procesos pueden estar consumiendo más recursos) , el cursor caducará de todos modos y obtendrá el mismo error nuevamente.

2. 👍 Eliminar el tiempo de espera del cursor

Otra opción es usar cursor.noCursorTimeout para evitar que el cursor se agote:

const cursor = db.collection.find().noCursorTimeout();

Esto se considera una mala práctica, ya que necesitaría cerrar el cursor manualmente o agotar todos sus resultados para que se cierre automáticamente:

Después de configurar la opción noCursorTimeout , debe cerrar el cursor manualmente con cursor.close() o agotar los resultados del cursor.

Como desea procesar todos los documentos en el cursor, no necesitaría cerrarlo manualmente, pero aún es posible que algo más salga mal en su código y se genere un error antes de que termine, dejando así el cursor abierto .

Si todavía desea utilizar este enfoque, utilice un try-catch para asegurarse de cerrar el cursor si algo sale mal antes de consumir todos sus documentos.

Tenga en cuenta que no considero que esta sea una mala solución (por lo tanto, el 👍), ya que incluso pensé que se considera una mala práctica ...:

  • Es una característica compatible con el controlador. Si fue tan malo, ya que hay formas alternativas de solucionar los problemas de tiempo de espera, como se explica en las otras soluciones, esto no será compatible.

  • Hay formas de usarlo de manera segura, es solo cuestión de ser extremadamente cauteloso con él.

  • Supongo que no está ejecutando este tipo de consultas regularmente, por lo que las posibilidades de que comience a dejar cursores abiertos en todas partes son bajas. Si este no es el caso, y realmente necesita lidiar con estas situaciones todo el tiempo, entonces tiene sentido no usar noCursorTimeout .

3. try Vuelva a intentar cuando el cursor caduque

Básicamente, coloca su código en un try-catch y cuando obtiene el error, obtiene un nuevo cursor omitiendo los documentos que ya ha procesado:

let processed = 0; let updated = 0; while(true) { const cursor = db.snapshots.find().sort({ _id: 1 }).skip(processed); try { while (cursor.hasNext()) { const doc = cursor.next(); ++processed; if (doc.stream && doc.roundedDate && !doc.sid) { db.snapshots.update({ _id: doc._id }, { $set: { sid: `${ doc.stream.valueOf() }-${ doc.roundedDate }` }}); ++updated; } } break; // Done processing all, exit outer loop } catch (err) { if (err.code !== 43) { // Something else than a timeout went wrong. Abort loop. throw err; } } }

Tenga en cuenta que debe ordenar los resultados para que esta solución funcione.

Con este enfoque, está minimizando el número de solicitudes al servidor utilizando el tamaño de lote máximo posible de 16 MB, sin tener que adivinar cuántos documentos podrá procesar en 10 minutos de antemano. Por lo tanto, también es más robusto que el enfoque anterior.

4. 👎 Consultar los resultados en lotes manualmente

Básicamente, usa skip() , limit() y sort() para hacer múltiples consultas con una cantidad de documentos que cree que puede procesar en 10 minutos.

Considero que esta es una mala solución porque el controlador ya tiene la opción de establecer el tamaño del lote, por lo que no hay razón para hacerlo manualmente, solo use la solución 1 y no reinvente la rueda.

Además, vale la pena mencionar que tiene los mismos inconvenientes que la solución 1,

5. 👎 Obtenga todos los documentos antes de que caduque el cursor

Probablemente su código se esté demorando en ejecutarse debido al procesamiento de resultados, por lo que podría recuperar todos los documentos primero y luego procesarlos:

const results = new Array(db.snapshots.find());

Esto recuperará todos los lotes uno tras otro y cerrará el cursor. Luego, puede recorrer todos los documentos dentro de los results y hacer lo que necesita hacer.

Sin embargo, si tiene problemas de tiempo de espera, es probable que su conjunto de resultados sea bastante grande, por lo que extraer todo lo que hay en la memoria puede no ser lo más recomendable.

Nota sobre el modo de instantánea y documentos duplicados

Es posible que algunos documentos se devuelvan varias veces si las operaciones de escritura intermedias los mueven debido a un aumento en el tamaño del documento. Para resolver esto, use cursor.snapshot() . De los documentos :

Agregue el método snapshot () a un cursor para alternar el modo "snapshot". Esto garantiza que la consulta no devolverá un documento varias veces, incluso si las operaciones de escritura intermedias resultan en un movimiento del documento debido al crecimiento en el tamaño del documento.

Sin embargo, tenga en cuenta sus limitaciones:

  • No funciona con colecciones fragmentadas.

  • No funciona con sort() o hint() , por lo que no funcionará con las soluciones 3 y 4.

  • No garantiza el aislamiento de la inserción o eliminación.

Tenga en cuenta que con la solución 5, la ventana de tiempo para mover los documentos que pueden causar la recuperación de documentos duplicados es más estrecha que con las otras soluciones, por lo que es posible que no necesite una snapshot() .

En su caso particular, como la colección se llama snapshot , probablemente no cambie, por lo que probablemente no necesite una snapshot() . Además, está realizando actualizaciones en documentos en función de sus datos y, una vez que se realiza la actualización, ese mismo documento no se actualizará nuevamente aunque se recupere varias veces, ya que la condición if lo omitirá.

Nota sobre cursores abiertos

Para ver un recuento de cursores abiertos, use db.serverStatus().metrics.cursor .

Necesito crear un nuevo sid campo en cada documento en una colección de aproximadamente 500K documentos. Cada sid es único y se basa en los campos de fecha roundedDate y stream existentes de ese registro.

Lo estoy haciendo con el siguiente código:

var cursor = db.getCollection(''snapshots'').find(); var iterated = 0; var updated = 0; while (cursor.hasNext()) { var doc = cursor.next(); if (doc.stream && doc.roundedDate && !doc.sid) { db.getCollection(''snapshots'').update({ "_id": doc[''_id''] }, { $set: { sid: doc.stream.valueOf() + ''-'' + doc.roundedDate, } }); updated++; } iterated++; }; print(''total '' + cursor.count() + '' iterated through '' + iterated + '' updated '' + updated);

Al principio funciona bien, pero después de unas horas y alrededor de 100K registra errores con:

Error: getMore command failed: { "ok" : 0, "errmsg": "Cursor not found, cursor id: ###", "code": 43, }: ...


Cuando se utiliza el controlador Java v3, noCursorTimeout debe establecerse en FindOptions.

DBCollectionFindOptions options = new DBCollectionFindOptions() .maxTime(90, TimeUnit.MINUTES) .noCursorTimeout(true) .batchSize(batchSize) .projection(projectionQuery); cursor = collection.find(filterQuery, options);



También me encontré con este problema, pero para mí fue causado por un error en el controlador MongDB.

Sucedió en la versión 3.0.x del paquete mongodb que se usa, por ejemplo, en Meteor 1.7.0.x , donde también grabé este problema. Se describe con más detalle en este comentario y el hilo contiene un proyecto de muestra que confirma el error: https://github.com/meteor/meteor/issues/9944#issuecomment-420542042

La actualización del paquete npm a 3.1.x arregló para mí, porque ya había tenido en cuenta los buenos consejos, dados aquí por @Danziger.