MongoDB
 sql >> Database >  >> NoSQL >> MongoDB

MongoDB - Errore:comando getMore non riuscito:cursore non trovato

MODIFICA - Prestazioni query:

Come ha sottolineato @NeilLunn nei suoi commenti, non dovresti filtrare i documenti manualmente, ma usare .find(...) per quello invece:

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

Inoltre, usando .bulkWrite() , disponibile a partire da MongoDB 3.2 , sarà molto più efficiente rispetto all'esecuzione di aggiornamenti individuali.

È possibile che, con ciò, tu sia in grado di eseguire la tua query entro i 10 minuti di vita del cursore. Se ci vuole ancora più di quello, il tuo cursore scadrà e avrai comunque lo stesso problema, che è spiegato di seguito:

Cosa sta succedendo qui:

Error: getMore command failed potrebbe essere dovuto a un timeout del cursore, correlato a due attributi del cursore:

  • Limite di timeout, che è di 10 minuti per impostazione predefinita. Dai documenti:

    Per impostazione predefinita, il server chiuderà automaticamente il cursore dopo 10 minuti di inattività o se il client ha esaurito il cursore.

  • Dimensione del batch, che è di 101 documenti o 16 MB per il primo batch, e 16 MB, indipendentemente dal numero di documenti, per i batch successivi (a partire da MongoDB 3.4 ). Dai documenti:

    find() e aggregate() per impostazione predefinita, le operazioni hanno una dimensione batch iniziale di 101 documenti. Le successive operazioni getMore eseguite sul cursore risultante non hanno dimensioni batch predefinite, quindi sono limitate solo dalla dimensione del messaggio di 16 megabyte.

Probabilmente stai consumando quei 101 documenti iniziali e quindi ottieni un batch di 16 MB, che è il massimo, con molti più documenti. Poiché sono necessari più di 10 minuti per elaborarli, il cursore sul server scade e, quando hai finito di elaborare i documenti nel secondo batch e ne richiedi uno nuovo, il cursore è già chiuso:

Mentre scorri il cursore e raggiungi la fine del batch restituito, se sono presenti più risultati, cursor.next() eseguirà un'operazione getMore per recuperare il batch successivo.

Possibili soluzioni:

Vedo 5 possibili modi per risolvere questo problema, 3 buoni, con i loro pro e contro, e 2 cattivi:

  1. 👍 Ridurre la dimensione del batch per mantenere vivo il cursore.

  2. 👍 Rimuovi il timeout dal cursore.

  3. 👍 Riprova quando il cursore scade.

  4. 👎 Interroga manualmente i risultati in batch.

  5. 👎 Ottieni tutti i documenti prima della scadenza del cursore.

Si noti che non sono numerati secondo criteri specifici. Leggili e decidi quale funziona meglio per il tuo caso particolare.

1. 👍 Ridurre la dimensione del batch per mantenere vivo il cursore

Un modo per risolverlo è usare cursor.bacthSize per impostare la dimensione del batch sul cursore restituito dal tuo find query in modo che corrisponda a quelle che puoi elaborare in quei 10 minuti:

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

Tuttavia, tieni presente che l'impostazione di una dimensione batch molto conservatrice (piccola) probabilmente funzionerà, ma sarà anche più lenta, poiché ora devi accedere al server più volte.

D'altra parte, impostarlo su un valore troppo vicino al numero di documenti che puoi elaborare in 10 minuti significa che è possibile che se alcune iterazioni impiegano un po' più di tempo per essere elaborate per qualsiasi motivo (altri processi potrebbero consumare più risorse) , il cursore scadrà comunque e riceverai di nuovo lo stesso errore.

2. 👍 Rimuovi il timeout dal cursore

Un'altra opzione consiste nell'usare cursor.noCursorTimeout per impedire il timeout del cursore:

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

Questa è considerata una cattiva pratica in quanto dovresti chiudere il cursore manualmente o esaurire tutti i suoi risultati in modo che venga chiuso automaticamente:

Dopo aver impostato il noCursorTimeout opzione, devi chiudere il cursore manualmente con cursor.close() o esaurendo i risultati del cursore.

Poiché vuoi elaborare tutti i documenti nel cursore, non avresti bisogno di chiuderlo manualmente, ma è comunque possibile che qualcos'altro vada storto nel tuo codice e venga generato un errore prima che tu abbia finito, lasciando così aperto il cursore .

