MongoDB
 sql >> Teknologi Basis Data >  >> NoSQL >> MongoDB

MongoDB - Kesalahan:perintah getMore gagal:Kursor tidak ditemukan

EDIT - Performa kueri:

Seperti yang ditunjukkan @NeilLunn dalam komentarnya, Anda tidak boleh memfilter dokumen secara manual, tetapi gunakan .find(...) untuk itu sebagai gantinya:

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

Juga, menggunakan .bulkWrite() , tersedia mulai dari MongoDB 3.2 , akan jauh lebih berperforma daripada melakukan pembaruan individual.

Ada kemungkinan bahwa, dengan itu, Anda dapat menjalankan kueri Anda dalam 10 menit masa pakai kursor. Jika masih membutuhkan lebih dari itu, kursor Anda akan kedaluwarsa dan Anda akan memiliki masalah yang sama, yang dijelaskan di bawah ini:

Apa yang terjadi di sini:

Error: getMore command failed mungkin karena batas waktu kursor, yang terkait dengan dua atribut kursor:

  • Batas waktu habis, yaitu 10 menit secara default. Dari dokumen:

    Secara default, server akan secara otomatis menutup kursor setelah 10 menit tidak aktif, atau jika klien telah kehabisan kursor.

  • Ukuran batch, yaitu 101 dokumen atau 16 MB untuk batch pertama, dan 16 MB, terlepas dari jumlah dokumen, untuk batch berikutnya (pada MongoDB 3.4 ). Dari dokumen:

    find() dan aggregate() operasi memiliki ukuran batch awal 101 dokumen secara default. Operasi getMore berikutnya yang dilakukan terhadap kursor yang dihasilkan tidak memiliki ukuran batch default, sehingga hanya dibatasi oleh ukuran pesan 16 megabyte.

Mungkin Anda menggunakan 101 dokumen awal itu dan kemudian mendapatkan kumpulan 16 MB, yang merupakan maksimum, dengan lebih banyak dokumen. Karena memerlukan lebih dari 10 menit untuk memprosesnya, kursor di server akan habis waktu dan, pada saat Anda selesai memproses dokumen di batch kedua dan meminta yang baru, kursor sudah ditutup:

Saat Anda mengulangi kursor dan mencapai akhir kumpulan yang dikembalikan, jika ada lebih banyak hasil, cursor.next() akan melakukan operasi getMore untuk mengambil kumpulan berikutnya.

Solusi yang memungkinkan:

Saya melihat 5 kemungkinan cara untuk menyelesaikan ini, 3 cara yang baik, dengan pro dan kontra, dan 2 cara yang buruk:

  1. 👍 Mengurangi ukuran batch untuk menjaga kursor tetap hidup.

  2. 👍 Hapus batas waktu dari kursor.

  3. 👍 Coba lagi saat kursor kedaluwarsa.

  4. Query hasil dalam batch secara manual.

  5. Dapatkan semua dokumen sebelum kursor berakhir.

Perhatikan bahwa mereka tidak diberi nomor mengikuti kriteria tertentu. Baca semuanya dan putuskan mana yang paling cocok untuk kasus Anda.

1. 👍 Mengurangi ukuran batch untuk menjaga kursor tetap hidup

Salah satu cara untuk mengatasinya adalah menggunakan cursor.bacthSize untuk mengatur ukuran batch pada kursor yang dikembalikan oleh find . Anda kueri untuk mencocokkan yang dapat Anda proses dalam 10 menit itu:

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

Namun, perlu diingat bahwa menyetel ukuran batch yang sangat konservatif (kecil) mungkin akan berhasil, tetapi juga akan lebih lambat, karena sekarang Anda perlu mengakses server lebih sering.

Di sisi lain, menyetelnya ke nilai yang terlalu dekat dengan jumlah dokumen yang dapat Anda proses dalam 10 menit berarti ada kemungkinan bahwa jika beberapa iterasi membutuhkan waktu lebih lama untuk diproses karena alasan apa pun (proses lain mungkin menghabiskan lebih banyak sumber daya) , kursor akan tetap kedaluwarsa dan Anda akan mendapatkan kesalahan yang sama lagi.

2. 👍 Hapus batas waktu dari kursor

Pilihan lainnya adalah dengan menggunakan cursor.noCursorTimeout untuk mencegah kursor dari waktu habis:

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

Ini dianggap sebagai praktik yang buruk karena Anda harus menutup kursor secara manual atau membuang semua hasilnya sehingga ditutup secara otomatis:

Setelah mengatur noCursorTimeout pilihan, Anda harus menutup kursor secara manual dengan cursor.close() atau dengan menguras hasil kursor.

Karena Anda ingin memproses semua dokumen di kursor, Anda tidak perlu menutupnya secara manual, tetapi masih ada kemungkinan ada kesalahan lain dalam kode Anda dan kesalahan muncul sebelum Anda selesai, sehingga kursor tetap terbuka. .

Jika Anda masih ingin menggunakan pendekatan ini, gunakan try-catch untuk memastikan Anda menutup kursor jika terjadi kesalahan sebelum Anda menggunakan semua dokumennya.

