Повний посібник з створення ефективних індексів у MongoDB 🛠️

#MongoDB #Індекси #БазаДаних #Продуктивність #ОптимізаціяЗапитів #КращіПрактики

Покращуйте продуктивність своїх запитів у MongoDB, створюючи ефективні та оптимізовані індекси.

pic

MongoDb

Чому Індекси Важливі? 📖

Уявіть собі книгу без індексу. Кожного разу, коли вам потрібно знайти інформацію, вам доведеться читати сторінка за сторінкою. Саме так працює MongoDB, коли немає індексів: вона перевіряє всі документи колекції (це називається COLLSCAN).

При наявності правильно налаштованих індексів:

  • Запити стають швидшими. Аналізуються лише необхідні документи.
  • Продуктивність покращується. Менше часу та ресурсів витрачається.

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

Стратегії Індексації: Як Почати? 🎯

Мапуйте Найпоширеніші Запити

Не намагайтеся здогадуватися, які індекси створювати. Використовуйте інструменти, такі як:

  • Query Profiler (Профайлер Запитів): допомагає визначити, які поля найчастіше використовуються в пошукових запитах.
  • Журнали MongoDB: виявляють шаблони доступу.

Практична порада: Пріоритетно створюйте індекси для найбільш повільних та частих запитів. Метою є максимізувати позитивний вплив індексу.

Збалансуйте Читання і Запис

Індекси чудово підходять для читання, але:

  • Кожен запис, оновлення чи видалення вимагає оновлення індексу.
  • Велика кількість операцій запису з індексами може знизити продуктивність.

Як ухвалити рішення?

  • Протестуйте з малими змінами та моніторьте результати.
  • Більшість колекцій виграють від 5-10 добре спланованих індексів.
  • Для колекцій з інтенсивними операціями запису почніть з мінімальної кількості індексів, додаючи їх за потребою.

Використовуйте explain() для Діагностики в MongoDB 🔍

Функція explain() — потужний інструмент для діагностики та оптимізації запитів у MongoDB. Завдяки їй ви можете перевірити, як база даних виконує запит, виявити «вузькі місця» та налаштувати індекси для покращення продуктивності.

Ось все, що вам потрібно знати для ефективного використання explain().

Чому Використовувати explain()?

