MongoDB
 sql >> Baza danych >  >> NoSQL >> MongoDB

Agregacja $lookup Całkowity rozmiar dokumentów w zgodnym potoku przekracza maksymalny rozmiar dokumentu

Jak wspomniano wcześniej w komentarzu, błąd występuje, ponieważ podczas wykonywania $lookup która domyślnie tworzy docelową "tablicę" w dokumencie nadrzędnym z wyników kolekcji zagranicznej, całkowity rozmiar dokumentów wybranych dla tej tablicy powoduje, że rodzic przekracza 16 MB limitu BSON.

Licznikiem tego jest przetwarzanie za pomocą $unwind który bezpośrednio następuje po $lookup etap rurociągu. To faktycznie zmienia zachowanie $lookup w taki sposób, że zamiast tworzyć tablicę w rodzicu, wyniki są zamiast tego "kopią" każdego rodzica dla każdego dopasowanego dokumentu.

Prawie tak, jak zwykłe użycie $unwind , z wyjątkiem tego, że zamiast przetwarzania jako „oddzielny” etap potoku, unwinding akcja jest faktycznie dodawana do $lookup samą obsługę rurociągu. Idealnie byłoby również podążać za $unwind z $match warunek, który również tworzy matching argument do dodania również do $lookup . Możesz to zobaczyć w explain wyjście dla potoku.

Temat jest w rzeczywistości omówiony (krótko) w sekcji Optymalizacja rurociągu agregacji w podstawowej dokumentacji:

$lookup + $odpręż Koalescencja

Nowość w wersji 3.2.

Kiedy $odwijanie następuje natychmiast po kolejnym $wyglądaniu, a $odwijanie działa na polu jako $wyszukiwania, optymalizator może połączyć $odwijanie z etapem $wyszukiwania. Pozwala to uniknąć tworzenia dużych dokumentów pośrednich.

Najlepiej zademonstrować listę, która obciąża serwer, tworząc „powiązane” dokumenty, które przekraczają limit 16 MB BSON. Zrób tak krótko, jak to możliwe, aby zarówno złamać, jak i obejść limit 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();
  }

})();

Po wstawieniu niektórych danych początkowych, listing spróbuje uruchomić agregat składający się jedynie z $lookup który zakończy się niepowodzeniem z następującym błędem:

{ MongoError:Całkowity rozmiar dokumentów w potoku dopasowania krawędzi { $match:{ $and :[ { gid:{ $eq:1 } }, {} ] } } przekracza maksymalny rozmiar dokumentu

Co w zasadzie oznacza, że ​​limit BSON został przekroczony podczas pobierania.

Natomiast następna próba dodaje $unwind i $match etapy rurociągu

Wyjście Wyjaśnij :

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

I ten wynik oczywiście się powiedzie, ponieważ ponieważ wyniki nie są już umieszczane w dokumencie nadrzędnym, nie można przekroczyć limitu BSON.

Tak naprawdę dzieje się to w wyniku dodania $unwind tylko, ale $match jest dodawany na przykład, aby pokazać, że jest to również dodane do $lookup etapie i że ogólnym efektem jest „ograniczenie” zwracanych wyników w skuteczny sposób, ponieważ wszystko odbywa się w tym $lookup operacja i żadne inne wyniki poza pasującymi nie są w rzeczywistości zwracane.

Konstruując w ten sposób możesz zapytać o "dane referencyjne", które przekroczyłyby limit BSON, a następnie, jeśli chcesz, $group wyniki z powrotem do formatu tablicy, gdy zostaną skutecznie przefiltrowane przez „ukryte zapytanie”, które jest faktycznie wykonywane przez $lookup .

MongoDB 3.6 i nowsze — dodatkowe dla „LEFT JOIN”

Jak zauważają wszystkie powyższe treści, limit BSON jest "twardy" limit, którego nie można przekroczyć i dlatego generalnie $unwind jest konieczne jako etap przejściowy. Istnieje jednak ograniczenie polegające na tym, że „LEFT JOIN” staje się „INNER JOIN” na mocy $unwind gdzie nie może zachować treści. Nawet preserveNulAndEmptyArrays zanegowałby „koalescencję” i nadal pozostawiłby nienaruszoną tablicę, powodując ten sam problem z limitem BSON.

MongoDB 3.6 dodaje nową składnię do $lookup co pozwala na użycie wyrażenia „podpotoku” zamiast kluczy „lokalnego” i „obcego”. Więc zamiast używać opcji „koalescencji”, jak pokazano, o ile wyprodukowana tablica nie przekracza również limitu, można umieścić warunki w tym potoku, który zwróci tablicę „nienaruszoną” i prawdopodobnie bez dopasowań, co byłoby orientacyjne „LEWE DOŁĄCZENIE”.

Nowe wyrażenie byłoby wtedy:

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

W rzeczywistości jest to w zasadzie to, co MongoDB robi „pod przykrywką” z poprzednią składnią od wersji 3.6 używa $expr „wewnętrznie” w celu skonstruowania instrukcji. Różnica oczywiście polega na tym, że nie ma "unwinding" opcja obecna w sposobie $lookup faktycznie zostanie wykonany.

Jeśli żadne dokumenty nie są faktycznie tworzone w wyniku "pipeline" wyrażenie, wtedy tablica docelowa w dokumencie głównym będzie w rzeczywistości pusta, tak jak robi to „LEFT JOIN” i byłoby normalnym zachowaniem $lookup bez żadnych innych opcji.

Jednak tablica wyjściowa NIE MOŻE powodować, że dokument, w którym jest tworzony, przekracza limit BSON . Tak więc to naprawdę zależy od ciebie, aby upewnić się, że jakakolwiek "pasująca" zawartość według warunków pozostanie w tym limicie lub ten sam błąd będzie się powtarzał, chyba że faktycznie użyjesz $unwind aby wykonać „WEWNĘTRZNE POŁĄCZENIE”.



  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Klient MongoDB GUI (wieloplatformowy lub Linux)

  2. MongoParseError:opcje useCreateIndex, useFindAndModify nie są obsługiwane

  3. Usuwanie pliku database.yml podczas używania Mongoid w Rails 3.2

  4. Zapytania hierarchiczne z Mongo za pomocą $graphLookup

  5. C# + MongoDB — ObjectId bez użycia MongoDB DataTypes/Attributes