जैसा कि पहले टिप्पणी में कहा गया है, त्रुटि इसलिए होती है क्योंकि $lookup
. करते समय जो डिफ़ॉल्ट रूप से विदेशी संग्रह के परिणामों से मूल दस्तावेज़ के भीतर एक लक्ष्य "सरणी" उत्पन्न करता है, उस सरणी के लिए चुने गए दस्तावेज़ों का कुल आकार माता-पिता को 16 एमबी बीएसओएन सीमा से अधिक होने का कारण बनता है।
इसके लिए काउंटर एक $unwind
. के साथ प्रोसेस करना है जो तुरंत $lookup
. का अनुसरण करता है पाइपलाइन चरण। यह वास्तव में $lookup
. के व्यवहार को बदल देता है ऐसे में माता-पिता में एक सरणी बनाने के बजाय, परिणाम मिलान किए गए प्रत्येक दस्तावेज़ के लिए प्रत्येक माता-पिता की "प्रतिलिपि" होते हैं।
लगभग $unwind
. के नियमित उपयोग की तरह ही , इस अपवाद के साथ कि "अलग" पाइपलाइन चरण के रूप में संसाधित करने के बजाय, unwinding
कार्रवाई वास्तव में $lookup
. में जोड़ी जाती है पाइपलाइन संचालन स्वयं। आदर्श रूप से आप $unwind
. का भी पालन करते हैं एक $match
. के साथ शर्त, जो एक matching
भी बनाती है $lookup
. में भी जोड़ा जाने वाला तर्क . आप वास्तव में इसे explain
. में देख सकते हैं पाइपलाइन के लिए आउटपुट।
विषय वास्तव में मुख्य दस्तावेज़ीकरण में एकत्रीकरण पाइपलाइन अनुकूलन के एक खंड में (संक्षेप में) कवर किया गया है:
<ब्लॉकक्वॉट>$लुकअप + $अनविंड सहसंयोजन
संस्करण 3.2 में नया।
जब एक $अनविंड तुरंत एक और $लुकअप का अनुसरण करता है, और $अनविंड $लुकअप के as फ़ील्ड पर संचालित होता है, तो ऑप्टिमाइज़र $अनइंड को $लुकअप चरण में मिला सकता है। यह बड़े मध्यवर्ती दस्तावेज़ बनाने से बचता है।
एक सूची के साथ सबसे अच्छा प्रदर्शन किया गया जो 16 एमबी बीएसओएन सीमा से अधिक "संबंधित" दस्तावेज़ बनाकर सर्वर को तनाव में रखता है। बीएसओएन सीमा को तोड़ने और उसके आसपास काम करने के लिए यथासंभव संक्षिप्त रूप से किया गया:
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
पाइपलाइन चरण
व्याख्या आउटपुट :
{
"$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"
}
}
}
और वह परिणाम निश्चित रूप से सफल होता है, क्योंकि परिणाम अब मूल दस्तावेज़ में नहीं रखे जा रहे हैं तो बीएसओएन सीमा को पार नहीं किया जा सकता है।
यह वास्तव में $unwind
. जोड़ने के परिणामस्वरूप होता है केवल, लेकिन $match
उदाहरण के लिए यह दिखाने के लिए जोड़ा जाता है कि यह भी . है $lookup
. में जोड़ा गया चरण और यह कि समग्र प्रभाव एक प्रभावी तरीके से लौटाए गए परिणामों को "सीमित" करना है, क्योंकि यह सब उस $lookup
में किया गया है संचालन और मिलान के अलावा कोई अन्य परिणाम वास्तव में वापस नहीं किया जाता है।
इस तरह से निर्माण करके आप "संदर्भित डेटा" के लिए क्वेरी कर सकते हैं जो बीएसओएन सीमा से अधिक होगा और फिर यदि आप $group
चाहते हैं एक बार जब वे "छिपी हुई क्वेरी" द्वारा प्रभावी रूप से फ़िल्टर कर दिए जाते हैं, जो वास्तव में $lookup
द्वारा निष्पादित किया जा रहा है, तो परिणाम एक सरणी प्रारूप में वापस आ जाते हैं। ।
MongoDB 3.6 और इसके बाद के संस्करण - "LEFT JOIN" के लिए अतिरिक्त
जैसा कि उपरोक्त सभी सामग्री नोट करती है, बीएसओएन सीमा एक "कठिन" . है सीमा जिसे आप भंग नहीं कर सकते हैं और यही कारण है कि आमतौर पर $unwind
एक अंतरिम कदम के रूप में आवश्यक है। हालांकि एक सीमा है कि $unwind
के आधार पर "LEFT JOIN" एक "INNER JOIN" बन जाता है। जहां यह सामग्री को संरक्षित नहीं कर सकता है। साथ ही preserveNulAndEmptyArrays
. भी "सहसंयोजन" को नकार देगा और अभी भी बरकरार सरणी को छोड़ देगा, जिससे समान BSON सीमा समस्या हो सकती है।
MongoDB 3.6 $lookup
. में नया सिंटैक्स जोड़ता है जो "स्थानीय" और "विदेशी" कुंजियों के स्थान पर "उप-पाइपलाइन" अभिव्यक्ति का उपयोग करने की अनुमति देता है। तो प्रदर्शन के रूप में "सहसंयोजन" विकल्प का उपयोग करने के बजाय, जब तक उत्पादित सरणी सीमा का उल्लंघन नहीं करती है, उस पाइपलाइन में शर्तों को रखना संभव है जो सरणी "बरकरार" देता है, और संभवतः बिना किसी मिलान के संकेतक होगा "लेफ्ट जॉइन" का।
तब नई अभिव्यक्ति होगी:
{ "$lookup": {
"from": "edge",
"let": { "gid": "$gid" },
"pipeline": [
{ "$match": {
"_id": { "$gte": 1, "$lte": 5 },
"$expr": { "$eq": [ "$$gid", "$to" ] }
}}
],
"as": "from"
}}
वास्तव में यह मूल रूप से वही होगा जो मोंगोडीबी कर रहा है "कवर के तहत" पिछले सिंटैक्स के साथ चूंकि 3.6 $expr
. का उपयोग करता है बयान बनाने के लिए "आंतरिक रूप से"। बेशक अंतर यह है कि कोई "unwinding"
नहीं है विकल्प मौजूद है कि कैसे $lookup
वास्तव में निष्पादित हो जाता है।
यदि "pipeline"
. के परिणामस्वरूप वास्तव में कोई दस्तावेज़ प्रस्तुत नहीं किया जाता है अभिव्यक्ति, तो मास्टर दस्तावेज़ के भीतर लक्ष्य सरणी वास्तव में खाली होगी, जैसे "बाएं जॉइन" वास्तव में करता है और $lookup
का सामान्य व्यवहार होगा बिना किसी अन्य विकल्प के।
हालांकि आउटपुट सरणी उस दस्तावेज़ का कारण नहीं होना चाहिए जहां इसे बीएसओएन सीमा से अधिक बनाया जा रहा है . इसलिए यह सुनिश्चित करना वास्तव में आप पर निर्भर है कि शर्तों के अनुसार कोई भी "मिलान" सामग्री इस सीमा के अंतर्गत रहती है या वही त्रुटि बनी रहेगी, जब तक कि आप वास्तव में $unwind
का उपयोग नहीं करते हैं "इनर जॉइन" को प्रभावित करने के लिए।