sql >> Databáze >  >> NoSQL >> MongoDB

MongoDB - Chyba:příkaz getMore selhal:Kurzor nenalezen

UPRAVIT – Výkon dotazu:

Jak ve svých komentářích zdůraznil @NeilLunn, neměli byste dokumenty filtrovat ručně, ale použít .find(...) místo toho:

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

Také pomocí .bulkWrite() , dostupné od MongoDB 3.2 , bude mnohem výkonnější než provádění jednotlivých aktualizací.

Je možné, že díky tomu budete schopni provést svůj dotaz během 10 minut životnosti kurzoru. Pokud to stále trvá déle, platnost kurzoru vyprší a stejně budete mít stejný problém, který je vysvětlen níže:

Co se zde děje:

Error: getMore command failed může být způsobeno časovým limitem kurzoru, který souvisí se dvěma atributy kurzoru:

  • Limit časového limitu, který je ve výchozím nastavení 10 minut. Z dokumentů:

    Ve výchozím nastavení server automaticky zavře kurzor po 10 minutách nečinnosti nebo pokud klient vyčerpá kurzor.

  • Velikost dávky, což je 101 dokumentů nebo 16 MB pro první dávku a 16 MB, bez ohledu na počet dokumentů, pro následující dávky (od MongoDB 3.4 ). Z dokumentů:

    find() a aggregate() operace mají ve výchozím nastavení počáteční velikost dávky 101 dokumentů. Následné operace getMore provedené proti výslednému kurzoru nemají žádnou výchozí velikost dávky, takže jsou omezeny pouze velikostí zprávy 16 megabajtů.

Pravděpodobně spotřebováváte těchto počátečních 101 dokumentů a poté dostáváte 16 MB dávku, což je maximum, s mnohem více dokumenty. Protože jejich zpracování trvá déle než 10 minut, kurzor na serveru vyprší, a když dokončíte zpracování dokumentů v druhé dávce a požádáte o novou, je kurzor již uzavřen:

Když budete iterovat kurzorem a dosáhnete konce vrácené dávky, pokud existuje více výsledků, kurzor.next() provede operaci getMore k načtení další dávky.

Možná řešení:

Vidím 5 možných způsobů, jak to vyřešit, 3 dobré se svými klady a zápory a 2 špatné:

  1. 👍 Zmenšení velikosti dávky, aby kurzor zůstal živý.

  2. 👍 Odstraňte časový limit z kurzoru.

  3. 👍 Zkuste to znovu, až kurzor vyprší.

  4. 👎 Dotazujte se na výsledky v dávkách ručně.

  5. 👎 Získejte všechny dokumenty před vypršením platnosti kurzoru.

Všimněte si, že nejsou číslovány podle žádných specifických kritérií. Přečtěte si je a rozhodněte se, který z nich nejlépe vyhovuje vašemu konkrétnímu případu.

1. 👍 Zmenšení velikosti dávky, aby kurzor zůstal živý

Jedním ze způsobů, jak to vyřešit, je použít cursor.bacthSize pro nastavení velikosti dávky na kurzor vrácený vaším find dotaz, který odpovídá těm, které můžete zpracovat během těchto 10 minut:

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

Mějte však na paměti, že nastavení velmi konzervativní (malé) velikosti dávky bude pravděpodobně fungovat, ale bude také pomalejší, protože nyní musíte k serveru přistupovat vícekrát.

Na druhou stranu nastavení na hodnotu příliš blízkou počtu dokumentů, které můžete zpracovat za 10 minut, znamená, že je možné, že pokud zpracování některých iterací bude z jakéhokoli důvodu trvat o něco déle (jiné procesy mohou spotřebovávat více zdrojů) , platnost kurzoru stejně vyprší a znovu se zobrazí stejná chyba.

2. 👍 Odstraňte časový limit z kurzoru

Další možností je použít kurzor.noCursorTimeout, abyste zabránili vypršení časového limitu kurzoru:

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

To je považováno za špatný postup, protože byste museli kurzor zavřít ručně nebo vyčerpat všechny jeho výsledky, aby se automaticky zavřel:

Po nastavení noCursorTimeout musíte kurzor zavřít ručně pomocí cursor.close() nebo vyčerpáním výsledků kurzoru.

Protože chcete zpracovat všechny dokumenty v kurzoru, nemusíte jej zavírat ručně, ale stále je možné, že se ve vašem kódu pokazí něco jiného a před dokončením se vyvolá chyba, takže kurzor zůstane otevřený. .

