MongoDB
 sql >> Teknologi Basis Data >  >> NoSQL >> MongoDB

Agregat $lookup Ukuran total dokumen dalam saluran yang cocok melebihi ukuran dokumen maksimum

Seperti yang dinyatakan sebelumnya dalam komentar, kesalahan terjadi karena saat melakukan $lookup yang secara default menghasilkan "array" target dalam dokumen induk dari hasil koleksi asing, ukuran total dokumen yang dipilih untuk larik tersebut menyebabkan induk melebihi Batas BSON 16MB.

Penghitung untuk ini adalah memproses dengan $unwind yang langsung mengikuti $lookup tahap pipa. Ini sebenarnya mengubah perilaku $lookup sedemikian rupa sehingga alih-alih menghasilkan larik di induk, hasilnya malah menjadi "salinan" dari setiap induk untuk setiap dokumen yang cocok.

Hampir seperti penggunaan biasa $unwind , dengan pengecualian bahwa alih-alih memproses sebagai tahap saluran pipa "terpisah", unwinding tindakan sebenarnya ditambahkan ke $lookup pengoperasian pipa itu sendiri. Idealnya Anda juga mengikuti $unwind dengan $match kondisi, yang juga membuat matching argumen untuk juga ditambahkan ke $lookup . Anda sebenarnya dapat melihat ini di explain keluaran untuk pipeline.

Topik ini sebenarnya dibahas (secara singkat) di bagian Pengoptimalan Pipa Agregasi dalam dokumentasi inti:

$lookup + $unwind Coalescence

Baru di versi 3.2.

Ketika $unwind segera mengikuti $lookup lain, dan $unwind beroperasi pada bidang as dari $lookup, pengoptimal dapat menggabungkan $unwind ke tahap $lookup. Ini menghindari pembuatan dokumen perantara berukuran besar.

Paling baik ditunjukkan dengan daftar yang menempatkan server di bawah tekanan dengan membuat dokumen "terkait" yang akan melebihi batas BSON 16 MB. Dilakukan sesingkat mungkin untuk memecahkan dan mengatasi Batas 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();
  }

})();

Setelah memasukkan beberapa data awal, cantuman akan mencoba menjalankan agregat yang hanya terdiri dari $lookup yang akan gagal dengan kesalahan berikut:

{ MongoError:Ukuran total dokumen dalam saluran pencocokan tepi { $match:{ $and :[ { gid:{ $eq:1 } }, {} ] } } melebihi ukuran dokumen maksimum

Yang pada dasarnya memberi tahu Anda bahwa batas BSON terlampaui saat pengambilan.

Sebaliknya, upaya berikutnya menambahkan $unwind dan $match tahapan pipa

Keluaran Jelaskan :

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

Dan hasil tersebut tentu saja berhasil, karena hasil tidak lagi ditempatkan pada dokumen induk maka batas BSON tidak dapat dilampaui.

Ini benar-benar terjadi sebagai akibat dari menambahkan $unwind saja, tetapi $match ditambahkan misalnya untuk menunjukkan bahwa ini juga ditambahkan ke $lookup tahap dan bahwa efek keseluruhannya adalah untuk "membatasi" hasil yang dikembalikan dengan cara yang efektif, karena semuanya dilakukan di $lookup operasi dan tidak ada hasil lain selain yang cocok yang benar-benar dikembalikan.

Dengan membangun dengan cara ini Anda dapat meminta "data referensi" yang akan melebihi batas BSON dan kemudian jika Anda ingin $group hasilnya kembali ke format array, setelah difilter secara efektif oleh "kueri tersembunyi" yang sebenarnya dilakukan oleh $lookup .

MongoDB 3.6 ke atas - Tambahan untuk "LEFT JOIN"

Seperti yang dicatat oleh semua konten di atas, Batas BSON adalah "keras" batas yang tidak dapat Anda langgar dan inilah alasan mengapa $unwind diperlukan sebagai langkah sementara. Namun ada batasan bahwa "LEFT JOIN" menjadi "INNER JOIN" berdasarkan $unwind di mana ia tidak dapat mempertahankan konten. Bahkan preserveNulAndEmptyArrays akan meniadakan "perpaduan" dan masih meninggalkan array yang utuh, menyebabkan masalah Batas BSON yang sama.

MongoDB 3.6 menambahkan sintaks baru ke $lookup yang memungkinkan ekspresi "sub-pipa" digunakan sebagai pengganti kunci "lokal" dan "asing". Jadi, alih-alih menggunakan opsi "perpaduan" seperti yang ditunjukkan, selama larik yang dihasilkan tidak juga melanggar batas, dimungkinkan untuk menempatkan kondisi dalam saluran itu yang mengembalikan larik "utuh", dan mungkin tanpa kecocokan seperti yang akan menjadi indikasi dari "LEFT JOIN".

Ekspresi baru akan menjadi:

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

Sebenarnya ini pada dasarnya adalah apa yang MongoDB lakukan "di balik selimut" dengan sintaks sebelumnya sejak 3.6 menggunakan $expr "internal" untuk membangun pernyataan. Bedanya tentu tidak ada "unwinding" opsi hadir dalam cara $lookup benar-benar dieksekusi.

Jika tidak ada dokumen yang benar-benar dihasilkan sebagai hasil dari "pipeline" ekspresi, maka array target dalam dokumen master sebenarnya akan kosong, seperti halnya "LEFT JOIN" yang sebenarnya dan akan menjadi perilaku normal $lookup tanpa pilihan lain.

Namun larik keluaran ke TIDAK HARUS menyebabkan dokumen yang sedang dibuat melebihi Batas BSON . Jadi terserah Anda untuk memastikan bahwa konten "cocok" apa pun dengan ketentuan tetap di bawah batas ini atau kesalahan yang sama akan tetap ada, kecuali tentu saja Anda benar-benar menggunakan $unwind untuk melakukan "INNER JOIN".



  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Mengapa kita membutuhkan 'arbiter' dalam replikasi MongoDB?

  2. MongoDB $objectToArray

  3. Mustahil untuk mendapatkan properti dari objek luwak

  4. MongoDB - berbeda dengan kueri tidak menggunakan indeks

  5. Bagaimana cara mengubah semua elemen array dalam dokumen mongodb ke nilai tertentu?