Se vuoi ancora usare questo approccio, usa un try-catch per assicurarti di chiudere il cursore se qualcosa va storto prima di consumare tutti i suoi documenti.

Nota che non la considero una cattiva soluzione (quindi il 👍), in quanto anche pensata sia considerata una cattiva pratica...:

  • È una funzionalità supportata dal driver. Se fosse così grave, poiché ci sono modi alternativi per aggirare i problemi di timeout, come spiegato nelle altre soluzioni, questo non sarà supportato.

  • Ci sono modi per usarlo in sicurezza, è solo questione di essere più cauti con esso.

  • Presumo che tu non stia eseguendo questo tipo di query regolarmente, quindi le possibilità che inizi a lasciare i cursori aperti ovunque sono basse. Se questo non è il caso, e hai davvero bisogno di affrontare queste situazioni tutto il tempo, allora ha senso non usare noCursorTimeout .

3. 👍 Riprova quando il cursore scade

Fondamentalmente, inserisci il tuo codice in un try-catch e quando ricevi l'errore, ottieni un nuovo cursore che salta i documenti che hai già elaborato:

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

Nota che devi ordinare i risultati affinché questa soluzione funzioni.

Con questo approccio, riduci al minimo il numero di richieste al server utilizzando la dimensione batch massima possibile di 16 MB, senza dover indovinare quanti documenti sarai in grado di elaborare in 10 minuti in anticipo. Pertanto, è anche più robusto dell'approccio precedente.

4. 👎 Interroga manualmente i risultati in batch

Fondamentalmente, usi skip(), limit() e sort() per eseguire più query con un numero di documenti che pensi di poter elaborare in 10 minuti.

Considero questa una cattiva soluzione perché il conducente ha già la possibilità di impostare la dimensione del batch, quindi non c'è motivo di farlo manualmente, basta usare la soluzione 1 e non reinventare la ruota.

Inoltre, vale la pena ricordare che presenta gli stessi inconvenienti della soluzione 1,

5. 👎 Ottieni tutti i documenti prima della scadenza del cursore

Probabilmente il tuo codice sta impiegando del tempo per essere eseguito a causa dell'elaborazione dei risultati, quindi potresti prima recuperare tutti i documenti e poi elaborarli:

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

Questo recupererà tutti i batch uno dopo l'altro e chiuderà il cursore. Quindi, puoi scorrere tutti i documenti all'interno di results e fai quello che devi fare.

Tuttavia, se riscontri problemi di timeout, è probabile che il tuo set di risultati sia piuttosto grande, quindi estrarre tutto in memoria potrebbe non essere la cosa più consigliabile da fare.

Nota sulla modalità snapshot e sui documenti duplicati

È possibile che alcuni documenti vengano restituiti più volte se le operazioni di scrittura intermedie li spostano a causa dell'aumento delle dimensioni del documento. Per risolvere questo problema, usa cursor.snapshot() . Dai documenti:

Aggiungi il metodo snapshot() a un cursore per attivare la modalità "istantanea". Ciò garantisce che la query non restituirà un documento più volte, anche se le operazioni di scrittura intermedie comportano uno spostamento del documento a causa dell'aumento delle dimensioni del documento.

Tuttavia, tieni presente i suoi limiti:

  • Non funziona con le raccolte partizionate.

  • Non funziona con sort() o hint() , quindi non funzionerà con le soluzioni 3 e 4.

  • Non garantisce l'isolamento da inserimenti o eliminazioni.

Nota con la soluzione 5 la finestra temporale per spostare i documenti che potrebbe causare il recupero di documenti duplicati è più ristretta rispetto alle altre soluzioni, quindi potresti non aver bisogno di snapshot() .

Nel tuo caso particolare, poiché la raccolta si chiama snapshot , probabilmente non cambierà, quindi probabilmente non hai bisogno di snapshot() . Inoltre, stai aggiornando i documenti in base ai loro dati e, una volta eseguito l'aggiornamento, lo stesso documento non verrà aggiornato nuovamente anche se viene recuperato più volte, come il if condizione lo salterà.

Nota sui cursori aperti

Per vedere il conteggio dei cursori aperti, usa db.serverStatus().metrics.cursor .