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()
operacionesfind()
yaggregate()
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:
-
👍 Reducir el tamaño del lote para mantener vivo el cursor.
-
👍 Eliminar el tiempo de espera del cursor.
-
👍 Vuelva a intentar cuando el cursor caduque.
-
👎 Consultar los resultados en lotes manualmente.
-
👎 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 concursor.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()
ohint()
, 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);
Es un error en la administración de la sesión del servidor mongodb. Arreglo actualmente en progreso, debe arreglarse en 4.0+
(reproducido en MongoDB 3.6.5)
agregar
collection.find().batchSize(20)
me ayudó con un pequeño rendimiento reducido.
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.