MongoDB запрашивает производительность для более чем 5 миллионов записей


недавно мы попали в > 2 миллиона записей для одной из наших основных коллекций, и теперь мы начали страдать от серьезных проблем с производительностью в этой коллекции.

Они документы в коллекции имеют около 8 полей, которые можно фильтровать с помощью пользовательского интерфейса и результаты должны быть отсортированы по полю метки времени запись была обработана.

я добавил несколько составных индексов с отфильтрованными полями и меткой времени например:

db.events.ensureIndex({somefield: 1, timestamp:-1})

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

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

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

изменить: пример для запроса:

> db.audit.find({'userAgent.deviceType': 'MOBILE', 'user.userName': {$in: ['nickey@acme.com']}}).sort({timestamp: -1}).limit(25).explain()
{
        "cursor" : "BtreeCursor user.userName_1_timestamp_-1",
        "isMultiKey" : false,
        "n" : 0,
        "nscannedObjects" : 30060,
        "nscanned" : 30060,
        "nscannedObjectsAllPlans" : 120241,
        "nscannedAllPlans" : 120241,
        "scanAndOrder" : false,
        "indexOnly" : false,
        "nYields" : 1,
        "nChunkSkips" : 0,
        "millis" : 26495,
        "indexBounds" : {
                "user.userName" : [
                        [
                                "nickey@acme.com",
                                "nickey@acme.com"
                        ]
                ],
                "timestamp" : [
                        [
                                {
                                        "$maxElement" : 1
                                },
                                {
                                        "$minElement" : 1
                                }
                        ]
                ]
        },
        "server" : "yarin:27017"
}

обратите внимание, что deviceType имеет только 2 значения в моей коллекции.

3 60

3 ответа:

это поиск иглы в стоге сена. Нам нужен какой-то выход explain() для тех запросов, которые не работают хорошо. К сожалению, даже это исправит проблему только для этого конкретного запроса, поэтому вот стратегия, как подойти к этому:

  1. убедитесь, что это не из-за недостаточной оперативной памяти и чрезмерной подкачки
  2. включить профилировщик БД (с помощью db.setProfilingLevel(1, timeout) здесь timeout пороговое значение для количества миллисекунд, которое занимает запрос или команда, все медленнее будет регистрироваться)
  3. Проверьте медленные запросы в db.system.profile и выполнить запросы вручную с помощью explain()
  4. попробуйте определить медленные операции в explain() выход, например scanAndOrder или большие nscanned и т. д.
  5. причина о селективности запроса и можно ли улучшить запрос с помощью индекса на всех. Если нет, рассмотрите возможность отмены настройки фильтра для конечного пользователя или выдайте ему диалоговое окно с предупреждением о том, что операция может быть медленной.

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

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

допустим, у вас есть запрос для всех пользователей, с status "активные" и некоторые другие критерии. Но из 5 миллионов пользователей 3 миллиона активны, а 2 миллиона нет, поэтому более 5 миллионов записей имеют только два разных значения. Такой индекс обычно не помогает. Лучше сначала искать другие критерии, а затем Сканировать результаты. В среднем при возврате 100 документов вам придется отсканировать 167 документов, что не повредит производительности слишком сильно. Но все не так просто. Если основным критерием является joined_at дата пользователя и вероятность прекращения использования пользователями со временем высока, вам может потребоваться сканирование тысячи документов, прежде чем найти сотню матчей.

так что оптимизация очень сильно зависит от данных (не только его структура, но и данные), ее внутренние корреляции и ваш шаблоны запросов.

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

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

если все остальное не удается, и если вам действительно нужна такая гибкость в фильтрах, возможно, стоит рассмотреть отдельный найдите БД, которая поддерживает пересечения индексов, извлеките идентификаторы mongo оттуда, а затем получите результаты от mongo с помощью $in. Но это чревато собственными опасностями.

-- EDIT --

объяснение, которое вы опубликовали, является прекрасным примером проблемы со сканированием полей с низкой селективностью. По-видимому, есть много документов для ...nickey@acme.com теперь найти эти документы и отсортировать их по убыванию по метке времени довольно быстро, потому что это поддерживается высокие показатели селективности. К сожалению, поскольку существует только два типа устройств, mongo необходимо сканировать 30060 документов, чтобы найти первый, который соответствует "мобильный".

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

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

a) ensureIndex({'username': 1, 'userAgent.deviceType' : 1, 'timestamp' :-1})

или

b) ensureIndex({'userAgent.deviceType' : 1, 'username' : 1, 'timestamp' :-1})

к сожалению, это означает, что запросы типа find({"username" : "foo"}).sort({"timestamp" : -1});не могу использовать один и тот же индекс больше, так, как описано, количество индексов будет расти очень быстро.

боюсь, что в настоящее время нет очень хорошего решения для этого с помощью mongodb.

Mongo использует только 1 индекс на запрос. Поэтому, если вы хотите отфильтровать 2 поля, mongo будет использовать индекс с одним из полей, но все равно нужно сканировать все подмножество.

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

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

Если вы используете $in, mongodb никогда не использует индекс. Измените свой запрос, удалив этот $in. Он должен использовать индекс, и это даст лучшую производительность, чем то, что вы получили ранее.

http://docs.mongodb.org/manual/core/query-optimization/