2013-10-24 3 views
57

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

Эти документы в коллекции содержат около 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 имеет только два значения в моей коллекции.

+0

Используете ли вы аргумент 'limit'? – Joe

+0

хороший! Вероятно, у меня будет подобный сценарий в будущем, и ответы могут быть полезны и мне. Насколько велика ваша база данных с такой коллекцией? Сколько времени потребовалось, прежде чем запрашивать эти пары с 8 КВ, прежде чем вы достигнете 2 мили и сколько времени это займет сейчас? (просто любопытно) – anvarik

+0

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

ответ

54

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

  1. Убедитесь, что это не из-за недостатка оперативной памяти и чрезмерного пейджинга
  2. Включение профилировщика DB (с использованием 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 -

Explain, вы вывесили красивый пример проблемы с сканирования полей низкой селективности. По-видимому, есть много документов для «[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});can't use the same index anymore, так, как описано выше, количество индексов будет расти очень быстро.

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

+0

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

+0

Я обновил OP с объяснением запроса –

+0

Думаю, это зависит от функций поиска, которые вам нужны. Для основ необходимо использовать любой db, поддерживающий перекрестки индексов. Если вам нужен полнотекстовый поиск, фасетный поиск или даже фрагмент и кости, все становится сложным, и есть целая вселенная инструментов, от SolR, Elastic Search до кубов OLAP. Пока вы на нем, вы также можете проголосовать за перекресток индексов в MongoDB Jira: https://jira.mongodb.org/browse/SERVER-3071 – mnemosyn

0

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

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

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

0

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

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

+9

FYI, $ in использует индекс, это $ nin, который не использует индекс. Проблема в $ in от того, что мы испытали, заключается в том, что монго выполняет запрос за значение в $ in. Несмотря на использование индекса для каждого запроса, он очень медленный. –

Смежные вопросы