MongoDB
 sql >> Baza danych >  >> NoSQL >> MongoDB

MongoDB — Błąd:polecenie getMore nie powiodło się:nie znaleziono kursora

EDYTUJ — Wydajność zapytania:

Jak zauważył @NeilLunn w swoich komentarzach, nie powinieneś filtrować dokumentów ręcznie, ale użyj .find(...) zamiast tego:

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

Również za pomocą .bulkWrite() , dostępny od MongoDB 3.2 , będzie o wiele bardziej wydajny niż wykonywanie pojedynczych aktualizacji.

Możliwe, że dzięki temu będziesz w stanie wykonać zapytanie w ciągu 10 minut życia kursora. Jeśli nadal zajmie to więcej czasu, kursor wygaśnie i i tak będziesz miał ten sam problem, co wyjaśniono poniżej:

Co się tutaj dzieje:

Error: getMore command failed może to być spowodowane przekroczeniem limitu czasu kursora, co jest związane z dwoma atrybutami kursora:

  • Limit czasu, który domyślnie wynosi 10 minut. Z dokumentów:

    Domyślnie serwer automatycznie zamyka kursor po 10 minutach bezczynności lub po wyczerpaniu kursora przez klienta.

  • Rozmiar wsadu, który wynosi 101 dokumentów lub 16 MB dla pierwszej partii i 16 MB, niezależnie od liczby dokumentów, dla kolejnych partii (od MongoDB 3.4 ). Z dokumentów:

    find() i aggregate() operacje mają domyślnie początkowy rozmiar partii 101 dokumentów. Kolejne operacje getMore wykonywane względem wynikowego kursora nie mają domyślnego rozmiaru wsadu, więc są ograniczone tylko przez rozmiar wiadomości wynoszący 16 megabajtów.

Prawdopodobnie zużywasz te początkowe 101 dokumentów, a następnie otrzymujesz partię 16 MB, która jest maksymalna, z dużo większą liczbą dokumentów. Ponieważ ich przetworzenie zajmuje więcej niż 10 minut, kursor na serwerze wygasa, a kiedy skończysz przetwarzanie dokumentów z drugiej partii i zażądasz nowej, kursor jest już zamknięty:

Gdy przejdziesz przez kursor i dojdziesz do końca zwróconej partii, jeśli jest więcej wyników, cursor.next() wykona operację getMore, aby pobrać następną partię.

Możliwe rozwiązania:

Widzę 5 możliwych sposobów rozwiązania tego problemu, 3 dobre, z ich zaletami i wadami, oraz 2 złe:

  1. 👍 Zmniejszenie rozmiaru partii, aby utrzymać kursor przy życiu.

  2. 👍 Usuń limit czasu z kursora.

  3. 👍 Spróbuj ponownie, gdy kursor wygaśnie.

  4. 👎 Ręczne sprawdzanie wyników w partiach.

  5. 👎 Pobierz wszystkie dokumenty, zanim kursor wygaśnie.

Należy zauważyć, że nie są one numerowane zgodnie z żadnymi określonymi kryteriami. Przeczytaj je i zdecyduj, który z nich najlepiej sprawdzi się w Twoim konkretnym przypadku.

1. 👍 Zmniejszenie rozmiaru partii, aby utrzymać kursor przy życiu

Jednym ze sposobów rozwiązania tego jest użycie cursor.bacthSize aby ustawić rozmiar partii na kursorze zwróconym przez find zapytanie pasujące do tych, które możesz przetworzyć w ciągu tych 10 minut:

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

Należy jednak pamiętać, że ustawienie bardzo konserwatywnego (małego) rozmiaru partii prawdopodobnie zadziała, ale będzie również wolniejsze, ponieważ teraz trzeba więcej razy uzyskiwać dostęp do serwera.

Z drugiej strony ustawienie go na wartość zbyt zbliżoną do liczby dokumentów, które można przetworzyć w ciągu 10 minut, oznacza, że ​​może się zdarzyć, że niektóre iteracje z jakiegoś powodu będą trwać nieco dłużej (inne procesy mogą zużywać więcej zasobów) , kursor i tak wygaśnie i ponownie pojawi się ten sam błąd.

2. 👍 Usuń limit czasu z kursora

Inną opcją jest użycie cursor.noCursorTimeout, aby zapobiec przekroczeniu limitu czasu kursora:

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

Jest to uważane za złą praktykę, ponieważ musisz ręcznie zamknąć kursor lub wyczerpać wszystkie jego wyniki, aby został automatycznie zamknięty:

Po ustawieniu noCursorTimeout opcja, musisz albo zamknąć kursor ręcznie za pomocą cursor.close() lub przez wyczerpanie wyników kursora.

Ponieważ chcesz przetworzyć wszystkie dokumenty w kursorze, nie musisz zamykać go ręcznie, ale nadal możliwe jest, że coś pójdzie nie tak w twoim kodzie i zostanie zgłoszony błąd, zanim skończysz, pozostawiając otwarty kursor .

Jeśli nadal chcesz korzystać z tego podejścia, użyj try-catch aby upewnić się, że zamkniesz kursor, jeśli coś pójdzie nie tak, zanim zużyjesz wszystkie jego dokumenty.

