sql >> Database >  >> NoSQL >> MongoDB

MongoDB - Fout:opdracht getMore mislukt:cursor niet gevonden

BEWERKEN - Queryprestaties:

Zoals @NeilLunn in zijn opmerkingen aangaf, moet u de documenten niet handmatig filteren, maar .find(...) gebruiken daarvoor in plaats daarvan:

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

Gebruik ook .bulkWrite() , beschikbaar vanaf MongoDB 3.2 , zal veel beter presteren dan afzonderlijke updates uitvoeren.

Het is mogelijk dat u daarmee uw query kunt uitvoeren binnen de levensduur van 10 minuten van de cursor. Als er nog steeds meer nodig is, verloopt je cursor en heb je toch hetzelfde probleem, wat hieronder wordt uitgelegd:

Wat is hier aan de hand:

Error: getMore command failed kan te wijten zijn aan een cursortime-out, die verband houdt met twee cursorattributen:

  • Time-outlimiet, die standaard 10 minuten is. Uit de documenten:

    Standaard sluit de server de cursor automatisch na 10 minuten inactiviteit, of als de client de cursor heeft uitgeput.

  • Batchgrootte, dat is 101 documenten of 16 MB voor de eerste batch, en 16 MB, ongeacht het aantal documenten, voor volgende batches (vanaf MongoDB 3.4 ). Uit de documenten:

    find() en aggregate() bewerkingen hebben standaard een initiële batchgrootte van 101 documenten. Daaropvolgende getMore-bewerkingen die tegen de resulterende cursor worden uitgevoerd, hebben geen standaardbatchgrootte, dus worden ze alleen beperkt door de berichtgrootte van 16 megabyte.

Waarschijnlijk verbruikt u die eerste 101 documenten en krijgt u vervolgens een batch van 16 MB, wat het maximum is, met veel meer documenten. Aangezien het meer dan 10 minuten duurt om ze te verwerken, treedt er een time-out van de cursor op de server op en tegen de tijd dat u klaar bent met het verwerken van de documenten in de tweede batch en een nieuwe aanvraagt, is de cursor al gesloten:

Terwijl u door de cursor gaat en het einde van de geretourneerde batch bereikt, zal cursor.next() een getMore-bewerking uitvoeren om de volgende batch op te halen als er meer resultaten zijn.

Mogelijke oplossingen:

Ik zie 5 mogelijke manieren om dit op te lossen, 3 goede, met hun voor- en nadelen, en 2 slechte:

  1. 👍 De batchgrootte verkleinen om de cursor in leven te houden.

  2. 👍 Verwijder de time-out van de cursor.

  3. 👍 Probeer het opnieuw wanneer de cursor verloopt.

  4. 👎 De resultaten handmatig in batches opvragen.

  5. 👎 Haal alle documenten op voordat de cursor verloopt.

Merk op dat ze niet genummerd zijn volgens specifieke criteria. Lees ze door en beslis welke het beste werkt voor uw specifieke geval.

1. 👍 De batchgrootte verkleinen om de cursor in leven te houden

Een manier om dat op te lossen is het gebruik van cursor.bacthSize om de batchgrootte in te stellen op de cursor die wordt geretourneerd door uw find zoekopdracht die overeenkomt met de zoekopdracht die u binnen die 10 minuten kunt verwerken:

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

Houd er echter rekening mee dat het instellen van een zeer conservatieve (kleine) batchgrootte waarschijnlijk zal werken, maar ook langzamer zal zijn, omdat u nu vaker toegang tot de server moet hebben.

Aan de andere kant, als u het instelt op een waarde die te dicht bij het aantal documenten ligt dat u in 10 minuten kunt verwerken, betekent dit dat het mogelijk is dat als sommige iteraties om welke reden dan ook wat langer duren om te verwerken (andere processen kunnen meer bronnen verbruiken) , zal de cursor hoe dan ook verlopen en krijgt u dezelfde fout opnieuw.

2. 👍 Verwijder de time-out van de cursor

Een andere optie is om cursor.noCursorTimeout te gebruiken om te voorkomen dat de cursor een time-out krijgt:

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

Dit wordt als een slechte gewoonte beschouwd, omdat u de cursor handmatig moet sluiten of alle resultaten moet uitputten, zodat deze automatisch wordt gesloten:

Na het instellen van de noCursorTimeout optie, moet u ofwel de cursor handmatig sluiten met cursor.close() of door de resultaten van de cursor uit te putten.

Omdat u alle documenten in de cursor wilt verwerken, hoeft u deze niet handmatig te sluiten, maar het is nog steeds mogelijk dat er iets anders misgaat in uw code en er een fout wordt gegenereerd voordat u klaar bent, waardoor de cursor open blijft .

