MongoDB
 sql >> база данни >  >> NoSQL >> MongoDB

MongoDB - Грешка:командата getMore е неуспешна:Курсорът не е намерен

РЕДАКТИРАНЕ – Ефективност на заявката:

Както @NeilLunn посочи в коментарите си, не трябва да филтрирате документите ръчно, а да използвате .find(...) вместо това:

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

Също така с помощта на .bulkWrite() , наличен от MongoDB 3.2 , ще бъде много по-ефективно от извършването на отделни актуализации.

Възможно е с това да сте в състояние да изпълните заявката си в рамките на 10 минути живот на курсора. Ако все пак е необходимо повече от това, курсорът ви ще изтече и така или иначе ще имате същия проблем, който е обяснен по-долу:

Какво става тук:

Error: getMore command failed може да се дължи на изчакване на курсора, което е свързано с два атрибута на курсора:

  • Ограничение за изчакване, което по подразбиране е 10 минути. От документите:

    По подразбиране сървърът автоматично ще затвори курсора след 10 минути бездействие или ако клиентът е изчерпал курсора.

  • Размер на пакета, който е 101 документа или 16 MB за първата партида и 16 MB, независимо от броя на документите, за следващите партиди (от MongoDB 3.4 ). От документите:

    find() и aggregate() операциите имат първоначален размер на партида от 101 документа по подразбиране. Последващите операции getMore, издадени срещу получения курсор, нямат размер на партида по подразбиране, така че са ограничени само от размера на съобщението от 16 мегабайта.

Вероятно консумирате тези първоначални 101 документа и след това получавате партида от 16 MB, което е максимумът, с много повече документи. Тъй като обработката им отнема повече от 10 минути, курсорът на сървъра изтече и докато приключите с обработката на документите във втората партида и заявите нова, курсорът вече е затворен:

Докато преминавате през курсора и достигате края на върнатата партида, ако има повече резултати, cursor.next() ще извърши операция getMore, за да извлече следващата партида.

Възможни решения:

Виждам 5 възможни начина за решаване на това, 3 добри, с техните плюсове и минуси, и 2 лоши:

  1. 👍 Намаляване на размера на партидата, за да поддържа курсора жив.

  2. 👍 Премахнете изчакването от курсора.

  3. 👍 Опитайте отново, когато курсорът изтече.

  4. 👎 Запитвайте резултатите в пакети ръчно.

  5. 👎 Вземете всички документи, преди курсорът да изтече.

Обърнете внимание, че те не са номерирани по никакви специфични критерии. Прочетете ги и решете кой от тях работи най-добре за вашия конкретен случай.

1. 👍 Намаляване на размера на партидата, за да поддържа курсора жив

Един от начините да решите това е да използвате cursor.bacthSize за да зададете размера на партидата върху курсора, върнат от вашия find заявка, за да съответства на тези, които можете да обработите в рамките на тези 10 минути:

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

Въпреки това, имайте предвид, че задаване на много консервативен (малък) размер на партидата вероятно ще работи, но също така ще бъде по-бавно, тъй като сега трябва да осъществявате достъп до сървъра повече пъти.

От друга страна, задаването му на стойност, твърде близка до броя документи, които можете да обработите за 10 минути, означава, че е възможно, ако някои итерации отнеме малко повече време за обработка по някаква причина (други процеси може да консумират повече ресурси) , курсорът така или иначе ще изтече и ще получите отново същата грешка.

2. 👍 Премахнете изчакването от курсора

Друга възможност е да използвате cursor.noCursorTimeout, за да предотвратите изтичане на курсора:

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

Това се счита за лоша практика, тъй като ще трябва да затворите курсора ръчно или да изчерпите всичките му резултати, така че да се затвори автоматично:

След задаване на noCursorTimeout опция, трябва или да затворите курсора ръчно с cursor.close() или чрез изчерпване на резултатите от курсора.

Тъй като искате да обработите всички документи в курсора, няма да е необходимо да го затваряте ръчно, но все пак е възможно нещо друго да се обърка във вашия код и да се изведе грешка, преди да сте готови, като по този начин курсорът остава отворен .

