2010-10-16 2 views
42

Ho una lunga storia di database relazionali, ma sono nuovo di MongoDB e MapReduce, quindi sono quasi sicuro che devo fare qualcosa di sbagliato. Passerò direttamente alla domanda. Scusa se è lungo.MongoDB: Terribile prestazione MapReduce

Ho una tabella di database in MySQL che tiene traccia del numero di visualizzazioni del profilo membro per ogni giorno. Per il test ha 10.000.000 di righe.

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; 

I dati tipici potrebbero essere simili a questo.

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

Io uso questa query per ottenere i 5 profili più visti dal 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 

Questa query termina tra meno di un minuto. Non male!

Ora stiamo passando al mondo di MongoDB. Ho installato un ambiente più semplice usando 3 server. Server M, S1 e S2. Ho usato i seguenti comandi per impostare il rig up (Nota: ho oscurato gli addy 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 volta installati e funzionanti, ho saltato sul server M e ho lanciato mongo. Ho emesso i seguenti comandi:

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

Ho poi importato gli stessi 10.000.000 righe da MySQL, che mi ha dato i documenti che assomigliano a questo:

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

Ora arriva la vera carne e patate qui ... La mia mappa e ridurre le funzioni. Tornato sul server M nella shell, ho impostato la query e l'ho eseguita in questo modo.

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

Ed ecco che ho incontrato problemi. Questa richiesta ha richiesto oltre 15 minuti per essere completata! La query MySQL è durata meno di un minuto. Ecco l'output:

{ 
     "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 solo c'è voluto per sempre a correre, ma i risultati non hanno nemmeno sembrano essere corrette.

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 } 

So che quei numeri di valore dovrebbero essere molto più alti.

La mia comprensione dell'intero paradigma MapReduce è il compito di eseguire questa query deve essere diviso tra tutti i membri del frammento, che dovrebbe aumentare le prestazioni. Ho aspettato che Mongo avesse finito di distribuire i documenti tra i due server shard dopo l'importazione. Ognuno aveva quasi esattamente 5.000.000 di documenti quando ho iniziato questa query.

Quindi devo fare qualcosa di sbagliato. Qualcuno può darmi qualche suggerimento?

Modifica: qualcuno su IRC ha menzionato l'aggiunta di un indice nel campo del giorno, ma per quanto posso dire è stato fatto automaticamente da MongoDB.

+0

Gah .. Ho appena realizzato un motivo per cui i risultati non sono corretti. Avrei dovuto scegliere "valore" piuttosto che "colpi". – mellowsoon

+2

Un problema è che quando si importano i dati in Mongo, il valore "giorno" è una stringa gigante, ma in mysql è una data (numero intero).Quando metti i tuoi dati in mongo, assicurati di memorizzarli come tipo di data. – Clint

+0

potresti anche separare il campo data e ora e memorizzare la data come stringa "20110101" o intero 20110101 e indice in base alla data –

risposta

53

brani tratti da Guida MongoDB Definitive da O'Reilly:

Il prezzo di utilizzo di MapReduce è la velocità: gruppo non è particolarmente veloce, ma MapReduce è più lento e non dovrebbe essere utilizzato in “ Si esegue MapReduce come lavoro di background , si crea una raccolta di risultati e quindi è possibile interrogare la raccolta in tempo reale.

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

Penso di aver frainteso lo scopo di MapReduce. Ho pensato che fosse usato per elaborare una grande quantità di dati più velocemente delle alternative. Penso di vedere ora che si tratta più della capacità di elaborare ** enormi ** quantità di dati che altrimenti sarebbero impossibili da elaborare su una singola macchina, e la velocità non è un fattore. – mellowsoon

+6

@mellowsoon, ovviamente lo scopo di mapreduce è di elaborare velocemente una grande quantità di dati. È solo l'implementazione di MongoDB che non è molto veloce. – TTT

+0

@TTT - Grazie! In questo momento sto pensando che mongodb sia ancora la scelta giusta per il tipo di dati che stiamo cercando di salvare, ma sembra che potrei dover usare altre tecnologie di mapreduce per scricchiolare effettivamente i dati. – mellowsoon

6

Non stai facendo nulla di sbagliato. (Oltre a selezionare il valore sbagliato come hai già notato nei tuoi commenti.)

MongoDB mappa/ridurre le prestazioni non è proprio eccezionale. Questo è un problema noto; vedere ad esempio http://jira.mongodb.org/browse/SERVER-1197 dove un approccio naive è ~ 350 volte più veloce di M/R.

Un vantaggio è che è possibile specificare un nome di raccolta di output permanente con l'argomento out della chiamata mapReduce. Una volta completato l'M/R, la collezione temporanea verrà rinominata atomicamente nel nome permanente. In questo modo è possibile pianificare gli aggiornamenti delle statistiche e interrogare la collezione di output M/R in tempo reale.

+0

Grazie per la risposta. Lascerò la domanda senza risposta ancora per un po 'per vedere se qualcun altro ha qualche input. Questo è davvero deludente. Mi chiedo dove sia il collo della bottiglia? Forse perché MongoDB è a thread singolo, quindi il server che coordina tutti i frammenti può andare così veloce? Sono anche curioso dei risultati. Appare tutti i 10 milioni di documenti in cui sono stati mappati, quando la maggior parte avrebbe dovuto essere esclusa dalla query. – mellowsoon

+0

@mellowsoon: verifica la query eseguendo un conteggio sulla raccolta con gli stessi argomenti (e ricorda che il mese per un oggetto Date JS è indicizzato su base zero). –

+0

Grazie, lo sto facendo ora. Ho effettuato un'installazione completa e completa di Mongo sui 3 server e sto importando i dati ora. Una volta fatto, vedrò come vengono distribuiti i dati tra i frammenti e scegliere un intervallo di date che dovrebbe mettere la metà dei documenti corrispondenti su ciascun frammento. – mellowsoon

27

Forse sono troppo tardi, ma ...

In primo luogo, si sta interrogando la collezione per riempire il MapReduce, senza un indice. Devi creare un indice su "giorno".

MongoDB MapReduce è un thread singolo su un singolo server, ma è in parallelo su shard. I dati nei frammenti di mongo sono tenuti insieme in blocchi contigui ordinati per chiave di separazione.

Poiché la chiave di condivisione è "giorno" e si sta eseguendo una query, probabilmente si sta utilizzando solo uno dei tre server. La chiave Sharding viene utilizzata solo per diffondere i dati. Map Reduce eseguirà una query utilizzando l'indice "day" su ciascun frammento e sarà molto veloce.

Aggiungere qualcosa davanti al tasto del giorno per diffondere i dati. Il nome utente può essere una buona scelta.

In questo modo, la riduzione della mappa verrà avviata su tutti i server e si spera che si riduca il tempo di tre.

Qualcosa di simile a questo:

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

Penso che con queste aggiunte, è possibile abbinare la velocità di MySQL, ancora più veloce.

Inoltre, meglio non usarlo in tempo reale. Se i tuoi dati non hanno bisogno di essere "minutamente" precisi, shedule una mappa riduci l'attività ogni ora e poi usa la raccolta dei risultati.

+1

Inoltre, un'ultima cosa da sottolineare è che MongoDB ti chiede di assicurarti che i tuoi indici può essere tenuto in memoria; eseguire db.views.stats() ti dice la dimensione dell'indice. Questo è ciò che ti aiuta a ottimizzare e massimizzare le prestazioni. – Krynble