mongodb aggregation-framework

mongodb - Búsqueda $ agregada El tamaño total de los documentos en la tubería coincidente excede el tamaño máximo del documento



aggregation-framework (1)

Como se indicó anteriormente en el comentario, el error ocurre porque cuando se realiza la $lookup que por defecto produce una "matriz" objetivo dentro del documento padre 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 BSON de 16 MB.

El contador para esto es procesar con un $unwind que sigue inmediatamente a la etapa de canalización de $lookup . En realidad, esto altera el comportamiento de la $lookup de tal manera que, en lugar de producir una matriz en el elemento primario, los resultados son en cambio una "copia" de cada elemento primario para cada documento coincidente.

Al igual que 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 a la operación de canalización de $lookup sí. Idealmente, también sigue el $unwind con una condición de $match , que también crea un argumento matching que también se agregará a la $lookup . De hecho, puede ver esto en la salida de explain para la canalización.

El tema se trata realmente (brevemente) en una sección de Optimización de canalización de agregación en la documentación principal:

$ búsqueda + $ desenrollar Coalescencia

Nuevo en la versión 3.2.

Cuando un $ desenrollar sigue inmediatamente a otra búsqueda $, y el $ desenrollar opera en el campo as de la búsqueda $, el optimizador puede unir el desenrollar $ en la etapa de búsqueda $. Esto evita crear documentos intermedios grandes.

Se demuestra mejor con una lista que pone el servidor bajo tensión creando documentos "relacionados" que excederían el límite BSON de 16 MB. Hecho lo más brevemente posible para romper y trabajar alrededor del Límite 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, el listado 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 tubería de coincidencia de bordes {$ match: {$ and: [{gid: {$ eq: 1}}, {}]}} excede el tamaño máximo del documento

Lo que básicamente te dice que el límite BSON se excedió en la recuperación.

Por el contrario, el siguiente intento agrega las etapas de canalización $unwind y $match

La salida 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, ya que como los resultados ya no se colocan en el documento principal, el límite BSON no se puede exceder.

Esto realmente sucede como resultado de agregar solo $unwind , pero la $match se agrega, por ejemplo, para mostrar que esto también se agrega a la etapa de $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 sean los que coinciden.

Al construir de esta manera, puede consultar "datos de referencia" que excederían el límite de BSON y luego, si desea $group los resultados volverán a un formato de matriz, una vez que la "consulta oculta" los haya filtrado efectivamente realizado por $lookup .

MongoDB 3.6 y superior - Adicional para "LEFT JOIN"

Como todo el contenido anterior señala, el Límite BSON es un límite "difícil" que no se puede violar y generalmente es por eso que el $unwind es necesario como un paso intermedio. Sin embargo, existe la limitación de que la "UNIÓN IZQUIERDA" se convierte en una "UNIÓN INTERNA" en virtud de la $unwind donde no puede preservar el contenido. También, incluso preserveNulAndEmptyArrays negaría la "fusión" y aún dejaría la matriz intacta, causando el mismo problema de límite BSON.

MongoDB 3.6 agrega una nueva sintaxis a $lookup que permite utilizar una expresión "sub-pipeline" en lugar de las claves "local" y "extranjera". Entonces, en lugar de usar la opción de "fusión" como se demostró, siempre que la matriz producida no viole el límite, es posible poner condiciones en esa tubería que devuelve la matriz "intacta", y posiblemente sin coincidencias como sería indicativo de una "IZQUIERDA UNIRSE".

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 "en secreto" con la sintaxis anterior, ya que 3.6 usa $expr "internamente" para construir la declaración. La diferencia, por supuesto, es que no existe una opción de "unwinding" en cómo se ejecuta realmente la $lookup .

Si en realidad no se producen documentos como resultado de la expresión "pipeline" , la matriz de destino dentro del documento maestro de hecho estará vacía, tal como lo hace una "LEFT JOIN" y sería el comportamiento normal de $lookup sin ninguna 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, realmente depende de usted asegurarse de que cualquier contenido "coincidente" según las condiciones permanezca por debajo de este límite o el mismo error persistirá, a menos que, por supuesto, use $unwind para efectuar la "UNIÓN INTERNA".

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

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

Cuando ejecuto esto en una coincidencia con suficientes documentos me sale 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 intentos 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?

Entonces veo el error nuevamente. ¿De dónde vinieron ese $match y $and y $eq ? ¿La tubería de agregación detrás de escena está generando la llamada de $lookup a otra agregación, una que se ejecuta por sí sola y que no tengo la capacidad de proporcionar límites o usar cursores?

¿Que esta pasando aqui?