sql >> Databasteknik >  >> NoSQL >> MongoDB

MongoDB - Fel:kommandot getMore misslyckades:Markören hittades inte

EDIT - Frågeprestanda:

Som @NeilLunn påpekade i sina kommentarer, bör du inte filtrera dokumenten manuellt, utan använda .find(...) för det istället:

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

Använder också .bulkWrite() , tillgänglig från MongoDB 3.2 , kommer att fungera mycket mer än att göra individuella uppdateringar.

Det är möjligt att du med det kan utföra din fråga inom markörens 10 minuters livslängd. Om det fortfarande krävs mer än så kommer markören att förfalla och du kommer att ha samma problem ändå, vilket förklaras nedan:

Vad händer här:

Error: getMore command failed kan bero på en markör-timeout, som är relaterad till två markörattribut:

  • Timeout-gräns, som är 10 minuter som standard. Från dokumenten:

    Som standard kommer servern automatiskt att stänga markören efter 10 minuters inaktivitet, eller om klienten har tömt markören.

  • Batchstorlek, vilket är 101 dokument eller 16 MB för den första batchen, och 16 MB, oavsett antalet dokument, för efterföljande partier (från MongoDB 3.4 ). Från dokumenten:

    find() och aggregate() operationer har en initial batchstorlek på 101 dokument som standard. Efterföljande getMore-operationer som utförs mot den resulterande markören har ingen standardbatchstorlek, så de begränsas endast av meddelandestorleken på 16 megabyte.

Förmodligen konsumerar du de första 101 dokumenten och får sedan en 16 MB batch, vilket är max, med mycket fler dokument. Eftersom det tar mer än 10 minuter att bearbeta dem, tar markören på servern timeout och när du är klar med att bearbeta dokumenten i den andra batchen och begär en ny, är markören redan stängd:

När du itererar genom markören och når slutet av den returnerade batchen, om det finns fler resultat, kommer cursor.next() att utföra en getMore-operation för att hämta nästa batch.

Möjliga lösningar:

Jag ser 5 möjliga sätt att lösa detta, 3 bra, med sina för- och nackdelar, och 2 dåliga:

  1. 👍 Minska batchstorleken för att hålla markören vid liv.

  2. 👍 Ta bort tidsgränsen från markören.

  3. 👍 Försök igen när markören tar slut.

  4. 👎 Fråga resultaten i partier manuellt.

  5. 👎 Få alla dokument innan markören går ut.

Observera att de inte är numrerade enligt några specifika kriterier. Läs igenom dem och bestäm vilken som fungerar bäst för just ditt fall.

1. 👍 Minska batchstorleken för att hålla markören vid liv

Ett sätt att lösa det är att använda cursor.bacthSize för att ställa in batchstorleken på markören som returneras av din find fråga för att matcha de som du kan bearbeta inom dessa 10 minuter:

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

Kom dock ihåg att inställning av en mycket konservativ (liten) batchstorlek förmodligen kommer att fungera, men kommer också att gå långsammare, eftersom du nu behöver komma åt servern fler gånger.

Å andra sidan, om du ställer in det till ett värde för nära antalet dokument du kan behandla på 10 minuter betyder det att det är möjligt att om vissa iterationer tar lite längre tid att bearbeta av någon anledning (andra processer kan ta mer resurser) , kommer markören att förfalla ändå och du får samma fel igen.

2. 👍 Ta bort tidsgränsen från markören

Ett annat alternativ är att använda cursor.noCursorTimeout för att förhindra att markören tar timeout:

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

Detta anses vara en dålig praxis eftersom du skulle behöva stänga markören manuellt eller uttömma alla resultat så att den stängs automatiskt:

Efter att ha ställt in noCursorTimeout alternativet måste du antingen stänga markören manuellt med cursor.close() eller genom att uttömma markörens resultat.

Eftersom du vill bearbeta alla dokument i markören, skulle du inte behöva stänga den manuellt, men det är fortfarande möjligt att något annat går fel i din kod och ett fel kastas innan du är klar, vilket gör att markören lämnas öppen .

Om du fortfarande vill använda detta tillvägagångssätt, använd en try-catch för att se till att du stänger markören om något går fel innan du förbrukar alla dess dokument.

