sql >> Base de Datos >  >> NoSQL >> MongoDB

MongoDB - Error:el comando getMore falló:no se encontró el cursor

EDITAR - Rendimiento de la consulta:

Como señaló @NeilLunn en sus comentarios, no debe filtrar los documentos manualmente, sino usar .find(...) por eso en su lugar:

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

Además, usando .bulkWrite() , disponible a partir de MongoDB 3.2 , tendrá mucho más rendimiento que hacer 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, que se explica a continuación:

Qué está pasando aquí:

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 por defecto. De los documentos:

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

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

    find() y aggregate() las operaciones tienen un tamaño de lote inicial de 101 documentos de forma predeterminada. Las operaciones getMore subsiguientes emitidas contra el cursor resultante no tienen un tamaño de lote predeterminado, por lo que solo están limitadas 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 se agota y, cuando termina de procesar los documentos en el segundo lote y solicita 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.

Posibles soluciones:

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

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

  2. 👍 Elimina el tiempo de espera del cursor.

  3. 👍 Vuelve a intentarlo cuando el cursor expire.

  4. 👎 Consulta los resultados por lotes de forma manual.

  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. 👍 Reduciendo 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 find consulta 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 configurar un tamaño de lote muy conservador (pequeño) probablemente funcionará, pero también será más lento, ya que ahora necesita acceder al servidor más veces.

Por otro lado, configurarlo en un valor demasiado cercano a la cantidad de documentos que puede procesar en 10 minutos significa que es posible que algunas iteraciones tarden un poco más en procesarse por algún motivo (otros procesos pueden estar consumiendo más recursos) , el cursor caducará de todos modos y volverá a aparecer el mismo error.

2. 👍 Eliminar el tiempo de espera del cursor

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

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

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

Después de configurar noCursorTimeout opción, debe cerrar el cursor manualmente con cursor.close() o agotando 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 arroje un error antes de que termine, dejando el cursor abierto. .

Si aún desea utilizar este enfoque, use 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 consideraba una mala práctica...:

  • Es una función compatible con el controlador. Si fue tan malo, ya que existen formas alternativas de evitar los problemas de tiempo de espera, como se explica en las otras soluciones, esto no será compatible.

  • Hay formas de usarlo de manera segura, solo es cuestión de tener mucho cuidado 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. 👍 Vuelve a intentarlo cuando el cursor expire

Básicamente, pones tu código en un try-catch y cuando obtiene el error, obtiene un nuevo cursor saltando 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 la cantidad 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. 👎 Consulta los resultados por lotes manualmente

Básicamente, utiliza skip(), limit() y sort() para realizar 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. 👎 Obtén todos los documentos antes de que expire el cursor

Probablemente su código esté tardando 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 results y haz lo que tengas que 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 en la memoria puede no ser lo más recomendable.

Nota sobre el modo de instantánea y los documentos duplicados

Es posible que algunos documentos se devuelvan varias veces si las operaciones de escritura intermedias los mueven debido al aumento del 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 "instantánea". Esto garantiza que la consulta no devolverá un documento varias veces, incluso si las operaciones de escritura intermedias dan como resultado un movimiento del documento debido al crecimiento del tamaño del documento.

Sin embargo, ten 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 inserciones o eliminaciones.

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

En tu caso particular, como la colección se llama snapshot , probablemente no cambie, por lo que probablemente no necesite 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, como if la condición lo omitirá.

Nota sobre cursores abiertos

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