sql >> Database teknologi >  >> NoSQL >> MongoDB

Samlet $lookup Den samlede størrelse af dokumenter i matchende pipeline overstiger den maksimale dokumentstørrelse

Som nævnt tidligere i kommentaren opstår fejlen, fordi når du udfører $lookup som som standard producerer et mål "array" i det overordnede dokument ud fra resultaterne af den udenlandske indsamling, får den samlede størrelse af dokumenter, der er valgt for det array, det overordnede til at overskride 16 MB BSON-grænsen.

Tælleren for dette er at behandle med en $unwind som umiddelbart følger $lookup pipeline fase. Dette ændrer faktisk adfærden for $lookup på en sådan måde, at resultaterne i stedet for at producere en matrix i det overordnede, i stedet er en "kopi" af hver forælder for hvert matchede dokument.

Stort set ligesom almindelig brug af $unwind , med den undtagelse, at unwinding i stedet for at behandle som et "separat" pipelinetrin handling er faktisk tilføjet til $lookup selve rørledningsdriften. Ideelt set følger du også $unwind med en $match betingelse, som også skaber en matching argument skal også tilføjes til $lookup . Du kan faktisk se dette i explain output for rørledningen.

Emnet er faktisk dækket (kort) i et afsnit af Aggregation Pipeline Optimization i kernedokumentationen:

$lookup + $unwind Coalescence

Ny i version 3.2.

Når et $unwind umiddelbart følger efter et andet $opslag, og $unwind opererer på as-feltet i $opslag, kan optimeringsværktøjet samle $unwind ind i $opslagsstadiet. Dette undgår at skabe store mellemliggende dokumenter.

Bedst demonstreret med en liste, der sætter serveren under stress ved at skabe "relaterede" dokumenter, der ville overskride 16 MB BSON-grænsen. Udført så kort som muligt for både at bryde og omgå BSON-grænsen:

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

})();

Efter at have indsat nogle indledende data, vil fortegnelsen forsøge at køre et aggregat, der kun består af $lookup som vil mislykkes med følgende fejl:

{ MongoError:Samlet størrelse af dokumenter i kantmatchende pipeline { $match:{ $and :[ { gid:{ $eq:1 } }, {} ] } } overstiger den maksimale dokumentstørrelse

Hvilket grundlæggende fortæller dig, at BSON-grænsen blev overskredet ved hentning.

I modsætning hertil tilføjer næste forsøg $unwind og $match pipeline stadier

Forklar-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"
      }
    }
  }

Og det resultat lykkes selvfølgelig, for da resultaterne ikke længere placeres i det overordnede dokument, kan BSON-grænsen ikke overskrides.

Dette sker egentlig bare som et resultat af tilføjelse af $unwind kun, men $match tilføjes f.eks. for at vise, at dette også er tilføjet i $lookup fase, og at den overordnede effekt er at "begrænse" de resultater, der returneres på en effektiv måde, da det hele foregår i den $lookup operation og ingen andre resultater end de matchende returneres faktisk.

Ved at konstruere på denne måde kan du forespørge efter "referencedata", der ville overskride BSON-grænsen, og derefter om du vil have $group resultaterne tilbage i et array-format, når de er blevet effektivt filtreret af den "skjulte forespørgsel", der rent faktisk udføres af $lookup .

MongoDB 3.6 og nyere - Yderligere til "LEFT JOIN"

Som alt indholdet ovenfor bemærker, er BSON-grænsen en "hård" grænse, som du ikke kan overtræde, og det er generelt derfor $unwind er nødvendigt som et midlertidigt skridt. Der er dog den begrænsning, at "LEFT JOIN" bliver en "INNER JOIN" i kraft af $unwind hvor den ikke kan bevare indholdet. Også selv preserveNulAndEmptyArrays ville ophæve "sammensmeltningen" og stadig forlade det intakte array, hvilket forårsager det samme BSON Limit-problem.

MongoDB 3.6 tilføjer ny syntaks til $lookup der gør det muligt at bruge et "sub-pipeline" udtryk i stedet for de "lokale" og "fremmede" nøgler. Så i stedet for at bruge "sammensmeltnings"-muligheden som vist, så længe det producerede array ikke også overskrider grænsen, er det muligt at sætte betingelser i den pipeline, som returnerer arrayet "intakt", og muligvis uden match, hvilket ville være vejledende af en "LEFT JOIN".

Det nye udtryk ville så være:

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

Faktisk ville dette dybest set være, hvad MongoDB laver "under dynen" med den tidligere syntaks siden 3.6 bruger $expr "internt" for at konstruere udsagnet. Forskellen er selvfølgelig, at der ikke er nogen "unwinding" mulighed til stede i hvordan $lookup faktisk bliver henrettet.

Hvis der faktisk ikke produceres dokumenter som følge af "pipeline" udtryk, så vil målarrayet i masterdokumentet faktisk være tomt, ligesom en "LEFT JOIN" faktisk gør og ville være den normale adfærd for $lookup uden andre muligheder.

Output-arrayet til MÅ IKKE forårsage, at dokumentet, hvor det oprettes, overskrider BSON-grænsen . Så det er virkelig op til dig at sikre, at alt "matchende" indhold efter betingelserne forbliver under denne grænse, ellers vil den samme fejl fortsætte, medmindre du selvfølgelig rent faktisk bruger $unwind for at udføre "INNER JOIN".



  1. RuntimeError:-ERR Ukendt kommando, der kører redis gem på Ruby

  2. Sådan bruges UNSUBSCRIBE-kommandoen i Redis 2.6.11

  3. Er Redis-opdateringer synkrone?

  4. Padding i SQL