Sau một số thử nghiệm, tôi thấy rằng có thể xây dựng hàm xếp hạng dựa trên MapReduce, giả sử tập kết quả có thể vừa với kích thước tài liệu tối đa.
Ví dụ: giả sử tôi có một bộ sưu tập như thế này:
{ player: "joe", points: 1000, foo: 10, bar: 20, bang: "some text" }
{ player: "susan", points: 2000, foo: 10, bar: 20, bang: "some text" }
{ player: "joe", points: 1500, foo: 10, bar: 20, bang: "some text" }
{ player: "ben", points: 500, foo: 10, bar: 20, bang: "some text" }
...
Tôi có thể thực hiện tương đương thô của DENSE_RANK như vậy:
var m = function() {
++g_counter;
if ((this.player == "joe") && (g_scores.length != g_fake_limit)) {
g_scores.push({
player: this.player,
points: this.points,
foo: this.foo,
bar: this.bar,
bang: this.bang,
rank: g_counter
});
}
if (g_counter == g_final)
{
emit(this._id, g_counter);
}
}}
var r = function (k, v) { }
var f = function(k, v) { return g_scores; }
var test_mapreduce = function (limit) {
var total_scores = db.scores.count();
return db.scores.mapReduce(m, r, {
out: { inline: 1 },
sort: { points: -1 },
finalize: f,
limit: total_scores,
verbose: true,
scope: {
g_counter: 0,
g_final: total_scores,
g_fake_limit: limit,
g_scores:[]
}
}).results[0].value;
}
Để so sánh, đây là cách tiếp cận "ngây thơ" được đề cập ở nơi khác:
var test_naive = function(limit) {
var cursor = db.scores.find({player: "joe"}).limit(limit).sort({points: -1});
var scores = [];
cursor.forEach(function(score) {
score.rank = db.scores.count({points: {"$gt": score.points}}) + 1;
scores.push(score);
});
return scores;
}
Tôi đã điểm chuẩn cả hai cách tiếp cận trên một phiên bản MongoDB 1.8.2 bằng mã sau:
var rand = function(max) {
return Math.floor(Math.random() * max);
}
var create_score = function() {
var names = ["joe", "ben", "susan", "kevin", "lucy"]
return { player: names[rand(names.length)], points: rand(1000000), foo: 10, bar: 20, bang: "some kind of example text"};
}
var init_collection = function(total_records) {
db.scores.drop();
for (var i = 0; i != total_records; ++i) {
db.scores.insert(create_score());
}
db.scores.createIndex({points: -1})
}
var benchmark = function(test, count, limit) {
init_collection(count);
var durations = [];
for (var i = 0; i != 5; ++i) {
var start = new Date;
result = test(limit)
var stop = new Date;
durations.push(stop - start);
}
db.scores.drop();
return durations;
}
Mặc dù MapReduce nhanh hơn tôi mong đợi, cách tiếp cận ngây thơ đã thổi nó ra khỏi nước với kích thước bộ sưu tập lớn hơn, đặc biệt là khi bộ đệm được làm nóng:
> benchmark(test_naive, 1000, 50);
[ 22, 16, 17, 16, 17 ]
> benchmark(test_mapreduce, 1000, 50);
[ 16, 15, 14, 11, 14 ]
>
> benchmark(test_naive, 10000, 50);
[ 56, 16, 17, 16, 17 ]
> benchmark(test_mapreduce, 10000, 50);
[ 154, 109, 116, 109, 109 ]
>
> benchmark(test_naive, 100000, 50);
[ 492, 15, 18, 17, 16 ]
> benchmark(test_mapreduce, 100000, 50);
[ 1595, 1071, 1099, 1108, 1070 ]
>
> benchmark(test_naive, 1000000, 50);
[ 6600, 16, 15, 16, 24 ]
> benchmark(test_mapreduce, 1000000, 50);
[ 17405, 10725, 10768, 10779, 11113 ]
Vì vậy, hiện tại, có vẻ như cách tiếp cận ngây thơ là hướng đi, mặc dù tôi sẽ quan tâm xem liệu câu chuyện có thay đổi vào cuối năm nay hay không khi nhóm MongoDB tiếp tục cải thiện hiệu suất MapReduce.