Ако все пак искате да използвате този подход, използвайте try-catch за да сте сигурни, че затваряте курсора, ако нещо се обърка, преди да използвате всичките му документи.

Забележете, че не смятам това за лошо решение (следователно 👍), тъй като дори смятам, че се счита за лоша практика...:

  • Това е функция, поддържана от драйвера. Ако беше толкова лошо, тъй като има алтернативни начини за заобикаляне на проблемите с изчакването, както е обяснено в другите решения, това няма да се поддържа.

  • Има начини да го използвате безопасно, въпросът е само да бъдете особено внимателни с него.

  • Предполагам, че не изпълнявате редовно този вид заявки, така че шансовете да започнете да оставяте отворени курсори навсякъде са ниски. Ако това не е така и наистина трябва да се справяте с тези ситуации през цялото време, тогава има смисъл да не използвате noCursorTimeout .

3. 👍 Опитайте отново, когато курсорът изтече

По принцип поставяте кода си в try-catch и когато получите грешката, получавате нов курсор, пропускащ документите, които вече сте обработили:

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

Имайте предвид, че трябва да сортирате резултатите, за да работи това решение.

С този подход минимизирате броя на заявките към сървъра, като използвате максималния възможен размер на партида от 16 MB, без да се налага предварително да гадаете колко документа ще можете да обработите за 10 минути. Следователно той е и по-стабилен от предишния подход.

4. 👎 Запитвайте резултатите в пакети ръчно

По принцип използвате skip(), limit() и sort(), за да направите множество заявки с редица документи, които смятате, че можете да обработите за 10 минути.

Считам това за лошо решение, защото драйверът вече има опцията да зададе размера на партидата, така че няма причина да правите това ръчно, просто използвайте решение 1 и не изобретявайте колелото.

Също така си струва да се спомене, че той има същите недостатъци като решение 1,

5. 👎 Вземете всички документи, преди курсорът да изтече

Вероятно кодът ви отнема известно време за изпълнение поради обработката на резултатите, така че можете първо да извлечете всички документи и след това да ги обработите:

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

Това ще извлече всички партиди една след друга и ще затвори курсора. След това можете да прегледате всички документи в results и направете каквото трябва.

Въпреки това, ако имате проблеми с изчакването, има вероятност вашият набор от резултати да е доста голям, така че изтеглянето на всичко в паметта може да не е най-препоръчителното нещо, което трябва да направите.

Забележка относно режима на моментна снимка и дублиращите се документи

Възможно е някои документи да се връщат няколко пъти, ако интервенционните операции на запис ги преместят поради нарастване на размера на документа. За да разрешите това, използвайте cursor.snapshot() . От документите:

Добавете метода snapshot() към курсора, за да превключите режима на „моментна снимка“. Това гарантира, че заявката няма да върне документ многократно, дори ако интервенционните операции за запис доведат до преместване на документа поради нарастването на размера на документа.

Имайте предвид обаче неговите ограничения:

  • Не работи с разчленени колекции.

  • Не работи с sort() или hint() , така че няма да работи с решения 3 и 4.

  • Това не гарантира изолация от вмъкване или изтриване.

Забележете, че при решение 5 времевият прозорец за преместване на документи, което може да доведе до извличане на дублирани документи, е по-тесен, отколкото при другите решения, така че може да не се нуждаете от snapshot() .

Във вашия конкретен случай, тъй като колекцията се нарича snapshot , вероятно е малко вероятно да се промени, така че вероятно нямате нужда от snapshot() . Освен това правите актуализации на документи въз основа на техните данни и след като актуализацията бъде извършена, същият документ няма да бъде актуализиран отново, въпреки че е извлечен многократно, като if условието ще го пропусне.

Забележка за отворените курсори

За да видите броя на отворените курсори, използвайте db.serverStatus().metrics.cursor .



  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Разлика между Find и FindAsync

  2. MongoDB:Как да дефинирам схема?

  3. как да покажете заявка, докато използвате анотации на заявка с MongoRepository с пролетни данни

  4. Заявката Mongoose near(...) в индексирано поле на 2dsphere не връща валидни резултати

  5. Aggregate $lookup Общият размер на документите в съответстващия конвейер надвишава максималния размер на документа