42 votes

MongoDB: Terrible MapReduce Performance

J'ai une longue histoire avec les bases de données relationnelles, mais je suis nouveau sur MongoDB et MapReduce, donc, je suis presque positif, je dois être en train de faire quelque chose de mal. Je vais sauter à droite dans la question. Désolé si c'est long.

J'ai une table de base de données dans MySQL qui suit le nombre de membre vues de profil pour chaque jour. Pour les tests il a 10 000 000 de lignes.

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;

De données typique pourrait ressembler à ceci.

+--------+----------+------------+------+
| 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 |
+--------+----------+------------+------+

J'ai utiliser cette requête pour obtenir le top 5 des plus consultés de profils depuis 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

Cette requête se termine en moins d'une minute. Pas mal!

Maintenant, le déplacement sur le monde de MongoDB. - Je configurer un environnement fragmenté à l'aide de 3 serveurs. Serveurs de M, S1 et S2. J'ai utilisé les commandes suivantes pour définir la plate-forme jusqu' (Note: j'ai caché la propriété intellectuelle addys).

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

Une fois ceux-ci étaient en place, j'ai sauté sur le serveur M, et a lancé mongo. J'ai lancé les commandes suivantes:

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

J'ai ensuite importé le même 10 000 000 de lignes de MySQL, ce qui m'a donné des documents qui ressemblent à ceci:

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

Maintenant vient la vraie viande et les pommes de terre ici... Ma carte et de réduire les fonctions. De retour sur le serveur M dans la coquille j'ai d'installation de la requête et de l'exécuter comme ça.

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

Et d'ici l'été j'ai des problèmes. Cette requête a pris plus de 15 minutes! La requête MySQL a pris moins d'une minute. Voici le résultat:

{
        "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
        },
}

Non seulement at-il jamais de courir, mais les résultats ne semblent même pas être correcte.

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 }

Je sais que ces numéros de valeur devrait être beaucoup plus élevé.

Ma compréhension de l'ensemble du paradigme MapReduce est la tâche de l'exécution de cette requête doivent être répartis entre tous éclat membres, ce qui devrait augmenter les performances. J'ai attendu jusqu'à Mongo a été fait distribuer les documents entre les deux tesson serveurs après l'importation. Chacun avait presque exactement de 5 000 000 de documents lorsque j'ai commencé cette requête.

Donc, je dois être en train de faire quelque chose de mal. Quelqu'un peut-il me donner des pointeurs?

Edit: Quelqu'un sur IRC évoqué l'ajout d'un index sur le champ du jour, mais aussi loin que je peux dire que c'était fait automatiquement par MongoDB.

53voto

動靜能量 Points 33008

extraits de MongoDB Definitive Guide de O'Reilly:

Le coût d'utilisation de MapReduce est la rapidité: le groupe n'est pas particulièrement rapide, mais MapReduce est plus lent et n'est pas censé être utilisé en "temps réel". Vous exécutez MapReduce en tant que tâche d’arrière-plan, une collection de résultats est créée, puis vous pouvez interroger cette collection en temps réel.

 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. 
 

27voto

FrameGrace Points 239

Je suis peut-être trop tard, mais...

Tout d'abord, vous interrogez la collection de remplir le MapReduce sans index. Il est recommandé de créer un index sur la "journée".

MongoDB MapReduce est mono-thread sur un seul serveur, mais parallelizes sur les fragments. Les données de mongo fragments sont conservés dans contiguë morceaux triés par fragmentation de la clé.

Que votre clé de sharding "jour", et vous vous interrogez sur elle, vous êtes probablement en utilisant l'un de vos trois serveurs. La fragmentation touche n'est utilisée pour répartir les données. Carte de Réduire vont de la requête à l'aide de la "journée" index sur chaque fragment, et sera très rapide.

Ajouter quelque chose en face de la journée touche à répartir les données. Le nom d'utilisateur peut être un bon choix.

De cette façon, la Carte de réduire sera lancé sur tous les serveurs, et de réduire ainsi le temps de trois.

Quelque chose comme ceci:

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

Je pense qu'avec ces ajouts, vous pouvez faire correspondre MySQL vitesse encore plus rapide.

Aussi, mieux ne pas l'utiliser en temps réel. Si vos données n'ont pas besoin d'être "finement" précises, un plan, une carte de réduire la tâche de chaque maintenant et puis utiliser le résultat de la collecte.

6voto

mids Points 326

Tu ne fais rien de mal. (En plus de trier sur la valeur faux comme vous l'avez déjà remarqué dans votre commentaire.)

MongoDB map/reduce performance n'est tout simplement pas grande. C'est un problème connu; voir, par exemple http://jira.mongodb.org/browse/SERVER-1197 d'où une approche naïve est ~350x plus vite que les M/R.

Un avantage est que vous pouvez spécifier une sortie permanente nom de la collection avec l' out argument de l' mapReduce appel. Une fois le M/R est terminé, le temporaire collection sera renommé le nom définitif de atomiquement. De cette façon, vous pouvez planifier votre mise à jour des statistiques et de la requête de la M/R sortie de la collection en temps réel.

0voto

Rogerio Hilbert Points 51

Avez-vous déjà essayé d'utiliser le connecteur hadoop pour mongodb?

Regardez ce lien ici: http://docs.mongodb.org/ecosystem/tutorial/getting-started-with-hadoop/

Puisque vous utilisez seulement 3 fragments, je ne sais pas si cette approche améliorerait votre cas.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X