Catatan Saya tidak menganggap ini sebagai solusi yang buruk (oleh karena itu 👍), bahkan menganggapnya sebagai praktik yang buruk...:

  • Ini adalah fitur yang didukung oleh driver. Jika sangat buruk, karena ada cara alternatif untuk mengatasi masalah waktu tunggu, seperti yang dijelaskan dalam solusi lain, ini tidak akan didukung.

  • Ada beberapa cara untuk menggunakannya dengan aman, hanya saja harus ekstra hati-hati.

  • Saya berasumsi Anda tidak menjalankan kueri semacam ini secara teratur, jadi kemungkinan Anda mulai membiarkan kursor terbuka di mana-mana adalah rendah. Jika tidak demikian, dan Anda benar-benar harus menghadapi situasi ini setiap saat, maka masuk akal untuk tidak menggunakan noCursorTimeout .

3. 👍 Coba lagi saat kursor kedaluwarsa

Pada dasarnya, Anda memasukkan kode Anda ke dalam try-catch dan ketika Anda mendapatkan kesalahan, Anda mendapatkan kursor baru yang melewatkan dokumen yang telah Anda proses:

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

Perhatikan bahwa Anda perlu mengurutkan hasil agar solusi ini berfungsi.

Dengan pendekatan ini, Anda meminimalkan jumlah permintaan ke server dengan menggunakan ukuran batch maksimum 16 MB, tanpa harus menebak berapa banyak dokumen yang dapat Anda proses dalam 10 menit sebelumnya. Oleh karena itu, pendekatan ini juga lebih kuat dari pendekatan sebelumnya.

4. Kueri hasil dalam batch secara manual

Pada dasarnya, Anda menggunakan skip(), limit() dan sort() untuk melakukan banyak kueri dengan sejumlah dokumen yang menurut Anda dapat diproses dalam 10 menit.

Saya menganggap ini sebagai solusi yang buruk karena pengemudi sudah memiliki opsi untuk mengatur ukuran batch, jadi tidak ada alasan untuk melakukannya secara manual, cukup gunakan solusi 1 dan jangan menemukan kembali kemudi.

Juga, perlu disebutkan bahwa ia memiliki kelemahan yang sama dari solusi 1,

5. Dapatkan semua dokumen sebelum kursor berakhir

Mungkin kode Anda membutuhkan waktu untuk dieksekusi karena pemrosesan hasil, sehingga Anda dapat mengambil semua dokumen terlebih dahulu dan kemudian memprosesnya:

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

Ini akan mengambil semua kumpulan satu demi satu dan menutup kursor. Kemudian, Anda dapat mengulang semua dokumen di dalam results dan lakukan apa yang perlu Anda lakukan.

Namun, jika Anda mengalami masalah waktu tunggu, kemungkinan hasil yang Anda set cukup besar, sehingga menarik semua yang ada di memori mungkin bukan hal yang paling disarankan untuk dilakukan.

Catatan tentang mode snapshot dan dokumen duplikat

Ada kemungkinan bahwa beberapa dokumen dikembalikan beberapa kali jika intervensi operasi penulisan memindahkannya karena pertumbuhan ukuran dokumen. Untuk mengatasi ini, gunakan cursor.snapshot() . Dari dokumen:

Tambahkan metode snapshot() ke kursor untuk mengaktifkan mode "snapshot". Ini memastikan bahwa kueri tidak akan mengembalikan dokumen beberapa kali, bahkan jika intervensi operasi tulis mengakibatkan pemindahan dokumen karena pertumbuhan ukuran dokumen.

Namun, perlu diingat batasannya:

  • Ini tidak berfungsi dengan koleksi yang di-shard.

  • Itu tidak bekerja dengan sort() atau hint() , sehingga tidak akan bekerja dengan solusi 3 dan 4.

  • Itu tidak menjamin isolasi dari penyisipan atau penghapusan.

Catatan dengan solusi 5 jendela waktu untuk memindahkan dokumen yang dapat menyebabkan pengambilan dokumen duplikat lebih sempit dibandingkan dengan solusi lain, jadi Anda mungkin tidak memerlukan snapshot() .

Dalam kasus khusus Anda, karena koleksinya disebut snapshot , mungkin tidak akan berubah, jadi Anda mungkin tidak perlu snapshot() . Selain itu, Anda melakukan pembaruan pada dokumen berdasarkan datanya dan, setelah pembaruan selesai, dokumen yang sama tidak akan diperbarui lagi meskipun diambil beberapa kali, seperti if kondisi akan melewatinya.

Catatan tentang kursor yang terbuka

Untuk melihat jumlah kursor yang terbuka, gunakan db.serverStatus().metrics.cursor .



  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Tidak dapat mengautentikasi di mongodb dengan PHP

  2. MongoDB, kinerja kueri dengan ekspresi reguler pada bidang yang diindeks

  3. Bagaimana Parameter arrayFilters Bekerja di MongoDB

  4. mongodb dapatkan _id sebagai string dalam permintaan pencarian

  5. Mongo Change Streams berjalan beberapa kali (semacam):Aplikasi Node menjalankan beberapa instance