コメントで前述したように、$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」を実行します。