Uwaga Nie uważam tego za złe rozwiązanie (stąd 👍), ponieważ nawet uważam to za złą praktykę...:

  • Jest to funkcja obsługiwana przez sterownik. Jeśli było tak źle, ponieważ istnieją alternatywne sposoby obejścia problemów z limitem czasu, jak wyjaśniono w innych rozwiązaniach, nie będzie to obsługiwane.

  • Istnieją sposoby na bezpieczne korzystanie z niego, wystarczy zachować szczególną ostrożność.

  • Zakładam, że nie uruchamiasz tego rodzaju zapytań regularnie, więc szanse, że zaczniesz zostawiać otwarte kursory wszędzie, są niskie. Jeśli tak nie jest, a naprawdę musisz cały czas radzić sobie z takimi sytuacjami, nie ma sensu używać noCursorTimeout .

3. 👍 Spróbuj ponownie, gdy kursor wygaśnie

Zasadniczo umieszczasz swój kod w try-catch a kiedy pojawi się błąd, pojawi się nowy kursor, pomijając dokumenty, które już przetworzyłeś:

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

Pamiętaj, że aby to rozwiązanie zadziałało, musisz posortować wyniki.

Dzięki takiemu podejściu minimalizujesz liczbę żądań do serwera, używając maksymalnego możliwego rozmiaru partii 16 MB, bez konieczności zgadywania, ile dokumentów będziesz w stanie przetworzyć w ciągu 10 minut wcześniej. Dlatego jest również bardziej niezawodny niż poprzednie podejście.

4. 👎 Ręczne sprawdzanie wyników w partiach

Zasadniczo używasz skip(), limit() i sort() do wykonywania wielu zapytań z wieloma dokumentami, które Twoim zdaniem można przetworzyć w ciągu 10 minut.

Uważam to za złe rozwiązanie, ponieważ kierowca ma już możliwość ustawienia wielkości partii, więc nie ma powodu, aby robić to ręcznie, po prostu użyj rozwiązania 1 i nie wymyślaj koła od nowa.

Warto również wspomnieć, że ma te same wady co rozwiązanie 1,

5. 👎 Pobierz wszystkie dokumenty, zanim kursor wygaśnie

Prawdopodobnie wykonanie Twojego kodu zajmuje trochę czasu z powodu przetwarzania wyników, więc możesz najpierw pobrać wszystkie dokumenty, a następnie je przetworzyć:

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

Spowoduje to pobranie wszystkich partii jedna po drugiej i zamknięcie kursora. Następnie możesz przeglądać wszystkie dokumenty w results i rób to, co musisz.

Jeśli jednak masz problemy z przekroczeniem limitu czasu, istnieje prawdopodobieństwo, że Twój zestaw wyników jest dość duży, dlatego wyciągnięcie wszystkiego z pamięci może nie być najbardziej wskazane.

Uwaga na temat trybu migawki i duplikatów dokumentów

Może się zdarzyć, że niektóre dokumenty zostaną zwrócone wielokrotnie, jeśli interweniujące operacje zapisu przeniosą je z powodu wzrostu rozmiaru dokumentu. Aby rozwiązać ten problem, użyj cursor.snapshot() . Z dokumentów:

Dołącz metodę snapshot() do kursora, aby przełączyć tryb „migawka”. Gwarantuje to, że zapytanie nie zwróci dokumentu wielokrotnie, nawet jeśli interweniujące operacje zapisu spowodują przesunięcie dokumentu z powodu wzrostu rozmiaru dokumentu.

Pamiętaj jednak o jego ograniczeniach:

  • Nie działa z kolekcjami podzielonymi na fragmenty.

  • Nie działa z sort() lub hint() , więc nie będzie działać z rozwiązaniami 3 i 4.

  • Nie gwarantuje izolacji od wstawiania lub usuwania.

Zwróć uwagę, że w rozwiązaniu 5 okno czasowe przenoszenia dokumentów, które może powodować pobieranie duplikatów dokumentów, jest węższe niż w przypadku innych rozwiązań, więc możesz nie potrzebować snapshot() .

W twoim konkretnym przypadku, ponieważ kolekcja nazywa się snapshot , prawdopodobnie nie zmieni się, więc prawdopodobnie nie potrzebujesz snapshot() . Co więcej, wykonujesz aktualizacje dokumentów na podstawie ich danych i po zakończeniu aktualizacji ten sam dokument nie zostanie ponownie zaktualizowany, mimo że jest pobierany wiele razy, jak if warunek pominie to.

Uwaga o otwartych kursorach

Aby zobaczyć liczbę otwartych kursorów, użyj db.serverStatus().metrics.cursor .



  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Usuń pole znalezione w dowolnej tablicy mongodb

  2. Nie udało się połączyć z 127.0.0.1:27017, powód:errno:111 Połączenie odrzucone

  3. Jak przejść do produkcji z MongoDB — dziesięć najważniejszych wskazówek

  4. Wbudowany dokument bez tablicy?

  5. MongoDB $druga