Als je deze aanpak toch wilt gebruiken, gebruik dan een try-catch om ervoor te zorgen dat u de cursor sluit als er iets misgaat voordat u alle documenten gebruikt.

Opmerking Ik beschouw dit niet als een slechte oplossing (daarom de 👍), omdat het zelfs als een slechte gewoonte wordt beschouwd...:

  • Het is een functie die door de bestuurder wordt ondersteund. Als het zo erg was, omdat er alternatieve manieren zijn om time-outproblemen te omzeilen, zoals uitgelegd in de andere oplossingen, wordt dit niet ondersteund.

  • Er zijn manieren om het veilig te gebruiken, het is alleen een kwestie van extra voorzichtig zijn.

  • Ik neem aan dat je dit soort zoekopdrachten niet regelmatig uitvoert, dus de kans dat je overal cursors open laat staan, is klein. Als dit niet het geval is, en je moet echt altijd met deze situaties omgaan, dan is het logisch om noCursorTimeout niet te gebruiken. .

3. 👍 Probeer het opnieuw wanneer de cursor verloopt

Kortom, u plaatst uw code in een try-catch en wanneer je de foutmelding krijgt, krijg je een nieuwe cursor die de documenten overslaat die je al hebt verwerkt:

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

Merk op dat u de resultaten moet sorteren om deze oplossing te laten werken.

Met deze aanpak minimaliseert u het aantal verzoeken aan de server door de maximaal mogelijke batchgrootte van 16 MB te gebruiken, zonder dat u van tevoren hoeft te raden hoeveel documenten u in 10 minuten kunt verwerken. Daarom is het ook robuuster dan de vorige aanpak.

4. 👎 De resultaten in batches handmatig opvragen

In principe gebruik je skip(), limit() en sort() om meerdere zoekopdrachten uit te voeren met een aantal documenten waarvan je denkt dat je ze in 10 minuten kunt verwerken.

Ik beschouw dit als een slechte oplossing omdat de bestuurder al de mogelijkheid heeft om de batchgrootte in te stellen, dus er is geen reden om dit handmatig te doen, gebruik gewoon oplossing 1 en vind het wiel niet opnieuw uit.

Het is ook vermeldenswaard dat het dezelfde nadelen heeft als oplossing 1,

5. 👎 Verkrijg alle documenten voordat de cursor verloopt

Waarschijnlijk duurt het even voordat uw code is uitgevoerd vanwege de verwerking van de resultaten, dus u kunt eerst alle documenten ophalen en ze vervolgens verwerken:

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

Hiermee worden alle batches één voor één opgehaald en wordt de cursor gesloten. Vervolgens kunt u door alle documenten in results bladeren en doe wat je moet doen.

Als je echter time-outproblemen hebt, is de kans groot dat je resultatenset vrij groot is, dus het is misschien niet het meest aan te raden om alles in het geheugen te halen.

Opmerking over snapshot-modus en dubbele documenten

Het is mogelijk dat sommige documenten meerdere keren worden geretourneerd als tussenliggende schrijfbewerkingen ze verplaatsen vanwege een toename in documentgrootte. Om dit op te lossen, gebruikt u cursor.snapshot() . Uit de documenten:

Voeg de methode snapshot() toe aan een cursor om de modus "snapshot" in te schakelen. Dit zorgt ervoor dat de query een document niet meerdere keren retourneert, zelfs als tussenliggende schrijfbewerkingen resulteren in een verplaatsing van het document vanwege de groei in documentgrootte.

Houd echter rekening met de beperkingen:

  • Het werkt niet met shard-collecties.

  • Het werkt niet met sort() of hint() , dus het werkt niet met oplossingen 3 en 4.

  • Het garandeert geen isolatie van invoegingen of verwijderingen.

Houd er rekening mee dat bij oplossing 5 het tijdvenster voor het verplaatsen van documenten waardoor dubbele documenten kunnen worden opgehaald, smaller is dan bij de andere oplossingen, dus het is mogelijk dat u snapshot() niet nodig heeft .

In uw specifieke geval, aangezien de verzameling snapshot . wordt genoemd , waarschijnlijk zal het niet veranderen, dus u heeft waarschijnlijk geen snapshot() nodig . Bovendien voert u updates van documenten uit op basis van hun gegevens en als de update eenmaal is voltooid, wordt datzelfde document niet opnieuw bijgewerkt, ook al wordt het meerdere keren opgehaald, zoals de if voorwaarde zal het overslaan.

Opmerking over open cursors

Gebruik db.serverStatus().metrics.cursor om het aantal geopende cursors te zien. .



  1. Hoe rauwe mongodb-bewerkingen in mangoest uitvoeren?

  2. For loop in redis met asynchrone verzoeken van nodejs

  3. Dynamische attributen met Rails en Mongoid

  4. Mongoose, zoek, retourneer specifieke eigenschappen