Pokud stále chcete používat tento přístup, použijte try-catch abyste se ujistili, že zavřete kurzor, pokud se něco pokazí, než spotřebujete všechny jeho dokumenty.

Všimněte si, že to nepovažuji za špatné řešení (proto to 👍), jak jsem si dokonce myslel, že je to považováno za špatný postup...:

  • Je to funkce podporovaná ovladačem. Pokud by to bylo tak špatné, protože existují alternativní způsoby, jak obejít problémy s časovým limitem, jak je vysvětleno v jiných řešeních, nebude to podporováno.

  • Existují způsoby, jak jej bezpečně používat, jde jen o to, být s ním extra opatrný.

  • Předpokládám, že tento druh dotazů nespouštíte pravidelně, takže šance, že všude začnete nechávat otevřené kurzory, je nízká. Pokud tomu tak není a opravdu potřebujete tyto situace neustále řešit, pak má smysl nepoužívat noCursorTimeout .

3. 👍 Zkuste to znovu, až vyprší platnost kurzoru

V zásadě vložíte svůj kód do try-catch a když se zobrazí chyba, zobrazí se nový kurzor, který přeskočí dokumenty, které jste již zpracovali:

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

Aby toto řešení fungovalo, musíte výsledky seřadit.

S tímto přístupem minimalizujete počet požadavků na server použitím maximální možné velikosti dávky 16 MB, aniž byste museli předem hádat, kolik dokumentů budete schopni zpracovat za 10 minut. Proto je také robustnější než předchozí přístup.

4. 👎 Ruční dotaz na výsledky v dávkách

V zásadě používáte skip(), limit() a sort() k provádění více dotazů s množstvím dokumentů, o kterých si myslíte, že je můžete zpracovat za 10 minut.

Považuji to za špatné řešení, protože ovladač již má možnost nastavit velikost dávky, takže není důvod to dělat ručně, stačí použít řešení 1 a nevymýšlet znovu kolo.

Také stojí za zmínku, že má stejné nevýhody než řešení 1,

5. 👎 Získejte všechny dokumenty před vypršením platnosti kurzoru

Spuštění vašeho kódu pravděpodobně nějakou dobu trvá kvůli zpracování výsledků, takže byste mohli nejprve načíst všechny dokumenty a poté je zpracovat:

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

Tím načtete všechny dávky jednu po druhé a zavřete kurzor. Poté můžete procházet všechny dokumenty v results a udělejte to, co potřebujete.

Pokud však máte problémy s časovým limitem, je pravděpodobné, že vaše sada výsledků je poměrně velká, takže stahování všeho z paměti nemusí být nejvhodnější.

Poznámka o režimu snímku a duplicitních dokumentech

Je možné, že některé dokumenty budou vráceny vícekrát, pokud je mezi sebou operace zápisu přesunou kvůli nárůstu velikosti dokumentu. Chcete-li to vyřešit, použijte cursor.snapshot() . Z dokumentů:

Připojením metody snapshot() ke kurzoru přepnete režim „snímek“. To zajišťuje, že dotaz nevrátí dokument vícekrát, i když intervenující operace zápisu vedou k přesunutí dokumentu kvůli nárůstu velikosti dokumentu.

Mějte však na paměti jeho omezení:

  • Nefunguje se sdílenými kolekcemi.

  • Nepracuje s sort() nebo hint() , takže nebude fungovat s řešeními 3 a 4.

  • Nezaručuje izolaci od vkládání nebo mazání.

Všimněte si, že u řešení 5 je časové okno pro přesun dokumentů, které mohou způsobit načítání duplicitních dokumentů, užší než u jiných řešení, takže možná nebudete potřebovat snapshot() .

Ve vašem konkrétním případě se kolekce nazývá snapshot , pravděpodobně se to nezmění, takže pravděpodobně nepotřebujete snapshot() . Kromě toho provádíte aktualizace dokumentů na základě jejich dat a jakmile bude aktualizace provedena, stejný dokument nebude znovu aktualizován, i když bude načten vícekrát, jako if podmínka to přeskočí.

Poznámka k otevřeným kurzorům

Chcete-li zobrazit počet otevřených kurzorů, použijte db.serverStatus().metrics.cursor .



  1. 2 způsoby, jak nahradit podřetězec v MongoDB

  2. Operátor agregace MongoDB $count

  3. Mongodb – Rozdíl mezi spuštěnými databázemi mongo a mongod

  4. Jak používat redis PUBLISH/SUBSCRIBE s nodejs k upozornění klientů, když se změní hodnoty dat?