2013-06-11 16 views
5

Io corro un'operazione MapReduce di rilevamento duplicati su una vasta collezione contro un'istanza mongos su un cluster sharded e mi aspetto l'operazione di prendere più di 10 minuti:Come evitare un timeout del cursore su un'operazione di ridimensionamento della mappa di lunga durata?

m = function() { 
    emit(this.fieldForDupCheck, 1); 
} 
r = function (k, vals) { 
    return Array.sum(vals); 
} 
res = db.Collection.mapReduce(m, r, { out : "dups" }); 

L'esecuzione di questo mi dà il seguente errore dopo circa 10 minuti di trattamento:

uncaught exception: map reduce failed:{ 
"ok" : 0, 
"errmsg" : "MR post processing failed: { result: "dups", errmsg: "exception: getMore: cursor didn't exist on server, possible restart or timeout?", code: 13127, ok: 0.0 }" 
} 

ho provato adding a noTimeout option utilizzando .addOption(DBQuery.Option.noTimeout) sulla chiamata MapReduce ma ciò comporta un errore JS nel guscio Object [object Object] has no method 'addOption'

Come evitare un timeout del cursore su un'operazione di ridimensionamento della mappa di lunga durata?

risposta

6

Non hai menzionato quale versione di MongoDB stai usando, ma la soluzione sarà simile a quanto presentato qui in ogni caso. Dimostrerò sopra 2.2.4, che è ciò che viene fornito con Ubuntu 13.04.

Il problema è di iniettare l'opzione nel cursore. Ecco dove le addOption vita:

> var cursor = db.test.find() 
> cursor.addOption 
function (option) { 
    this._options |= option; 
    return this; 
} 

diamo un'occhiata a come mapReduce è definito:

> db.test.mapReduce 
function (map, reduce, optionsOrOutString) { 
    var c = {mapreduce:this._shortName, map:map, reduce:reduce}; 
    ... 
    var raw = this._db.runCommand(c); 
    ... 
    return new MapReduceResult(this._db, raw); 
} 

Così si costruisce un documento per eseguire il comando tramite runCommand. Diamo un'occhiata più al suo interno:

> db.runCommand 
function (obj) { 
    if (typeof obj == "string") { 
     var n = {}; 
     n[obj] = 1; 
     obj = n; 
    } 
    return this.getCollection("$cmd").findOne(obj); 
} 

Così il comando viene eseguito tramite findOne. Diamo un'occhiata:

> db.test.findOne 
function (query, fields, options) { 
    var cursor = this._mongo.find(this._fullName, this._massageObject(query) || {}, fields, -1, 0, 0, options || this.getQueryOptions()); 
    if (!cursor.hasNext()) { 
     return null; 
    } 
    var ret = cursor.next(); 
    ... 
    return ret; 
} 

Ah, c'è qualcosa di interessante qui. Il cursore viene inizializzato con flag provenienti dal parametro options, che sfortunatamente non aiuta il tuo caso perché runCommand lascia non impostato, ma lo esegue con getQueryOptions(), che proviene dalla raccolta. Diamo un'occhiata:

> db.collection.getQueryOptions 
function() { 
    var options = 0; 
    if (this.getSlaveOk()) { 
     options |= 4; 
    } 
    return options; 
} 

Oops .. non va bene. Quindi non abbiamo accesso al cursore, né alcun modo per iniettare opzioni di query nel comando eseguito tramite mezzi non hacker.

Bene, ma abbiamo imparato molto su come i comandi di riduzione della mappa vengono effettivamente inviati al server attraverso tale processo. È solo un documento che viene interrogato su una specifica raccolta nel database. Ciò significa che possiamo costruire la stessa query ed eseguirla autonomamente, ma fornendo tutti i flag necessari.

Non mi occuperò della creazione dell'intero comando MongoDB e dell'impostazione del risultato, ma ti mostrerò che funziona effettivamente eseguendo il comando isMaster.

Questo è il comando che esegue senza bandiere:

> db.getCollection("$cmd").findOne({isMaster: 1}).ismaster 
true 

Per vedere la differenza in effetti, ci TCPDUMP la comunicazione con il database.Possiamo vedere in the wire protocol documentation che le bandiere rilevanti vivono a destra prima del nome della raccolta, in un 32 bit integer, quindi è facile da individuare il pezzo rilevante della discarica:

.     vvvvvvvvv 
    0x0040: d407 0000 0000 0000 7465 7374 2e24 636d ........test.$cm 
    0x0050: 6400 0000 0000 ffff ffff 1700 0000 0169 d..............i 

Good. Possiamo vedere quattro byte azzerati, proprio prima del nome della collezione.

Ora, facciamo lo stesso mentre forniamo alcune bandiere. Abbiamo imparato dalla sezione di debug sopra che le bandiere di query possono essere forniti come la terza opzione di findOne, quindi cerchiamo di fare quello:

> db.getCollection("$cmd").findOne({isMaster: 1}, undefined, 0xBEEF).ismaster 
true 

e vediamo la discarica:

.     vvvvvvvvv 
    0x0040: d407 0000 efbe 0000 7465 7374 2e24 636d ........test.$cm 
    0x0050: 6400 0000 0000 ffff ffff 1700 0000 0169 d..............i 

Hey, il nostro le bandiere sono state consegnate dove dovrebbero, e possiamo anche vederle invertite, il che significa che i byte sono codificati come little-endian, corrispondenti allo the docs.

Quindi, questo significa che è possibile fornire la bandiera DBQuery.Option.noTimeout come la terza opzione di findOne, e mano-codice il comando map-reduce come descritto in the documentation in un modo simile a quello che abbiamo fatto con isMaster, ed otterrete quello che tu vuoi.

Problemi correlati