MongoDB приймає рішення про виконання запиту на основі доступних індексів, але не завжди обирає найефективніший шлях. З explain() ви можете:

  • Визначити, чи використовується індекс.
  • Виявити неефективні операції, такі як COLLSCAN (повний скан колекції) та SORT (сортування в пам'яті).
  • Оцінити вплив нещодавно створених індексів на запити.

Приклад:

db.records.find({ a: 5 }).sort({ b: 1 }).explain("executionStats")

Параметр "executionStats" надає детальну статистику про виконання запиту.

Основні Поля в Результатах explain()

Коли ви виконуєте explain(), MongoDB надає детальну інформацію. Ось основні поля, які слід розуміти при інтерпретації результатів:

1: totalDocsExamined

  • Загальна кількість документів, перевірених під час виконання запиту.
  • Що шукати:
    • Якщо це значення набагато більше за nReturned, запит неефективний.
    • Ідеально, якщо totalDocsExamined близьке до кількості повернених документів.

2: nReturned

  • Кількість документів, що були повернуті запитом.
  • Порівняйте з totalDocsExamined для оцінки ефективності:
    • totalDocsExamined ≈ nReturned: запит ефективний.
    • totalDocsExamined >> nReturned: запит сканує багато непотрібних документів.

3: Stage

  • Показує етап виконання, який виконує MongoDB.
  • Типові значення:
    • COLLSCAN: Повний скан колекції. Сигналізує, що для запиту немає налаштованого індексу (НЕ ПОВИННО БУТИ COLLSCAN).
    • IXSCAN: Використання індексу. Вказує, що запит оптимізований для зазначеного індексу.
    • SORT: Сортування, виконане в пам'яті.
      Він також охоплює важливість створення 2dsphere індексу, який є критичним для забезпечення швидких та ефективних геопросторових запитів на великих наборах даних.

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

// Перегляньте повний код на GitHub, не забудьте поставити зірочку 🙂

[

GitHub - ASMohamedFaheemAnver/BasicGeoSearchInMongoDB: Як здійснювати базовий гео-пошук у MongoDB!

Як здійснювати базовий гео-пошук у MongoDB! Підтримуйте розвиток ASMohamedFaheemAnver/BasicGeoSearchInMongoDB...

github.com

](https://github.com/ASMohamedFaheemAnver/BasicGeoSearchInMongoDB?source=post_page-----9f6ccd3a790f--------------------------------)
Це відбувається тому, що предикати інтервалу порушують ефективне використання індексу для наступних полів.

⚠️ Примітка: Оператори, такі як $regex, $ne, $nin вважаються інтервалами і повинні бути в кінці.

Практичні Приклади

Правильно Спланований Індекс ✅

// Запит:  
db.records.find({ a: 5, b: 10, d: { $gt: 10 }}).sort({ c: 1 })  

// Складений індекс згідно з порядком ESR  
db.records.createIndex({ a: 1, b: 1, c: 1, d: 1 })
  • a: Поле рівності (a: 5).
  • b: Поле рівності (b: 10).
  • c: Поле сортування (sort({ c: 1 })).
  • d: Поле інтервалу (d: { $gt: 10 }).

Цей індекс охоплює всі аспекти запиту (фільтрацію та сортування), забезпечуючи високу ефективність.

Погано Спланований Індекс ❌

// Запит:  
db.records.find({ a: 5, b: 10 }).sort({ c: 1 })  

// Індекс, де не дотримано порядку ESR  
db.records.createIndex({ c: 1, a: 1, b: 1 })

Проблема:

  • Поле c з'являється перед полями рівності (a і b), що перешкоджає ефективному використанню індексу MongoDB. Це може призвести до ScanAndOrder, де сортування виконується в пам'яті, що знижує ефективність.

Спеціальні Оператори та Правило ESR

$in як Рівність або Інтервал

Оператор $in може діяти як предикат рівності або інтервалу, в залежності від контексту:

Як Рівність:
Якщо масив містить менше ніж 200 елементів, MongoDB трактує $in як серію значень рівності, оптимізуючи запит.

db.records.find({ a: { $in: [1, 2, 3] } }).sort({ b: 1 })

Тут $in подібний до кількох предикатів рівності.

Як Інтервал:
Для більших масивів (200 елементів або більше) $in веде себе як предикат інтервалу. В правилі ESR він вважається інтервалом.

db.records.find({ a: { $in: [...Array(300).keys()] } }).sort({ b: 1 })

⚠️ Ліміт у 200 елементів може змінюватися між версіями MongoDB.

Кращі Практики для Використання Правила ESR

  1. Розташовуйте $in стратегічно:
    Використовуйте $in як рівність для малих масивів.
    Розміщуйте $in як інтервал для великих масивів.
  2. Уникайте полів інтервалу на початку індексу:
    Поля, такі як $gt, $lt, порушують ефективне використання індексу для наступних полів. Завжди розміщуйте їх в кінці.
  3. Тестуйте за допомогою explain():
    Перевірте, чи використовує запит індекс правильно:
  4. Комбінуйте запити та індекси:
    Переконайтесь, що індекси покривають як умови фільтрації, так і сортування.

Індекси Для Сортування (Sort) в MongoDB 🔄

Коли йдеться про покращення ефективності запитів, що вимагають сортування, індекси відіграють важливу роль у MongoDB. Цей посібник пояснює, як прості та складені індекси можуть бути використані для ефективного сортування результатів, уникаючи таких дорогих операцій, як Scan And Order.

Індекс для Одного Поля

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

Практичний Приклад:

// Створення індексу на полі "a"  
db.records.createIndex({ a: 1 })  

// Запити, які підтримуються  
db.records.find().sort({ a: 1 }) // ✅ Зростаюче сортування  
db.records.find().sort({ a: -1 }) // ✅ Спадаюче сортування

Складений Індекс

Складені індекси підтримують сортування по кількох полях, але вимагають уваги до порядку та напрямку індексованих полів.

Правила для Складених Індексів

  1. Запит повинен слідувати порядку полів в індексі або його зворотному порядку.
    2.
    MongoDB може використовувати індекс для сортування лише в тому випадку, якщо набір полів відповідає префіксу, визначеному в індексі.

Практичний Приклад:

// Створення складеного індексу  
db.records.createIndex({ a: 1, b: -1 })  

// Підтримувані запити  
db.records.find().sort({ a: 1, b: -1 }) // ✅ Правильний напрямок  
db.records.find().sort({ a: -1, b: 1 }) // ✅ Зворотній напрямок  
db.records.find().sort({ a: 1, b: 1 }) // ❌ Не відповідає напрямку

Префікс Індексації

Префікс відноситься до полів, які з'являються на початку визначення складеного індексу. Лише запити, які використовують повний або частковий префікс, можуть скористатися індексом для сортування.

Практичний Приклад:

// Складений індекс з кількома полями  
db.data.createIndex({ a: 1, b: 1, c: 1 })  

// Підтримувані запити  
db.data.find().sort({ a: 1 }) // ✅ Використовує префікс "a"  
db.data.find().sort({ a: 1, b: 1 }) // ✅ Використовує префікс "a, b"  
db.data.find().sort({ a: 1, b: 1, c: 1 }) // ✅ Використовує весь індекс  

// Запити, що не підтримуються  
db.data.find().sort({ b: 1 }) // ❌ Не містить "a"

Сортування з Фільтрами

Коли запит комбінує сортування та фільтри, MongoDB застосовує Правило ESR (Equality, Sort, Range), щоб визначити, чи може індекс бути використаний:

  • E (Рівність): Умови рівності ($eq, $in) йдуть першими.
  • S (Сортування): Сортування відбувається за наступними полями індексу.
  • R (Інтервал): Умови інтервалу ($gt, $lt) йдуть останніми.

Приклади Запитів

// Складений індекс з кількома полями  
db.data.createIndex({ a: 1, b: 1, c: 1 })  

db.data.find({ a: 5 }).sort({ b: 1 }) // ✅ Використовує індекс правильно  
db.data.find({ c: 5 }).sort({ b: 1 }) // ❌ Не відповідає префіксу індексу

ScanAndOrder 📈

ScanAndOrder є однією з основних причин зниження ефективності запитів у MongoDB. Це відбувається, коли індекс не може покрити як фільтрацію, так і сортування запиту, змушуючи MongoDB виконувати сортування результатів в пам'яті.

ScanAndOrder відбувається, коли:

  1. Індекс покриває лише фільтр, але не сортування.
  2. Не існує індексу для обробки запиту.
  3. Запит використовує сортування поза порядком визначених індексів.

Ефект:

  1. Документи скануються та фільтруються.
    2.
    Сортування виконується в пам'яті (Scan And Order).

Приклад ScanAndOrder

// Індекс створений на полі "a"  
db.records.createIndex({ a: 1 })  

// Запит, який використовує сортування за "b", не покритим індексом  
db.records.find({ a: { $gt: 5 }}).sort({ b: 1 })

У цьому випадку:

  • Індекс по a допомагає фільтрувати документи.
  • Але оскільки індекс не покриває сортування за b, MongoDB повинен просканувати документи та виконати сортування в пам'яті.

Як Ідентифікувати ScanAndOrder? 📊

Ви можете використати команду explain("executionStats"), щоб визначити, чи страждає запит від ScanAndOrder.

Приклад:

db.records.find({ a: { $gt: 5 }}).sort({ b: 1 }).explain("executionStats")

Результат:

{  
 "executionStats": {  
 "executionSuccess": true,  
 "nReturned": 50,  
 "executionTimeMillis": 123,  
 "totalDocsExamined": 5000,  
 "nScanned": 5000,  
 "inputStage": {  
 "stage": "COLLSCAN",  
 "nReturned": 50,  
 "totalDocsExamined": 5000,  
 "executionTimeMillisEstimate": 10,  
 "inputStage": {  
 "stage": "SORT",  
 "nReturned": 50,  
 "executionTimeMillisEstimate": 20  
 }  
 }  
 }  
}

Ключові показники:

  • "stage": "SORT": Підтверджує, що запит виконав сортування в пам'яті.
  • totalDocsExamined: Кількість перевірених документів (значно більша за необхідну — це вказує на неефективність).
  • nScanned набагато більше, ніж nReturned: Це свідчить, що MongoDB сканувало більше документів, ніж потрібно.

ScanAndOrder може спричинити проблеми з продуктивністю, особливо в великих колекціях або при частих запитах.

Метріка ефективності:

  • nScanned/nReturned: Має бути близьким до 1 для ефективного запиту.
  • Високі значення свідчать про те, що MongoDB сканує набагато більше документів, ніж повертає.

Основні впливи:

  1. 🔴 Використання пам'яті RAM:
    Сортування в пам'яті використовує додаткову пам'ять, що може вплинути на продуктивність сервера.
  2. ⏳ Більша затримка:
    Чим більше документів аналізується — тим більший час відповіді.
  3. 💻 Перевантаження сервера:
    Збільшується використання CPU і дискових ресурсів, особливо для запитів, що не використовують індекси.

Як уникнути ScanAndOrder? 🛠️

1. Створення складених індексів 🔍

Складений індекс, що покриває фільтр і сортування, може усунути ScanAndOrder.

Приклад:

// Складений індекс, що покриває фільтр (a) і сортування (b)  
db.records.createIndex({ a: 1, b: 1 })  

// Ефективний запит  
db.records.find({ a: 5 }).sort({ b: 1 })

2. Зменшення залежності від сортування 🚫

Іноді уникнення сортування в базі даних може бути рішенням:

  • Виконуйте сортування на стороні додатку, особливо для невеликих наборів даних.

3. Регулярно моніторьте за допомогою explain() 📈

Перевіряйте ефективність запитів періодично, щоб виявляти неефективні шаблони.

db.records.find(query).sort(sort).explain("executionStats")

Як створювати кращі індекси в MongoDB 🚀

Створення ефективних індексів у MongoDB — це важлива навичка для покращення продуктивності запитів і зменшення навантаження на базу даних. Погано спроектовані індекси можуть погіршити операції запису та читання, в той час як добре сплановані індекси допомагають прискорити доступ до даних, особливо в великих колекціях.

Ось найкращі практики для створення більш універсальних і продуктивних індексів.

1.

Використовуйте складені індекси для найбільш запитуваних полів

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

Приклад:

db.orders.createIndex({ status: 1, product_type: 1, order_date: -1 })

Запити, які використовують цей індекс:

  • db.orders.find({ status: 'pending' })
  • db.orders.find({ status: 'pending', product_type: 'electronics' })
  • db.orders.find({ status: 'pending', product_type: 'electronics', order_date: '2023-12-01' })

Запит, який НЕ використовує індекс:

  • db.orders.find({ product_type: 'electronics' }) Це відбувається, тому що запит не включає префікс індексу (status).

2. Пріоритезуйте поля з високою селективністю

Селективність поля визначається кількістю унікальних значень відносно загальної кількості документів. Поля з високою селективністю повертають мало документів і тому повинні бути розміщені першими в індексі.

Висока селективність:

  • Поля як user_id, email або будь-який унікальний ідентифікатор.

Низька селективність:

  • Поля як status або category, які мають мало можливих значень.

Приклад:

db.products.createIndex({ product_id: 1, category: 1 }) // Спочатку висока селективність

3. Уникайте створення множинних повторюваних індексів

Редундантні індекси займають місце і збільшують витрати на запис. Завжди, коли можливо, використовуйте складені індекси для уникнення повторення.

Приклад повторюваних індексів:

db.collection.createIndex({ a: 1, b: 1 })  
db.collection.createIndex({ a: 1 }) // Необхідний, оскільки вже покритий попереднім індексом.

4. Уникайте ScanAndOrder

// Уникаємо ScanAndOrder  
db.records.createIndex({ a: 1, b: 1 })  
db.records.find({ a: 10 }).sort({ b: 1 })

Підсумок:

✅ Враховуйте правило ESR (Equality, Sort, Range)

Завжди визначайте поля в індексі у такому порядку:

  1. Equality: Поля для фільтрації через рівність ($eq, $in).
  2. Sort: Поля для сортування.
  3. Range: Поля з умовами інтервалу ($lt, $gt).

✅ Використовуйте префікси індексації

Переконайтесь, що ваші запити слідують за префіксом полів, визначених в індексі. Це максимізує повторне використання та ефективність.

Приклад:

db.records.createIndex({ category: 1, subcategory: 1, price: -1 })  

// Дійсні запити:  
db.records.find({ category: 'books' }) // ✅  
db.records.find({ category: 'books', subcategory: 'fiction' }) // ✅  
db.records.find({ category: 'books', subcategory: 'fiction' }).sort({ price: -1 }) // ✅  

// Недійсні запити:  
db.records.find({ subcategory: 'fiction' }) // ❌ Не використовує індекс.

✅ Моніторьте використання індексів за допомогою explain()

Перевіряйте регулярно, чи використовуються індекси ефективно. Використовуйте команду explain("executionStats"), щоб проаналізувати ефективність ваших запитів.

Приклад:

db.orders.find({ status: 'pending' }).sort({ order_date: -1 }).explain("executionStats")

Шукайте:

  • totalDocsExamined: Має бути близьким до кількості повернутих документів.
  • executionStages.stage: Переконайтесь, що немає "COLLSCAN" або "SORT" без необхідності.

Висновок

Створення кращих індексів у MongoDB вимагає планування та постійного моніторингу. Використовуйте складені індекси для покриття кількох шаблонів запитів, пріоритезуйте поля з високою селективністю та уникайте повторень. З цими практиками ви забезпечите високий рівень продуктивності для ваших додатків і уникнете вузьких місць, викликаних погано спроектованими індексами.
🚀

Посилання:

Перекладено з: Guia Completo para Criar Índices Performáticos no MongoDB 🛠️

Leave a Reply

Your email address will not be published. Required fields are marked *