sql >> Database teknologi >  >> NoSQL >> MongoDB

MongoDB - Fejl:kommandoen getMore mislykkedes:Markøren blev ikke fundet

EDIT - Forespørgselsydeevne:

Som @NeilLunn påpegede i sine kommentarer, bør du ikke filtrere dokumenterne manuelt, men bruge .find(...) for det i stedet:

db.snapshots.find({
    roundedDate: { $exists: true },
    stream: { $exists: true },
    sid: { $exists: false }
})

Også ved at bruge .bulkWrite() , tilgængelig fra MongoDB 3.2 , vil være langt mere effektiv end at lave individuelle opdateringer.

Det er muligt, at du dermed er i stand til at udføre din forespørgsel inden for markørens 10 minutters levetid. Hvis det stadig tager mere end det, vil din markør udløbe, og du vil alligevel have det samme problem, hvilket er forklaret nedenfor:

Hvad foregår der her:

Error: getMore command failed kan skyldes en markørtimeout, som er relateret til to markørattributter:

  • Timeout-grænse, som er 10 minutter som standard. Fra dokumenterne:

    Som standard vil serveren automatisk lukke markøren efter 10 minutters inaktivitet, eller hvis klienten har brugt markøren.

  • Batchstørrelse, som er 101 dokumenter eller 16 MB for den første batch, og 16 MB, uanset antallet af dokumenter, for efterfølgende batches (fra MongoDB 3.4 ). Fra dokumenterne:

    find() og aggregate() operationer har en initial batchstørrelse på 101 dokumenter som standard. Efterfølgende getMore-operationer udstedt mod den resulterende markør har ingen standard batchstørrelse, så de er kun begrænset af meddelelsesstørrelsen på 16 megabyte.

Sandsynligvis bruger du de første 101 dokumenter og får derefter en batch på 16 MB, hvilket er det maksimale, med mange flere dokumenter. Da det tager mere end 10 minutter at behandle dem, timeout for markøren på serveren, og når du er færdig med at behandle dokumenterne i den anden batch og anmoder om en ny, er markøren allerede lukket:

Når du itererer gennem markøren og når slutningen af ​​den returnerede batch, hvis der er flere resultater, vil cursor.next() udføre en getMore-operation for at hente den næste batch.

Mulige løsninger:

Jeg ser 5 mulige måder at løse dette på, 3 gode, med deres fordele og ulemper, og 2 dårlige:

  1. 👍 Reduktion af batchstørrelsen for at holde markøren i live.

  2. 👍 Fjern timeout fra markøren.

  3. 👍 Prøv igen, når markøren udløber.

  4. 👎 Forespørg resultaterne i batches manuelt.

  5. 👎 Hent alle dokumenter, før markøren udløber.

Bemærk, at de ikke er nummereret efter nogle specifikke kriterier. Læs dem igennem, og afgør, hvilken der passer bedst til netop din sag.

1. 👍 Reduktion af batchstørrelsen for at holde markøren i live

En måde at løse det på er at bruge cursor.bacthSize for at indstille batchstørrelsen på markøren, der returneres af din find forespørgsel for at matche dem, som du kan behandle inden for disse 10 minutter:

const cursor = db.collection.find()
    .batchSize(NUMBER_OF_DOCUMENTS_IN_BATCH);

Husk dog, at indstilling af en meget konservativ (lille) batchstørrelse nok vil virke, men vil også være langsommere, da du nu skal have adgang til serveren flere gange.

På den anden side betyder det, at sætte den til en værdi, der er for tæt på antallet af dokumenter, du kan behandle på 10 minutter, at det er muligt, at hvis nogle iterationer af en eller anden grund tager lidt længere tid at behandle (andre processer kan bruge flere ressourcer) , vil markøren alligevel udløbe, og du får den samme fejl igen.

2. 👍 Fjern timeout fra markøren

En anden mulighed er at bruge cursor.noCursorTimeout for at forhindre, at markøren får timeout:

const cursor = db.collection.find().noCursorTimeout();

Dette betragtes som en dårlig praksis, da du skal lukke markøren manuelt eller udtømme alle dens resultater, så den automatisk lukkes:

Efter indstilling af noCursorTimeout mulighed, skal du enten lukke markøren manuelt med cursor.close() eller ved at udtømme markørens resultater.

Da du ønsker at behandle alle dokumenterne i markøren, behøver du ikke at lukke den manuelt, men det er stadig muligt, at noget andet går galt i din kode, og der kommer en fejl, før du er færdig, og dermed forlader markøren åben. .

Hvis du stadig ønsker at bruge denne fremgangsmåde, skal du bruge en try-catch for at sikre, at du lukker markøren, hvis noget går galt, før du bruger alle dens dokumenter.

