MongoDB: ужасная производительность MapReduce - PullRequest
42 голосов
/ 16 октября 2010

У меня долгая история с реляционными базами данных, но я новичок в MongoDB и MapReduce, так что я почти уверен, что что-то не так делаю. Я прыгну прямо в вопрос. Извините, если это долго.

У меня есть таблица базы данных в MySQL, которая отслеживает количество просмотров профиля участника за каждый день. Для тестирования у него 10 000 000 строк.

CREATE TABLE `profile_views` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `username` varchar(20) NOT NULL,
  `day` date NOT NULL,
  `views` int(10) unsigned default '0',
  PRIMARY KEY  (`id`),
  UNIQUE KEY `username` (`username`,`day`),
  KEY `day` (`day`)
) ENGINE=InnoDB;

Типичные данные могут выглядеть следующим образом.

+--------+----------+------------+------+
| id     | username | day        | hits |
+--------+----------+------------+------+
| 650001 | Joe      | 2010-07-10 |    1 |
| 650002 | Jane     | 2010-07-10 |    2 |
| 650003 | Jack     | 2010-07-10 |    3 |
| 650004 | Jerry    | 2010-07-10 |    4 |
+--------+----------+------------+------+

Я использую этот запрос, чтобы получить топ-5 самых просматриваемых профилей с 2010-07-16.

SELECT username, SUM(hits)
FROM profile_views
WHERE day > '2010-07-16'
GROUP BY username
ORDER BY hits DESC
LIMIT 5\G

Этот запрос завершается менее чем за минуту. Неплохо!

Теперь переходим в мир MongoDB. Я установил защищенную среду, используя 3 сервера. Серверы M, S1 и S2. Я использовал следующие команды для настройки буровой установки (Примечание: я скрыл IP-адреса).

S1 => 127.20.90.1
./mongod --fork --shardsvr --port 10000 --dbpath=/data/db --logpath=/data/log

S2 => 127.20.90.7
./mongod --fork --shardsvr --port 10000 --dbpath=/data/db --logpath=/data/log

M => 127.20.4.1
./mongod --fork --configsvr --dbpath=/data/db --logpath=/data/log
./mongos --fork --configdb 127.20.4.1 --chunkSize 1 --logpath=/data/slog

Как только они были запущены, я запрыгнул на сервер M и запустил mongo. Я выполнил следующие команды:

use admin
db.runCommand( { addshard : "127.20.90.1:10000", name: "M1" } );
db.runCommand( { addshard : "127.20.90.7:10000", name: "M2" } );
db.runCommand( { enablesharding : "profiles" } );
db.runCommand( { shardcollection : "profiles.views", key : {day : 1} } );
use profiles
db.views.ensureIndex({ hits: -1 });

Затем я импортировал те же 10000000 строк из MySQL, что дало мне документы, которые выглядят так:

{
    "_id" : ObjectId("4cb8fc285582125055295600"),
    "username" : "Joe",
    "day" : "Fri May 21 2010 00:00:00 GMT-0400 (EDT)",
    "hits" : 16
}

Теперь пришло настоящее мясо и картошка ... Моя карта и функции снижения. Вернувшись на сервер M в оболочке, я настроил запрос и выполнил его следующим образом.

use profiles;
var start = new Date(2010, 7, 16);
var map = function() {
    emit(this.username, this.hits);
}
var reduce = function(key, values) {
    var sum = 0;
    for(var i in values) sum += values[i];
    return sum;
}
res = db.views.mapReduce(
    map,
    reduce,
    {
        query : { day: { $gt: start }}
    }
);

И вот тут я столкнулся с проблемами. Этот запрос занял более 15 минут! Запрос MySQL занял меньше минуты. Вот вывод:

{
        "result" : "tmp.mr.mapreduce_1287207199_6",
        "shardCounts" : {
                "127.20.90.7:10000" : {
                        "input" : 4917653,
                        "emit" : 4917653,
                        "output" : 1105648
                },
                "127.20.90.1:10000" : {
                        "input" : 5082347,
                        "emit" : 5082347,
                        "output" : 1150547
                }
        },
        "counts" : {
                "emit" : NumberLong(10000000),
                "input" : NumberLong(10000000),
                "output" : NumberLong(2256195)
        },
        "ok" : 1,
        "timeMillis" : 811207,
        "timing" : {
                "shards" : 651467,
                "final" : 159740
        },
}

