Что такое курсор в MongoDB?

Мы обеспокоены тем, что в конце концов asList cursor not found exceptions для некоторых asList Queries asList и я обнаружил подсказку для SO, что это может быть довольно много памяти.

Теперь я хотел бы узнать немного больше об истории вопроса: может ли кто-нибудь объяснить (на английском языке), что на самом деле представляет собой Cursor (в MongoDB)? Почему его можно оставить открытым или не найти?


Документация определяет курсор как:

Указатель на набор результатов запроса. Клиенты могут перемещаться по курсору для получения результатов. По умолчанию время ожидания курсора после 10 минут бездействия

Но это не очень показательно. Может быть, было бы полезно определить batch для результатов запроса, потому что в документации также говорится:

Сервер MongoDB возвращает результаты запроса в пакетном режиме. Размер пакета не будет превышать максимальный размер документа BSON. Для большинства запросов первая партия возвращает 101 документ или достаточно документов, превышающих 1 мегабайт. Размер последующей партии составляет 4 мегабайта. [...] Для запросов, которые включают операцию сортировки без индекса, сервер должен загрузить все документы в память, чтобы выполнить сортировку, прежде чем возвращать какие-либо результаты.

Примечание: в наших запросах мы вообще не используем операторы сортировки, но также не limit и не offset.

Ответ 1

Я не имею в виду эксперта mongodb, но я просто хочу добавить некоторые наблюдения из работы в системе mongo среднего размера за последний год. Также благодаря @xameeramir за отличную прогулку о том, как работают курсоры в целом.

Причинами исключения курсора могут быть несколько. В этом ответе объясняется тот, который я заметил.

Курсор живет на стороне сервера. Он не распространяется по набору реплик, а существует на экземпляре, который является основным во время создания. Это означает, что если другой экземпляр берет на себя как основной, курсор будет потерян клиенту. Если старый первичный объект все еще работает, и вокруг него все еще может быть, но не используется. Наверное, через некоторое время его убирают. Поэтому, если ваш набор реплик mongo неустойчив или у вас есть шаткая сеть перед вами, вам не повезло при выполнении длинных запросов.

Если полное содержание того, что курсор хочет вернуть, не помещается в память на сервере, запрос может быть очень медленным. ОЗУ на ваших серверах должно быть больше, чем самый большой запрос, который вы запускаете.

Все это частично можно избежать, если лучше спроектировать. Для случая использования с большими длинными запросами вам может быть лучше с несколькими небольшими коллекциями баз данных, а не с большим.

Ответ 2

Здесь сравнение между toArray() и курсорами после find() в драйвере MongoDB Node.js. Общий код:

var MongoClient = require('mongodb').MongoClient,
assert = require('assert');

MongoClient.connect('mongodb://localhost:27017/crunchbase', function (err, db) {
    assert.equal(err, null);
    console.log('Successfully connected to MongoDB.');

    const query = { category_code: "biotech" };

    // toArray() vs. cursor code goes here
});

Здесь код toArray() который идет в разделе выше.

    db.collection('companies').find(query).toArray(function (err, docs) {
        assert.equal(err, null);
        assert.notEqual(docs.length, 0);

        docs.forEach(doc => {
            console.log('${doc.name} is a ${doc.category_code} company.');
        });

        db.close();
    });

Согласно документации,

Вызывающий отвечает за то, чтобы убедиться, что памяти достаточно для сохранения результатов.

Здесь подход, основанный на курсоре, используя метод cursor.forEach():

    const cursor = db.collection('companies').find(query);

    cursor.forEach(
        function (doc) {
            console.log('${doc.name} is a ${doc.category_code} company.');
        },
        function (err) {
            assert.equal(err, null);
            return db.close();
        }
    );
});

При использовании метода forEach() вместо извлечения всех данных из памяти мы передаем данные в наше приложение. find() немедленно создает курсор, потому что он на самом деле не делает запрос к базе данных, пока мы не попытаемся использовать некоторые документы, которые он предоставит. cursor должен описать наш запрос. Второй параметр к cursor.forEach показывает, что делать при возникновении ошибки.

В первоначальной версии приведенного выше кода именно toArray() вызывал вызов базы данных. Это означало, что нам нужны ВСЕ документы и мы хотим, чтобы они были в array.

Обратите внимание, что MongoDB возвращает данные в пакетном режиме. На рисунке ниже показаны запросы от курсоров (из приложения) к MongoDB:

MongoDB cursor graphic

forEach масштабируется лучше, чем toArray потому что мы можем обрабатывать документы по мере их поступления, пока не достигнем конца. Сравните это с toArray - где мы ожидаем получения ВСЕХ документов и построения всего массива. Это означает, что мы не получаем никаких преимуществ от того факта, что драйвер и система базы данных работают вместе для пакетной обработки результатов для вашего приложения. Пакетирование предназначено для обеспечения эффективности с точки зрения накладных расходов памяти и времени выполнения. Воспользуйтесь этим в своем приложении, если можете.

Ответ 3

Эта ошибка также возникает, когда большой набор данных и пакетная обработка этих данных, и каждый пакет занимает больше времени, чем время жизни курсора по умолчанию.

Затем вам нужно изменить это время по умолчанию, чтобы сообщить монго, что этот курсор не истечет, пока обработка не будет завершена.

Не проверяйте документацию No TimeOut