Подтвердить что ты не робот

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

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

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

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

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

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

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

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

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

> db.audit.find({'userAgent.deviceType': 'MOBILE', 'user.userName': {$in: ['[email protected]']}}).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" : [
                        [
                                "[email protected]",
                                "[email protected]"
                        ]
                ],
                "timestamp" : [
                        [
                                {
                                        "$maxElement" : 1
                                },
                                {
                                        "$minElement" : 1
                                }
                        ]
                ]
        },
        "server" : "yarin:27017"
}

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

4b9b3361

Ответ 1

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

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

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

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

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

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

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

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

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

- EDIT -

Объяснение, которое вы опубликовали, является прекрасным примером проблемы с полями сканирования с низкой селективностью. Видимо, там много документов для "[email protected]". Теперь найти эти документы и отсортировать их по меткам времени очень быстро, потому что он поддерживается индексами высокой избирательности. К сожалению, поскольку существует только два типа устройств, 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.

Ответ 2

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

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

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

Ответ 3

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

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