sql >> Database >  >> NoSQL >> MongoDB

Totale grootte van documenten in overeenkomende pijplijn overschrijdt de maximale documentgrootte

Zoals eerder vermeld in het commentaar, treedt de fout op omdat bij het uitvoeren van de $lookup die standaard een doel-"array" binnen het bovenliggende document produceert op basis van de resultaten van de buitenlandse verzameling, zorgt de totale grootte van de documenten die voor die matrix zijn geselecteerd ervoor dat de bovenliggende limiet de 16 MB BSON-limiet overschrijdt.

De teller hiervoor is te verwerken met een $unwind die onmiddellijk volgt op de $lookup pijplijn stadium. Dit verandert feitelijk het gedrag van $lookup zodanig dat in plaats van een array in de parent te produceren, de resultaten in plaats daarvan een "kopie" zijn van elke parent voor elk overeenkomend document.

Vrijwel net als normaal gebruik van $unwind , met de uitzondering dat in plaats van het verwerken als een "aparte" pijplijnfase, de unwinding actie wordt daadwerkelijk toegevoegd aan de $lookup leidingoperatie zelf. Idealiter volg je ook de $unwind met een $match voorwaarde, die ook een matching . creëert argument dat ook moet worden toegevoegd aan de $lookup . Je kunt dit zien in de explain output voor de pijplijn.

Het onderwerp wordt feitelijk (kort) behandeld in een sectie van Aggregation Pipeline Optimization in de kerndocumentatie:

$lookup + $unwind Coalescence

Nieuw in versie 3.2.

Wanneer een $unwind onmiddellijk volgt op een andere $lookup, en de $unwind werkt in het as-veld van de $lookup, kan de optimizer de $unwind samenvoegen tot de $lookup-fase. Dit voorkomt het maken van grote tussenliggende documenten.

Het beste wordt gedemonstreerd met een vermelding die de server onder druk zet door "gerelateerde" documenten te maken die de 16 MB BSON-limiet zouden overschrijden. Zo kort mogelijk gedaan om zowel de BSON-limiet te doorbreken als te omzeilen:

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();
  }

})();

Na het invoegen van enkele initiële gegevens, zal de lijst proberen een aggregaat uit te voeren dat alleen bestaat uit $lookup die zal mislukken met de volgende fout:

{ MongoError:Totale grootte van documenten in edge matching pipeline { $match:{ $and:[ {gid:{ $eq:1 } }, {} ] } } overschrijdt de maximale documentgrootte

Wat in feite aangeeft dat de BSON-limiet is overschreden bij het ophalen.

Daarentegen voegt de volgende poging de $unwind . toe en $match pijplijn stadia

De Explain-output :

  {
    "$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"
      }
    }
  }

En dat resultaat slaagt natuurlijk, want aangezien de resultaten niet meer in het bovenliggende document worden geplaatst, kan de BSON-limiet niet overschreden worden.

Dit gebeurt eigenlijk gewoon als gevolg van het toevoegen van $unwind alleen, maar de $match wordt bijvoorbeeld toegevoegd om aan te geven dat dit ook . is toegevoegd aan de $lookup en dat het algehele effect is dat de geretourneerde resultaten op een effectieve manier worden "beperkt", aangezien het allemaal in die $lookup wordt gedaan bewerking en geen andere resultaten dan die die overeenkomen, worden daadwerkelijk geretourneerd.

Door op deze manier te construeren, kunt u zoeken naar "referentiegegevens" die de BSON-limiet zouden overschrijden en vervolgens als u $group wilt de resultaten terug in een array-indeling, zodra ze effectief zijn gefilterd door de "verborgen zoekopdracht" die daadwerkelijk wordt uitgevoerd door $lookup .

MongoDB 3.6 en hoger - extra voor "LEFT JOIN"

Zoals alle bovenstaande inhoud aangeeft, is de BSON-limiet een "hard" limiet die u niet kunt overschrijden en dit is over het algemeen de reden waarom de $unwind is noodzakelijk als tussenstap. Er is echter de beperking dat de "LEFT JOIN" een "INNER JOIN" wordt dankzij de $unwind waar het de inhoud niet kan behouden. Ook zelfs preserveNulAndEmptyArrays zou de "coalescentie" teniet doen en toch de intacte array achterlaten, wat hetzelfde BSON-limietprobleem veroorzaakt.

MongoDB 3.6 voegt nieuwe syntaxis toe aan $lookup waarmee een "subpijplijn"-expressie kan worden gebruikt in plaats van de "lokale" en "buitenlandse" toetsen. Dus in plaats van de "coalescentie" -optie te gebruiken, zoals aangetoond, is het mogelijk om, zolang de geproduceerde array de limiet niet overschrijdt, voorwaarden in die pijplijn te plaatsen die de array "intact" retourneert en mogelijk zonder overeenkomsten, zoals indicatief zou zijn van een "LEFT JOIN".

De nieuwe uitdrukking zou dan zijn:

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

In feite zou dit eigenlijk zijn wat MongoDB doet "onder de dekens" met de vorige syntaxis sinds 3.6 gebruikt $expr "intern" om de verklaring te construeren. Het verschil is natuurlijk dat er geen "unwinding" is optie aanwezig in hoe de $lookup daadwerkelijk wordt uitgevoerd.

Als er daadwerkelijk geen documenten worden geproduceerd als gevolg van de "pipeline" expressie, dan is de doelarray in het hoofddocument in feite leeg, net zoals een "LEFT JOIN" dat doet en zou het normale gedrag zijn van $lookup zonder andere opties.

De uitvoerarray naar MOET er echter NIET toe leiden dat het document waarin het wordt gemaakt de BSON-limiet overschrijdt . Het is dus echt aan jou om ervoor te zorgen dat alle "overeenkomstige" inhoud door de voorwaarden onder deze limiet blijft, anders blijft dezelfde fout bestaan, tenzij je natuurlijk daadwerkelijk $unwind gebruikt. om de "INNER JOIN" uit te voeren.



  1. Gegeven een lijst met id's, wat is de beste manier om op te vragen welke id's niet in de verzameling voorkomen?

  2. hoe krijg ik sessies werkend met redis, express &socket.io?

  3. Top MongoDB-bronnen

  4. SCUMM:de op agenten gebaseerde databasebewakingsinfrastructuur in ClusterControl