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

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

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

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

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

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

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

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

EDIT: пример для запроса:

> 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 ответов


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

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

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

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

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

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

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

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

если все остальное не удается, и если вам действительно нужна такая большая гибкость в фильтрах, возможно, стоит рассмотреть отдельный поиск БД, которая поддерживает пересечение индексов, получить идентификаторы монго оттуда, а затем получить результаты от монго с помощью $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/