2013-06-11 12 views
5

biegnę operację mapreduce wykrywania duplikatów na dużej kolekcji na przykład mongos na sharded klastra i spodziewam się, że operacja trwa dłużej niż 10 minut:Jak uniknąć przekroczenia limitu czasu kursora w długiej operacji mapreduce?

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

Running to daje mi następujący błąd po około 10 minutach przetwarzania:

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

próbowałem adding a noTimeout option za pomocą .addOption(DBQuery.Option.noTimeout) na wezwanie mapreduce ale prowadzi to do błędów JS w powłoce Object [object Object] has no method 'addOption'

Jak uniknąć przekroczenia limitu czasu kursora w długiej operacji mapreduce?

Odpowiedz

6

Nie wspomniałeś, jakiego wydania używasz MongoDB, ale rozwiązanie będzie podobne do tego, co i tak jest tutaj prezentowane. Będę demonstrował na 2.2.4, co jest związane z Ubuntu 13.04.

Problem w tym, polega na wstrzyknięciu opcji do kursora. To miejsce, gdzie addOption życie:

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

przyjrzyjmy się jak mapReduce jest zdefiniowana:

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

Tak buduje dokument uruchomić polecenie poprzez runCommand. Spójrzmy dalej do niego:

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

więc polecenie jest uruchamiane poprzez findOne. Spójrzmy na to:

> 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, jest tu coś interesującego. Kursor jest inicjowany z flagami pochodzącymi z parametru options, co niestety nie pomaga w przypadku, ponieważ runCommand pozostawia to niezaznaczone, ale ORs to z getQueryOptions(), które pochodzi z kolekcji. Spójrzmy na to:

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

Ups ... to nie jest dobre. Tak więc nie mamy dostępu do kursora ani żadnego sposobu wprowadzania opcji zapytania do wykonanego polecenia za pomocą niehackowskich metod.

Cóż, ale dowiedzieliśmy się wiele o tym, jak polecenia redukcji mapy są w rzeczywistości dostarczane do serwera za pośrednictwem tego procesu. To tylko dokument, który jest sprawdzany w odniesieniu do określonej kolekcji w bazie danych. Oznacza to, że możemy zbudować to samo zapytanie i uruchomić je samodzielnie, ale zapewniając niezbędne flagi.

Nie będę się kłopotać budowaniem całego polecenia MongoDB i ustawianiem dla ciebie wyniku, ale pokażę, że faktycznie działa, wykonując polecenie za pomocą polecenia isMaster.

To polecenie działa bez żadnych flag:

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

Aby zobaczyć różnicę w efekcie będziemy tcpdump komunikacji z bazą danych.Widzimy w the wire protocol documentation że odpowiednie flagi żyć tuż przed nazwą zbiórki w 32 bitach całkowitą, więc jest to łatwe do wykrycia odpowiedniego kawałka wysypisko:

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

dobre. Możemy zobaczyć cztery bajty wyzerowane, tuż przed nazwą kolekcji.

Teraz zróbmy to samo, zapewniając trochę flag. Nauczyliśmy się od powyższej sekcji debugowania że flagi zapytań mogą być dostarczone jako trzecią opcją findOne, więc zróbmy to:

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

i patrz zrzut:

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

Hej, nasz flagi zostały dostarczone tam, gdzie powinny, a my możemy zobaczyć, że zostały odwrócone, co oznacza, że ​​bajty są zakodowane jako little-endian, pasujące do the docs.

Tak, to oznacza, że ​​można dostarczyć flagę DBQuery.Option.noTimeout jako trzecią opcją findOne i ręcznie kod polecenie map-redukować jak opisano in the documentation w sposób podobny do tego, co zrobiliśmy z isMaster, a dostaniesz co chcesz.

Powiązane problemy