El $ total de búsqueda El tamaño total de los documentos en la canalización coincidente excede el tamaño máximo del documento

Tengo una $lookup agregación de $lookup bastante simple como la siguiente:

 {'$lookup': {'from': 'edge', 'localField': 'gid', 'foreignField': 'to', 'as': 'from'}} 

Cuando ejecuto esto en una coincidencia con suficientes documentos obtengo el siguiente error:

 Command failed with error 4568: 'Total size of documents in edge matching { $match: { $and: [ { from: { $eq: "geneDatabase:hugo" } }, {} ] } } exceeds maximum document size' on server 

Todos los bashs de limitar el número de documentos fallan. allowDiskUse: true no hace nada. Enviar un cursor no hace nada. Agregar un $limit en la agregación también falla.

¿Cómo podría ser esto?

Luego veo el error nuevamente. ¿De dónde vienen ese $match y $and y $eq ? ¿La cola de agregación detrás de las escenas está agotando la llamada de $lookup a otra agregación, una que se ejecuta sola y que no tengo la capacidad de proporcionar límites o usar cursores?

¿Que esta pasando aqui?

Como se indicó anteriormente en el comentario, el error ocurre porque al realizar la $lookup que produce de forma predeterminada una “matriz” de destino dentro del documento principal a partir de los resultados de la colección extranjera, el tamaño total de los documentos seleccionados para esa matriz hace que el padre exceda el límite de 16MB BSON.

El contador para esto es procesar con un $unwind que sigue inmediatamente a la etapa de canalización de $lookup . Esto realmente altera el comportamiento de $lookup de modo que en lugar de producir una matriz en la matriz, los resultados son en cambio una “copia” de cada padre por cada documento coincidente.

Más o menos como el uso regular de $unwind , con la excepción de que en lugar de procesar como una etapa de canalización “separada”, la acción de unwinding se agrega realmente a la operación de la línea $lookup . Lo ideal es que también siga el $unwind con una condición de $match , que también crea un argumento matching que también se agregará a $lookup . De hecho, puede ver esto en la salida de explain para la tubería.

El tema se cubre en realidad (brevemente) en una sección de optimización de canalización de agregación en la documentación central:

$ lookup + $ unwind Coalescence

Nuevo en la versión 3.2.

Cuando un $ unwind inmediatamente sigue a otra $ búsqueda, y el $ unwind opera en el campo como de $ lookup, el optimizador puede fusionar $ unwind en la etapa $ lookup. Esto evita la creación de documentos intermedios grandes.