Observera att jag inte anser att detta är en dålig lösning (därför 👍), eftersom jag till och med trodde att det anses vara en dålig praxis...:

  • Det är en funktion som stöds av föraren. Om det var så illa, eftersom det finns alternativa sätt att komma runt timeoutproblem, som förklaras i de andra lösningarna, kommer detta inte att stödjas.

  • Det finns sätt att använda den på ett säkert sätt, det gäller bara att vara extra försiktig med den.

  • Jag antar att du inte kör den här typen av frågor regelbundet, så chansen att du börjar lämna öppna markörer överallt är liten. Om så inte är fallet och du verkligen behöver hantera dessa situationer hela tiden, är det vettigt att inte använda noCursorTimeout .

3. 👍 Försök igen när markören går ut

I grund och botten lägger du din kod i en try-catch och när du får felet får du en ny markör som hoppar över de dokument som du redan har bearbetat:

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

Observera att du måste sortera resultaten för att den här lösningen ska fungera.

Med detta tillvägagångssätt minimerar du antalet förfrågningar till servern genom att använda den maximala batchstorleken på 16 MB, utan att behöva gissa hur många dokument du kommer att kunna bearbeta på 10 minuter i förväg. Därför är den också mer robust än den tidigare metoden.

4. 👎 Fråga resultaten i omgångar manuellt

I grund och botten använder du skip(), limit() och sort() för att göra flera frågor med ett antal dokument som du tror att du kan bearbeta på 10 minuter.

Jag anser att detta är en dålig lösning eftersom föraren redan har möjlighet att ställa in batchstorleken, så det finns ingen anledning att göra detta manuellt, använd bara lösning 1 och uppfinn inte hjulet igen.

Det är också värt att nämna att den har samma nackdelar som lösning 1,

5. 👎 Få alla dokument innan markören går ut

Förmodligen tar din kod lite tid att köra på grund av resultatbearbetning, så du kan hämta alla dokument först och sedan bearbeta dem:

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

Detta kommer att hämta alla batcher en efter en och stänger markören. Sedan kan du gå igenom alla dokument i results och gör vad du behöver göra.

Men om du har problem med timeout är chansen stor att din resultatuppsättning är ganska stor, så att hämta allt i minnet kanske inte är det mest lämpliga att göra.

Anmärkning om ögonblicksbildsläge och dubbletter av dokument

Det är möjligt att vissa dokument returneras flera gånger om mellanliggande skrivoperationer flyttar dem på grund av att dokumentstorleken ökar. För att lösa detta, använd cursor.snapshot() . Från dokumenten:

Lägg till metoden snapshot() till en markör för att växla mellan "snapshot"-läget. Detta säkerställer att frågan inte returnerar ett dokument flera gånger, även om mellanliggande skrivoperationer leder till att dokumentet flyttas på grund av att dokumentstorleken ökar.

Kom dock ihåg dess begränsningar:

  • Det fungerar inte med fragmenterade samlingar.

  • Det fungerar inte med sort() eller hint() , så det kommer inte att fungera med lösningar 3 och 4.

  • Det garanterar inte isolering från infogning eller borttagning.

Notera med lösning 5 att tidsfönstret för att flytta dokument som kan orsaka hämtning av dubbletter av dokument är smalare än med de andra lösningarna, så du kanske inte behöver snapshot() .

I just ditt fall, eftersom samlingen heter snapshot , förmodligen kommer det inte att ändras, så du behöver förmodligen inte snapshot() . Dessutom gör du uppdateringar av dokument baserat på deras data och, när uppdateringen är gjord, kommer samma dokument inte att uppdateras igen även om det hämtas flera gånger, eftersom if skick kommer att hoppa över det.

Anmärkning om öppna markörer

För att se antalet öppna markörer använd db.serverStatus().metrics.cursor .



  1. Hur man uppdaterar och lägger upp flera dokument i MongoDB med C#-drivrutiner

  2. Kan selleri celerybeat använda en Databas Scheduler utan Django?

  3. En guide till MongoDB-distribution och underhåll med Puppet:Del 2

  4. Vilken NoSQL-databas ska jag använda för loggning?