sql >> データベース >  >> NoSQL >> MongoDB

集計$lookup一致するパイプライン内のドキュメントの合計サイズが最大ドキュメントサイズを超えています

    コメントで前述したように、$lookupを実行すると、エラーが発生します。 デフォルトでは、外部コレクションの結果から親ドキュメント内にターゲット「配列」が生成されます。その配列用に選択されたドキュメントの合計サイズにより、親は16MBのBSON制限を超えます。

    このためのカウンターは、$unwindで処理することです $lookupの直後にあります パイプラインステージ。これにより、実際には$lookupの動作が変わります。 親で配列を生成する代わりに、結果は一致するすべてのドキュメントの各親の「コピー」になります。

    $unwindの通常の使用法とほとんど同じです 、「個別の」パイプラインステージとして処理する代わりに、unwinding アクションは実際には$lookupに追加されます パイプライン操作自体。理想的には、$unwindもフォローします $matchを使用 条件。これにより、matchingも作成されます。 $lookupにも追加される引数 。これはexplainで実際に確認できます パイプラインの出力。

    このトピックは、コアドキュメントの集約パイプライン最適化のセクションで実際に(簡単に)カバーされています:

    $ lookup + $ unwind Coalescence

    バージョン3.2の新機能。

    $unwindが別の$lookupの直後に続き、$unwindが$lookupのasフィールドで動作する場合、オプティマイザーは$unwindを$lookupステージに合体させることができます。これにより、大きな中間ドキュメントの作成を回避できます。

    16MBのBSON制限を超える「関連する」ドキュメントを作成することにより、サーバーにストレスを与えるリストで最もよく示されます。 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();
      }
    
    })();
    

    いくつかの初期データを挿入した後、リストは$lookupのみで構成される集計を実行しようとします。 これは次のエラーで失敗します:

    {MongoError:エッジマッチングパイプライン内のドキュメントの合計サイズ{$ match:{$ and:[{gid:{$ eq:1}}、{}]}}が最大ドキュメントサイズを超えています

    これは基本的に、取得時にBSONの制限を超えたことを示しています。

    対照的に、次の試行では$unwindが追加されます および$match パイプラインステージ

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

    もちろん、その結果は成功します。これは、結果が親ドキュメントに配置されなくなったため、BSONの制限を超えることができないためです。

    これは、実際には$unwindを追加した結果として発生します。 のみですが、$match たとえば、これがまたであることを示すために追加されます $lookupに追加されました ステージであり、全体的な効果は、効果的な方法で返される結果を「制限」することです。これは、すべてその$lookupで行われるためです。 操作と、それらの一致以外の結果は実際には返されません。

    このように構築することで、BSONの制限を超える「参照データ」をクエリし、$groupが必要な場合にクエリを実行できます。 $lookupによって実際に実行されている「非表示のクエリ」によって効果的にフィルタリングされると、結果は配列形式に戻ります。 。

    MongoDB3.6以降-「LEFTJOIN」の追加

    上記のすべてのコンテンツが指摘しているように、BSON制限は「ハード」です。 違反できないことを制限します。これが一般的に$unwindの理由です。 暫定的なステップとして必要です。ただし、$unwindにより、「LEFTJOIN」が「INNERJOIN」になるという制限があります。 コンテンツを保存できない場合。また、preserveNulAndEmptyArrays 「合体」を無効にし、そのままのアレイを残して、同じBSON制限の問題を引き起こします。

    MongoDB 3.6は、$lookupに新しい構文を追加します これにより、「ローカル」キーと「外部」キーの代わりに「サブパイプライン」式を使用できます。したがって、示されているように「合体」オプションを使用する代わりに、生成された配列も制限に違反しない限り、配列を「無傷」に戻す条件をパイプラインに入れることができます。 「LEFTJOIN」の

    その場合、新しい式は次のようになります。

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

    実際、これは基本的にMongoDBが「裏で」行っていることです 3.6は$exprを使用するため、以前の構文で ステートメントを構築するために「内部的に」。もちろん違いは、"unwinding"がないことです。 $lookupに存在するオプション 実際に実行されます。

    "pipeline"の結果として実際にドキュメントが作成されていない場合 式の場合、「LEFT JOIN」が実際に行うのと同じように、マスタードキュメント内のターゲット配列は実際には空になり、$lookupの通常の動作になります。 他のオプションなし。

    ただし、出力配列を作成するドキュメントがBSON制限を超えないようにする必要があります 。したがって、実際に$unwindを使用しない限り、条件による「一致する」コンテンツがこの制限内にとどまるか、同じエラーが続くかを確認するのは、実際にはあなた次第です。 「INNERJOIN」を実行します。



    1. socket.io-redisの使用例

    2. MongoDB:インデックスの順序とクエリの順序は一致する必要がありますか?

    3. クラスターを使用してSocket.IOを複数のNode.jsプロセスにスケーリングする

    4. docker-compose:コンテナ間の接続は拒否されましたが、ホストからサービスにアクセスできます