Se demostró mejor con una lista que pone al servidor bajo tensión al crear documentos “relacionados” que excederían el límite de 16 MB de BSON. Hecho lo más brevemente posible para romper y trabajar alrededor del Límite de BSON:

 const MongoClient = require('mongodb').MongoClient; const uri = 'mongodb://localhost/test'; function data(data) { console.log(JSON.stringify(data, undefined, 2)) } (async function() { let db; try { db = await MongoClient.connect(uri); console.log('Cleaning....'); // Clean data await Promise.all( ["source","edge"].map(c => db.collection(c).remove() ) ); console.log('Inserting...') await db.collection('edge').insertMany( Array(1000).fill(1).map((e,i) => ({ _id: i+1, gid: 1 })) ); await db.collection('source').insert({ _id: 1 }) console.log('Fattening up....'); await db.collection('edge').updateMany( {}, { $set: { data: "x".repeat(100000) } } ); // The full pipeline. Failing test uses only the $lookup stage let pipeline = [ { $lookup: { from: 'edge', localField: '_id', foreignField: 'gid', as: 'results' }}, { $unwind: '$results' }, { $match: { 'results._id': { $gte: 1, $lte: 5 } } }, { $project: { 'results.data': 0 } }, { $group: { _id: '$_id', results: { $push: '$results' } } } ]; // List and iterate each test case let tests = [ 'Failing.. Size exceeded...', 'Working.. Applied $unwind...', 'Explain output...' ]; for (let [idx, test] of Object.entries(tests)) { console.log(test); try { let currpipe = (( +idx === 0 ) ? pipeline.slice(0,1) : pipeline), options = (( +idx === tests.length-1 ) ? { explain: true } : {}); await new Promise((end,error) => { let cursor = db.collection('source').aggregate(currpipe,options); for ( let [key, value] of Object.entries({ error, end, data }) ) cursor.on(key,value); }); } catch(e) { console.error(e); } } } catch(e) { console.error(e); } finally { db.close(); } })(); 

Después de insertar algunos datos iniciales, la lista intentará ejecutar un agregado que consiste simplemente en $lookup que fallará con el siguiente error:

{MongoError: el tamaño total de los documentos en la interconexión de coincidencia de bordes {$ match: {$ y: [{gid: {$ eq: 1}}, {}]}} excede el tamaño máximo del documento

Lo que básicamente le dice que el límite de BSON fue excedido en la recuperación.

Por el contrario, el siguiente bash agrega las etapas de tramitación $unwind y $match

El resultado de Explain :

  { "$lookup": { "from": "edge", "as": "results", "localField": "_id", "foreignField": "gid", "unwinding": { // $unwind now is unwinding "preserveNullAndEmptyArrays": false }, "matching": { // $match now is matching "$and": [ // and actually executed against { // the foreign collection "_id": { "$gte": 1 } }, { "_id": { "$lte": 5 } } ] } } }, // $unwind and $match stages removed { "$project": { "results": { "data": false } } }, { "$group": { "_id": "$_id", "results": { "$push": "$results" } } } 

Y ese resultado, por supuesto, tiene éxito, porque como los resultados ya no se colocan en el documento principal, entonces no se puede exceder el límite de BSON.

Esto simplemente sucede como resultado de agregar $unwind solamente, pero el $match se agrega, por ejemplo, para mostrar que esto también se agrega a la etapa $lookup y que el efecto general es “limitar” los resultados devueltos de manera efectiva , ya que todo se hace en esa operación de $lookup y no se devuelve ningún otro resultado que no sea la coincidencia.

Al construir de esta manera, puede consultar los “datos referenciados” que superarían el límite BSON y luego, si desea $group los resultados en un formato de matriz, una vez que hayan sido filtrados efectivamente por la “consulta oculta” que realmente se está realizado por $lookup .


MongoDB 3.6 y superior – Adicional para “IZQUIERDA”

Como todas las notas anteriores de contenido, el límite de BSON es un límite “difícil” que no puede violar y, por lo general, este es el motivo por el cual $unwind es necesario como paso intermedio. Sin embargo, existe la limitación de que “LEFT JOIN” se convierte en “INNER JOIN” en virtud de $unwind donde no puede conservar el contenido. Incluso incluso preserveNulAndEmptyArrays negaría la “coalescencia” y aún dejaría la matriz intacta, causando el mismo problema de límite de BSON.

MongoDB 3.6 agrega una nueva syntax a $lookup que permite usar una expresión “sub-canalización” en lugar de las claves “local” y “extranjera”. Entonces, en lugar de usar la opción de “coalescencia” como se demostró, siempre que la matriz producida no rompa el límite también es posible poner condiciones en esa tubería que devuelve la matriz “intacta” y posiblemente sin coincidencias como sería indicativo de una “JUNTA IZQUIERDA”.

La nueva expresión sería entonces:

 { "$lookup": { "from": "edge", "let": { "gid": "$gid" }, "pipeline": [ { "$match": { "_id": { "$gte": 1, "$lte": 5 }, "$expr": { "$eq": [ "$$gid", "$to" ] } }} ], "as": "from" }} 

De hecho, esto sería básicamente lo que MongoDB está haciendo “bajo las sábanas” con la syntax anterior, ya que 3.6 usa $expr “internamente” para construir la statement. La diferencia, por supuesto, es que no existe una opción de "unwinding" presente en cómo se ejecuta realmente la $lookup .

Si no se producen realmente documentos como resultado de la expresión "pipeline" , entonces la matriz de destino dentro del documento maestro estará vacía, tal como lo hace realmente una “ASAMBLEA IZQUIERDA” y sería el comportamiento normal de $lookup sin ningún otras opciones.

Sin embargo, la matriz de salida NO DEBE hacer que el documento donde se está creando exceda el Límite BSON . Por lo tanto, depende de usted asegurarse de que cualquier contenido “coincidente” según las condiciones se mantenga por debajo de este límite o persistirá el mismo error, a menos que, por supuesto, use $unwind para efectuar la “UNIÓN INTERNA”.