MongoDB запитує продуктивність для понад 5 мільйонів записів


78

Нещодавно ми досягли> 2 мільйонів рекордів для однієї з наших основних колекцій, і тепер ми почали страждати через серйозні проблеми з продуктивністю цієї колекції.

Ці документи у колекції містять близько 8 полів, які можна відфільтрувати за допомогою інтерфейсу користувача, а результати передбачається відсортувати за полем мітки часу, в якому був оброблений запис.

Я додав кілька складених індексів із відфільтрованими полями та позначкою часу, наприклад:

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

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

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

Мені було цікаво, чи варто зараз робити шардінг .. але незабаром ми почнемо мати в цій колекції близько 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 значення в моїй колекції.


Ви використовуєте limitаргумент?
Джо

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

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

2
Енвер, коли в колекції було близько 1-2 мільйонів записів, я почав відчувати деякі проблеми з ефективністю (час запиту 5-50 секунд). Потім я додав індекси, і я отримав розумну ефективність для запитів <1000 мс, тепер запити займають від 20 мс до 60 секунд, але все залежить від розподілу значень полів, які фільтруються, і наскільки "корисними" були індекси.
Ярін Міран,

Які запити повільні? Простий запит без фільтрування вже повільний? Або лише запити, відфільтровані одним полем, повільні? Або двома полями?
Джо

Відповіді:


71

Це пошук голки в копиці сіна. Нам знадобиться певний результат 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. Але це загрожує власними небезпеками.

- РЕДАГУВАТИ -

Пояснення, яке ви опублікували, є прекрасним прикладом проблеми зі скануванням полів низької селективності. Очевидно, що на "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 є кілька клієнтських баз даних, де кожна має таку величезну колекцію. Ми боїмося, що індексація всієї цієї колекції сильно погіршить продуктивність, оскільки нам потрібно мати величезну кількість оперативної пам'яті для підтримки одночасних запитів від різних користувачів. У вас є пропозиція щодо хорошої пошукової бази даних для цієї мети?
Ярін Міран,

Я думаю, це залежить від функцій пошуку, які вам потрібні. Для основ, будь-який db, який підтримує перетинання індексу, повинен робити. Якщо вам потрібен повнотекстовий пошук, гранований пошук або навіть нарізки та кістки, все стає складним, і існує цілий всесвіт інструментів, від SolR, еластичного пошуку до кубів OLAP. Поки ви на цьому, ви також можете проголосувати за перетин індексу в MongoDB Jira: jira.mongodb.org/browse/SERVER-3071
mnemosyn

Я думаю, що ми підемо на ElasticSearch для цієї конкретної таблиці. Що ви думаєте про це ?
Ярін Міран,

2
Чудова відповідь. Мені було б цікаво дізнатись, що змінилося за останні 4,5 роки у цьому плані.
Даніель Хільгарт

2

Mongo використовує лише 1 індекс на запит. Отже, якщо ви хочете відфільтрувати 2 поля, mongo використовуватиме індекс з одним із полів, але все одно повинен просканувати всю підмножину.

Це означає, що в основному вам потрібен індекс для кожного типу запиту, щоб досягти найкращої продуктивності.

Залежно від ваших даних, може бути непоганою ідеєю мати один запит на поле та обробляти результати у вашому додатку. Таким чином вам знадобляться лише індекси в усіх полях, але для обробки даних може бути забагато даних.


-1

Якщо ви використовуєте $ in, mongodb ніколи не використовує INDEX. Змініть свій запит, видаливши цей $ in. Він повинен використовувати індекс, і це дасть кращу продуктивність, ніж те, що ви отримали раніше.

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


15
FYI, $ in використовує індекс, це $ nin, який не використовує індекс. Проблема в $ in з того, що ми пережили, полягає в тому, що mongo виконує запит на значення в $ in. Незважаючи на використання індексу для кожного запиту, це надзвичайно повільно ..
Ярін Міран,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.