Подтвердить что ты не робот

MongoDB: Ужасная производительность MapReduce

У меня длинная история с реляционными базами данных, но я новичок в 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 и запустил монго. Я выпустил следующие команды:

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 });

Затем я импортировал те же 10 000 000 строк из 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 заключается в том, что задача выполнения этого запроса должна быть разделена между всеми членами shard, что должно повысить производительность. Я ждал, пока Монго не будет раздавать документы между двумя серверами осколков после импорта. У каждого из них было почти ровно 5 000 000 документов, когда я начал этот запрос.

Поэтому я должен делать что-то неправильно. Может ли кто-нибудь дать мне какие-либо указания?

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

4b9b3361

Ответ 1

выдержки из окончательного руководства MongoDB от 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. 

Ответ 2

Возможно, я опоздал, но...

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

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

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

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

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

Что-то вроде этого:

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 еще быстрее.

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

Ответ 3

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

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

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

Ответ 4

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

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

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