Xử lý hiện tại là mapReduce
Nếu bạn cần thực hiện điều này trên máy chủ và sắp xếp các kết quả hàng đầu và chỉ giữ lại 100 đầu, thì bạn có thể sử dụng mapReduce cho việc này như sau:
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 } }
)
Vì vậy, hàm ánh xạ thực hiện tính toán và xuất mọi thứ theo cùng một khóa để tất cả kết quả được gửi đến bộ giảm. Đầu ra cuối sẽ được chứa trong một mảng trong một tài liệu đầu ra duy nhất, vì vậy điều quan trọng là tất cả các kết quả đều được phát ra với cùng một giá trị khóa và đầu ra của mỗi lần phát chính nó là một mảng để mapReduce có thể hoạt động bình thường.
Việc phân loại và giảm thiểu được thực hiện trong chính bộ giảm tải, vì mỗi tài liệu được phát ra được kiểm tra, các phần tử được đưa vào một mảng tạm thời duy nhất, được sắp xếp và kết quả hàng đầu được trả về.
Đó là điều quan trọng và chỉ là lý do tại sao bộ phát tạo ra điều này dưới dạng một mảng ngay cả khi ban đầu là một phần tử duy nhất. MapReduce hoạt động bằng cách xử lý kết quả theo "khối", vì vậy, ngay cả khi tất cả các tài liệu được phát ra có cùng một khóa, chúng không được xử lý cùng một lúc. Thay vào đó, trình giảm bớt đặt các kết quả của nó trở lại hàng đợi các kết quả đã phát ra để giảm bớt cho đến khi chỉ còn một tài liệu duy nhất cho khóa cụ thể đó.
Tôi đang giới hạn đầu ra "lát cắt" ở đây là 10 để liệt kê ngắn gọn và bao gồm các số liệu thống kê để tạo điểm nhấn, vì có thể thấy 100 chu kỳ giảm được gọi trên mẫu 10000 này:
{
"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
}
Vì vậy, đây là một đầu ra tài liệu duy nhất, ở định dạng mapReduce cụ thể, trong đó "giá trị" chứa một phần tử là một mảng của kết quả được sắp xếp và giới hạn.
Xử lý trong tương lai là Tổng hợp
Theo văn bản, bản phát hành ổn định mới nhất hiện tại của MongoDB là 3.0 và bản này thiếu chức năng để giúp bạn có thể thực hiện được. Nhưng bản phát hành 3.2 sắp tới giới thiệu các toán tử mới giúp điều này trở nên khả thi:
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 }
])
Cũng giới hạn trong 10 kết quả giống nhau cho ngắn gọn, bạn sẽ nhận được kết quả như thế này:
{ "_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 }
Điều này có thể thực hiện được phần lớn do $ unwind
được sửa đổi để chiếu một trường trong kết quả có chứa chỉ mục mảng và cũng do $ arrayElemAt
là một toán tử mới có thể trích xuất một phần tử mảng dưới dạng giá trị số ít từ một chỉ mục được cung cấp.
Điều này cho phép "tra cứu" các giá trị theo vị trí chỉ mục từ mảng đầu vào của bạn để áp dụng phép toán cho từng phần tử. Mảng đầu vào được hỗ trợ bởi $ đen
toán tử so $ arrayElemAt
không phàn nàn và thu thập lại nó dưới dạng một mảng, (có vẻ là một lỗi nhỏ hiện tại, vì các hàm mảng khác không gặp vấn đề với đầu vào trực tiếp) và nhận giá trị chỉ mục phù hợp bằng cách sử dụng trường "chỉ mục" được tạo bởi $ thư giãn
để so sánh.
Phép toán được thực hiện bởi $ subtract
và tất nhiên là một toán tử mới khác trong $ abs
để đáp ứng chức năng của bạn. Ngoài ra, vì cần phải giải phóng mảng ngay từ đầu, tất cả điều này được thực hiện bên trong $ group
giai đoạn tích lũy tất cả các thành viên mảng trên mỗi tài liệu và áp dụng thêm các mục nhập qua $ sum
bộ tích lũy.
Cuối cùng, tất cả các tài liệu kết quả được xử lý với $ sort
và sau đó là giới hạn $
được áp dụng để chỉ trả về kết quả hàng đầu.
Tóm tắt
Ngay cả với chức năng mới sắp được sử dụng cho khung tổng hợp cho MongoDB, vẫn còn tranh cãi về cách tiếp cận nào thực sự hiệu quả hơn cho kết quả. Điều này phần lớn là do vẫn cần $ thư giãn
nội dung mảng, tạo ra một bản sao hiệu quả của mỗi tài liệu cho mỗi thành viên mảng trong đường dẫn được xử lý và điều đó thường gây ra chi phí.
Vì vậy, mặc dù mapReduce là cách duy nhất hiện tại để thực hiện điều này cho đến khi có bản phát hành mới, nhưng nó thực sự có thể hoạt động tốt hơn câu lệnh tổng hợp tùy thuộc vào lượng dữ liệu được xử lý và mặc dù thực tế là khung tổng hợp hoạt động trên các toán tử được mã hóa gốc chứ không phải JavaScript được dịch hoạt động.
Như với tất cả mọi thứ, bạn luôn nên thử nghiệm để xem trường hợp nào phù hợp với mục đích của bạn hơn và trường hợp nào mang lại hiệu suất tốt nhất cho quá trình xử lý dự kiến của bạn.
Mẫu
Tất nhiên, kết quả mong đợi cho tài liệu mẫu được cung cấp trong câu hỏi là 0.9
bằng toán học được áp dụng. Nhưng chỉ cho mục đích thử nghiệm của tôi, đây là danh sách ngắn được sử dụng để tạo một số dữ liệu mẫu mà tôi muốn ít nhất là xác minh mã mapReduce đang hoạt động như bình thường:
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();
}
}
Các mảng là các giá trị dấu thập phân hoàn toàn ngẫu nhiên, vì vậy không có nhiều phân phối trong các kết quả được liệt kê mà tôi đã đưa ra dưới dạng đầu ra mẫu.