Мало того, что это заняло целую вечность, но результаты даже не кажутся правильными.

db[res.result].find().sort({ hits: -1 }).limit(5);
{ "_id" : "Joe", "value" : 128 }
{ "_id" : "Jane", "value" : 2 }
{ "_id" : "Jerry", "value" : 2 }
{ "_id" : "Jack", "value" : 2 }
{ "_id" : "Jessy", "value" : 3 }

Я знаю, что эти значения должны быть намного выше.

Мое понимание всей парадигмы MapReduce заключается в том, что задача выполнения этого запроса должна быть разделена между всеми элементами шарда, что должно повысить производительность. Я ждал, пока Монго не закончит распределение документов между двумя серверами шарда после импорта. У каждого было почти ровно 5 000 000 документов, когда я начал этот запрос.

Так что я, должно быть, делаю что-то не так. Кто-нибудь может дать мне какие-нибудь указатели?

Редактировать: Кто-то из IRC упоминал о добавлении индекса в поле дня, но, насколько я могу судить, это было сделано автоматически MongoDB.

Ответы [ 4 ]

53 голосов
/ 17 октября 2010

выдержки из MongoDB Definitive Guide от O'Reilly:

Цена использования MapReduce - скорость: группа не особенно быстра, но MapReduce медленнее и не должна использоваться в «реальномвремя ». Вы запускаете MapReduce в качестве фонового задания, оно создает коллекцию результатов, а затем вы можете запросить эту коллекцию в режиме реального времени.

options for map/reduce:

"keeptemp" : boolean 
If the temporary result collection should be saved when the connection is closed. 

"output" : string 
Name for the output collection. Setting this option implies keeptemp : true. 
27 голосов
/ 01 декабря 2010

Может быть, я опоздал, но ...

Сначала вы запрашиваете коллекцию для заполнения MapReduce без индекса. Вы должны создать индекс на «день».

MongoDB MapReduce является однопоточным на одном сервере, но распараллеливается на шардах. Данные в осколках монго хранятся в виде непрерывных кусков, отсортированных по ключу осколка.

Поскольку ваш ключ шардинга - "день", и вы запрашиваете его, вы, вероятно, используете только один из трех своих серверов. Ключ Sharding используется только для распространения данных. Map Reduce будет выполнять запрос с использованием индекса "day" для каждого шарда и будет очень быстрым.

Добавьте что-нибудь перед ключом дня, чтобы распространить данные. Имя пользователя может быть хорошим выбором.

Таким образом, уменьшение карт будет запущено на всех серверах и, как мы надеемся, сократит время на три.

Примерно так:

use admin
db.runCommand( { addshard : "127.20.90.1:10000", name: "M1" } );
db.runCommand( { addshard : "127.20.90.7:10000", name: "M2" } );
db.runCommand( { enablesharding : "profiles" } );
db.runCommand( { shardcollection : "profiles.views", key : {username : 1,day: 1} } );
use profiles
db.views.ensureIndex({ hits: -1 });
db.views.ensureIndex({ day: -1 });

Я думаю, что с этими дополнениями вы можете сопоставить скорость MySQL, даже быстрее.

Кроме того, лучше не используйте его в режиме реального времени. Если ваши данные не обязательно должны быть «точными», планируйте задачу сокращения карт каждый раз и используйте коллекцию результатов.

6 голосов
/ 16 октября 2010

Вы не делаете ничего плохого.(Помимо сортировки по неправильному значению, как вы уже заметили в своих комментариях.)

MongoDB отображает / снижает производительность, просто не так уж велика.Это известная проблема;см., например, http://jira.mongodb.org/browse/SERVER-1197, где наивный подход в ~ 350 раз быстрее, чем M / R.

Одно из преимуществ заключается в том, что вы можете указать постоянное имя выходной коллекции с аргументом out mapReduce звонок.После завершения M / R временная коллекция будет переименована в постоянное имя атомарно.Таким образом, вы можете планировать обновления статистики и запрашивать коллекцию выходных данных M / R в режиме реального времени.

0 голосов
/ 17 февраля 2014

Вы уже пробовали использовать коннектор hadoop для mongodb?

Посмотрите на эту ссылку здесь: http://docs.mongodb.org/ecosystem/tutorial/getting-started-with-hadoop/

Поскольку вы используете только 3 осколка, я не знаю, улучшит ли этот подход ваш случай.

...