Pemrosesan saat ini adalah mapReduce
Jika Anda perlu menjalankan ini di server dan mengurutkan hasil teratas dan hanya menyimpan 100 teratas, maka Anda dapat menggunakan mapReduce untuk ini seperti ini:
db.test.mapReduce(
function() {
var input = [0.1,0.3,0.4];
var value = Array.sum(this.vals.map(function(el,idx) {
return Math.abs( el - input[idx] )
}));
emit(null,{ "output": [{ "_id": this._id, "value": value }]});
},
function(key,values) {
var output = [];
values.forEach(function(value) {
value.output.forEach(function(item) {
output.push(item);
});
});
output.sort(function(a,b) {
return a.value < b.value;
});
return { "output": output.slice(0,100) };
},
{ "out": { "inline": 1 } }
)
Jadi fungsi mapper melakukan perhitungan dan output semuanya di bawah kunci yang sama sehingga semua hasil dikirim ke peredam. Keluaran akhir akan dimasukkan ke dalam larik dalam satu dokumen keluaran, jadi penting bahwa semua hasil dipancarkan dengan nilai kunci yang sama dan keluaran dari setiap pancaran itu sendiri adalah larik sehingga mapReduce dapat bekerja dengan baik.
Penyortiran dan pengurangan dilakukan dalam peredam itu sendiri, karena setiap dokumen yang dipancarkan diperiksa, elemen-elemennya dimasukkan ke dalam satu larik sementara, diurutkan, dan hasil teratas dikembalikan.
Itu penting, dan hanya alasan mengapa emitor menghasilkan ini sebagai array meskipun elemen tunggal pada awalnya. MapReduce bekerja dengan memproses hasil dalam "potongan", jadi meskipun semua dokumen yang dipancarkan memiliki kunci yang sama, tidak semuanya diproses sekaligus. Sebaliknya, peredam mengembalikan hasilnya ke dalam antrean hasil yang dipancarkan untuk dikurangi hingga hanya tersisa satu dokumen untuk kunci tertentu.
Saya membatasi output "slice" di sini menjadi 10 untuk singkatnya daftar, dan menyertakan statistik untuk menegaskan, karena 100 pengurangan siklus yang dipanggil pada sampel 10.000 ini dapat dilihat:
{
"results" : [
{
"_id" : null,
"value" : {
"output" : [
{
"_id" : ObjectId("56558d93138303848b496cd4"),
"value" : 2.2
},
{
"_id" : ObjectId("56558d96138303848b49906e"),
"value" : 2.2
},
{
"_id" : ObjectId("56558d93138303848b496d9a"),
"value" : 2.1
},
{
"_id" : ObjectId("56558d93138303848b496ef2"),
"value" : 2.1
},
{
"_id" : ObjectId("56558d94138303848b497861"),
"value" : 2.1
},
{
"_id" : ObjectId("56558d94138303848b497b58"),
"value" : 2.1
},
{
"_id" : ObjectId("56558d94138303848b497ba5"),
"value" : 2.1
},
{
"_id" : ObjectId("56558d94138303848b497c43"),
"value" : 2.1
},
{
"_id" : ObjectId("56558d95138303848b49842b"),
"value" : 2.1
},
{
"_id" : ObjectId("56558d96138303848b498db4"),
"value" : 2.1
}
]
}
}
],
"timeMillis" : 1758,
"counts" : {
"input" : 10000,
"emit" : 10000,
"reduce" : 100,
"output" : 1
},
"ok" : 1
}
Jadi ini adalah keluaran dokumen tunggal, dalam format mapReduce tertentu, di mana "nilai" berisi elemen yang merupakan larik dari hasil yang diurutkan dan dibatasi.
Pemrosesan di Masa Mendatang adalah Agregat
Pada saat penulisan, rilis stabil terbaru dari MongoDB adalah 3.0, dan ini tidak memiliki fungsionalitas untuk memungkinkan operasi Anda. Namun rilis 3.2 mendatang memperkenalkan operator baru yang memungkinkan hal ini:
db.test.aggregate([
{ "$unwind": { "path": "$vals", "includeArrayIndex": "index" }},
{ "$group": {
"_id": "$_id",
"result": {
"$sum": {
"$abs": {
"$subtract": [
"$vals",
{ "$arrayElemAt": [ { "$literal": [0.1,0.3,0.4] }, "$index" ] }
]
}
}
}
}},
{ "$sort": { "result": -1 } },
{ "$limit": 100 }
])
Juga membatasi 10 hasil yang sama untuk singkatnya, Anda mendapatkan output seperti ini:
{ "_id" : ObjectId("56558d96138303848b49906e"), "result" : 2.2 }
{ "_id" : ObjectId("56558d93138303848b496cd4"), "result" : 2.2 }
{ "_id" : ObjectId("56558d96138303848b498e31"), "result" : 2.1 }
{ "_id" : ObjectId("56558d94138303848b497c43"), "result" : 2.1 }
{ "_id" : ObjectId("56558d94138303848b497861"), "result" : 2.1 }
{ "_id" : ObjectId("56558d96138303848b499037"), "result" : 2.1 }
{ "_id" : ObjectId("56558d96138303848b498db4"), "result" : 2.1 }
{ "_id" : ObjectId("56558d93138303848b496ef2"), "result" : 2.1 }
{ "_id" : ObjectId("56558d93138303848b496d9a"), "result" : 2.1 }
{ "_id" : ObjectId("56558d96138303848b499182"), "result" : 2.1 }
Hal ini dimungkinkan sebagian besar karena $unwind
sedang dimodifikasi untuk memproyeksikan bidang dalam hasil yang berisi indeks larik, dan juga karena $arrayElemAt
yang merupakan operator baru yang dapat mengekstrak elemen array sebagai nilai tunggal dari indeks yang disediakan.
Ini memungkinkan "pencarian" nilai berdasarkan posisi indeks dari array input Anda untuk menerapkan matematika ke setiap elemen. Array input difasilitasi oleh $literal
operator jadi $arrayElemAt
tidak mengeluh dan mengenalinya sebagai array, (tampaknya menjadi bug kecil saat ini, karena fungsi array lainnya tidak memiliki masalah dengan input langsung) dan mendapatkan nilai indeks pencocokan yang sesuai dengan menggunakan bidang "indeks" yang dihasilkan oleh $unwind
untuk perbandingan.
Perhitungan dilakukan dengan $subtract
dan tentu saja operator baru lainnya di $abs
untuk memenuhi fungsionalitas Anda. Juga karena array perlu dilonggarkan, semua ini dilakukan di dalam $group
tahap mengumpulkan semua anggota larik per dokumen dan menerapkan penambahan entri melalui $jumlah
akumulator.
Akhirnya semua dokumen hasil diproses dengan $sort
lalu $limit
diterapkan hanya untuk mengembalikan hasil teratas.
Ringkasan
Bahkan dengan fungsionalitas baru yang akan tersedia untuk kerangka kerja agregasi untuk MongoDB, masih dapat diperdebatkan pendekatan mana yang sebenarnya lebih efisien untuk hasil. Ini sebagian besar karena masih ada kebutuhan untuk $unwind
konten larik, yang secara efektif menghasilkan salinan setiap dokumen per anggota larik dalam pipa untuk diproses, dan yang umumnya menyebabkan overhead.
Jadi sementara mapReduce adalah satu-satunya cara saat ini untuk melakukan ini hingga rilis baru, itu sebenarnya dapat mengungguli pernyataan agregasi tergantung pada jumlah data yang akan diproses, dan terlepas dari kenyataan bahwa kerangka kerja agregasi bekerja pada operator kode asli daripada JavaScript yang diterjemahkan operasi.
Seperti halnya semua hal, pengujian selalu disarankan untuk melihat kasing mana yang lebih sesuai dengan tujuan Anda dan mana yang memberikan kinerja terbaik untuk pemrosesan yang Anda harapkan.
Contoh
Tentu saja hasil yang diharapkan untuk contoh dokumen yang diberikan dalam pertanyaan adalah 0.9
dengan matematika yang diterapkan. Tetapi hanya untuk tujuan pengujian saya, berikut adalah daftar singkat yang digunakan untuk menghasilkan beberapa data sampel yang saya ingin setidaknya memverifikasi bahwa kode mapReduce berfungsi sebagaimana mestinya:
var bulk = db.test.initializeUnorderedBulkOp();
var x = 10000;
while ( x-- ) {
var vals = [0,0,0];
vals = vals.map(function(val) {
return Math.round((Math.random()*10),1)/10;
});
bulk.insert({ "vals": vals });
if ( x % 1000 == 0) {
bulk.execute();
bulk = db.test.initializeUnorderedBulkOp();
}
}
Array adalah nilai titik desimal tunggal yang benar-benar acak, jadi tidak ada banyak distribusi dalam daftar hasil yang saya berikan sebagai output sampel.