Bemærk, at jeg ikke betragter dette som en dårlig løsning (derfor 👍), da jeg selv troede, at det betragtes som en dårlig praksis...:

  • Det er en funktion, der understøttes af driveren. Hvis det var så slemt, da der er alternative måder at omgå timeout-problemer på, som forklaret i de andre løsninger, vil dette ikke blive understøttet.

  • Der er måder at bruge det sikkert på, det er bare et spørgsmål om at være ekstra forsigtig med det.

  • Jeg antager, at du ikke kører denne slags forespørgsler regelmæssigt, så chancerne for, at du begynder at efterlade markører åbne overalt, er lav. Hvis dette ikke er tilfældet, og du virkelig har brug for at håndtere disse situationer hele tiden, så giver det mening ikke at bruge noCursorTimeout .

3. 👍 Prøv igen, når markøren udløber

Grundlæggende lægger du din kode i en try-catch og når du får fejlen, får du en ny markør, der springer de dokumenter over, du allerede har behandlet:

let processed = 0;
let updated = 0;

while(true) {
    const cursor = db.snapshots.find().sort({ _id: 1 }).skip(processed);

    try {
        while (cursor.hasNext()) {
            const doc = cursor.next();

            ++processed;

            if (doc.stream && doc.roundedDate && !doc.sid) {
                db.snapshots.update({
                    _id: doc._id
                }, { $set: {
                    sid: `${ doc.stream.valueOf() }-${ doc.roundedDate }`
                }});

                ++updated;
            } 
        }

        break; // Done processing all, exit outer loop
    } catch (err) {
        if (err.code !== 43) {
            // Something else than a timeout went wrong. Abort loop.

            throw err;
        }
    }
}

Bemærk, at du skal sortere resultaterne for at denne løsning kan fungere.

Med denne tilgang minimerer du antallet af anmodninger til serveren ved at bruge den maksimalt mulige batchstørrelse på 16 MB uden at skulle gætte på, hvor mange dokumenter du vil være i stand til at behandle på 10 minutter i forvejen. Derfor er den også mere robust end den tidligere tilgang.

4. 👎 Forespørg resultaterne i batches manuelt

Grundlæggende bruger du skip(), limit() og sort() til at lave flere forespørgsler med et antal dokumenter, som du tror, ​​du kan behandle på 10 minutter.

Jeg betragter dette som en dårlig løsning, fordi chaufføren allerede har mulighed for at indstille batchstørrelsen, så der er ingen grund til at gøre dette manuelt, brug bare løsning 1 og opfind ikke hjulet igen.

Det er også værd at nævne, at det har de samme ulemper som løsning 1,

5. 👎 Hent alle dokumenter, før markøren udløber

Sandsynligvis tager din kode noget tid at udføre på grund af resultatbehandling, så du kunne hente alle dokumenter først og derefter behandle dem:

const results = new Array(db.snapshots.find());

Dette vil hente alle batches efter hinanden og lukke markøren. Derefter kan du gennemgå alle dokumenterne i results og gør, hvad du skal gøre.

Men hvis du har problemer med timeout, er chancerne for, at dit resultatsæt er ret stort, så det er måske ikke den mest tilrådelige ting at trække alt i hukommelsen.

Bemærkning om snapshot-tilstand og dublerede dokumenter

Det er muligt, at nogle dokumenter returneres flere gange, hvis mellemliggende skriveoperationer flytter dem på grund af en vækst i dokumentstørrelsen. For at løse dette, brug cursor.snapshot() . Fra dokumenterne:

Føj snapshot()-metoden til en markør for at skifte mellem "snapshot"-tilstand. Dette sikrer, at forespørgslen ikke returnerer et dokument flere gange, selvom mellemliggende skriveoperationer resulterer i en flytning af dokumentet på grund af væksten i dokumentstørrelsen.

Husk dog dens begrænsninger:

  • Det virker ikke med opdelte samlinger.

  • Det virker ikke med sort() eller hint() , så det vil ikke fungere med løsning 3 og 4.

  • Det garanterer ikke isolation fra indsættelse eller sletninger.

Bemærk med løsning 5, at tidsvinduet for at flytte dokumenter, der kan forårsage hentning af duplikerede dokumenter, er smallere end med de andre løsninger, så du behøver muligvis ikke snapshot() .

I dit særlige tilfælde, da samlingen hedder snapshot , sandsynligvis vil det ikke ændre sig, så du behøver sandsynligvis ikke snapshot() . Desuden laver du opdateringer på dokumenter baseret på deres data, og når opdateringen er udført, vil det samme dokument ikke blive opdateret igen, selvom det er hentet flere gange, som if tilstand vil springe det over.

Bemærkning om åbne markører

For at se et antal åbne markører, brug db.serverStatus().metrics.cursor .



  1. Vælg Max() med gruppe efter i mongodb

  2. Udfyld en mangustmodel med et felt, der ikke er et id

  3. Introduktion til Redis-datastrukturer:Sorterede sæt

  4. MongoDB $dayOfYear