Tengo una larga historia con bases de datos relacionales, pero soy nuevo en MongoDB y MapReduce, así que estoy casi seguro de que debo estar haciendo algo mal. Iré directo a la pregunta. Lo siento si es largo.MongoDB: Terrible rendimiento de MapReduce
Tengo una tabla de base de datos en MySQL que rastrea el número de vistas de perfil de miembro para cada día. Para la prueba tiene 10,000,000 filas.
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;
Los datos típicos pueden verse así.
+--------+----------+------------+------+
| 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 |
+--------+----------+------------+------+
Utilizo esta consulta para obtener los 5 perfiles más vistos desde 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
Esta consulta se completa en menos de un minuto. ¡No está mal!
Ahora avanzando hacia el mundo de MongoDB. Configuré un entorno fragmentado usando 3 servidores. Servidores M, S1 y S2. Usé los siguientes comandos para configurar el equipo (Nota: he oscurecido los dispositivos adicionales de 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
Una vez que estaban en funcionamiento, salté al servidor M y lancé mongo. Emití los siguientes comandos:
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 });
entonces me importan las mismas 10.000.000 filas de MySQL, que me dio los documentos que se ven así:
{
"_id" : ObjectId("4cb8fc285582125055295600"),
"username" : "Joe",
"day" : "Fri May 21 2010 00:00:00 GMT-0400 (EDT)",
"hits" : 16
}
Ahora viene la verdadera carne y patatas aquí ... Mi mapa y reducir funciones. De vuelta en el servidor M en el shell configuro la consulta y la ejecuto así.
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 }}
}
);
Y aquí estaba me encontré con problemas. ¡Esta consulta tomó más de 15 minutos en completarse! La consulta de MySQL tomó menos de un minuto. Aquí está la salida:
{
"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
},
}
No sólo le tomó por siempre a correr, pero los resultados ni siquiera parecen ser correctas.
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 }
Sé que los números de valores deberían ser mucho más altos.
Mi comprensión de todo el paradigma de MapReduce es que la tarea de realizar esta consulta debe dividirse entre todos los miembros del shard, lo que debería aumentar el rendimiento. Esperé a que Mongo terminara de distribuir los documentos entre los dos servidores de fragmentos después de la importación. Cada uno tenía casi exactamente 5,000,000 de documentos cuando comencé esta consulta.
Así que debo estar haciendo algo mal. ¿Alguien puede darme algún consejo?
Editar: Alguien en el IRC mencionó la adición de un índice en el campo del día, pero hasta donde puedo decir que MongoDB lo hizo automáticamente.
Gah .. Acabo de darme cuenta de una razón por la cual los resultados son incorrectos. Debería haber estado clasificando en "valor" en lugar de "hits". – mellowsoon
Un problema es que cuando importa sus datos en Mongo, el valor 'día' es una cadena gigante, pero en mysql, es una fecha (número entero).Cuando pones tus datos en mongo, asegúrate de almacenarlos como un tipo de fecha. – Clint
también puede separar el campo de fecha y hora, y almacenar la fecha como la cadena "20110101" o entero 20110101 y el índice basado en la fecha –