Оптимізація MongoDB: де запити втрачають швидкість
Повільні запити в MongoDB рідко з'являються одразу: сервіс місяцями працює на невеликій колекції, де все потрібне читається з пам'яті.
Точка перелому настає на зростанні даних — вибірка, яка вчора відповідала миттєво, починає впиратися в планувальник, і час відповіді зростає разом з обсягом колекції.
Перше місце втрат — сканування колекції замість індексного доступу. explain() показує COLLSCAN там, де очікувався IXSCAN, і далі кожна нова порція документів робить читання дорожчим.
Буває, індекс формально є, але складений у неправильному порядку — рівність, сортування, діапазон — і планувальник його просто не бере.
Друге — сортування та конвеєр агрегації. Сортування без підтримки індексу виконується в пам'яті, а при перевищенні ліміту вивантажується на диск; проміжні етапи накопичують набори документів, коли фільтрація та проєкція стоять після з'єднання колекцій, а не до нього. Кожен зайвий етап — це додаткове читання й зайве навантаження на диск.
Третє — повторювані запити до диска. Без покривного індексу ті самі документи читаються знову й знову, робочий набір перестає вміщатися в пам'ять, кеш витісняється, і дисковий ввід-вивід стає вузьким місцем усієї бази. Далі розбираємо, як це закривається.
Що дає налаштування індексів і планів виконання
Роботу з доступом до даних починаємо з плану виконання: explain у режимі статистики показує, скільки документів переглянуто проти повернутих, чи потрапив запит в індекс, чи пішов на повне сканування колекції, де сортування вийшло за ліміт пам'яті.
Розрив між цими числами в десятки разів — нормальна відправна точка, і він прямо переводиться в час відгуку: зайві документи читаються з диска, а не з кешу.
Далі перебудовуємо індекси під фактичні шаблони запитів.
Складений індекс збирається в порядку «рівність → сортування → діапазон», щоб фільтр і сортування закривалися одним проходом; там, де застосунок вибирає лише кілька полів, робимо його покривним — сервер віддає відповідь без звернення до самих документів.
Паралельно знімаємо дублювальні та невикористовувані індекси: кожен зайвий індекс уповільнює запис і роздуває робочу множину, витісняючи потрібні сторінки з пам'яті. Для MongoDB це не разова правка, а звірка з поточним профілем запитів.
Ефект видно в цифрах: час відповіді на гарячих вибірках скорочується в рази, частка читань з диска падає з десятків відсотків до одиниць, а найнеприємніший клас відмов — таймаути під піком — зникає, бо запит перестає тримати блокування та чергу на виконання при зростанні кількості одночасних звернень.
Стабільність під навантаженням важливіша за середній час: система, яка тримає відгук при десятикратному сплеску, не провалює користувацькі сценарії в години пік.
План виконання — величина непостійна: колекція зростає, фільтри змінюються, оптимізатор може вибрати іншу стратегію. Тому налаштовуємо регулярний контроль повільних запитів і пороги оповіщення, щоб деградація ловилася до скарг, а не після.
Підходи до прискорення запитів: порівняння варіантів
Перший крок — зняти план виконання запиту: він показує, чи йде вибірка за індексом, чи колекція сканується повністю, і скільки документів переглянуто заради кількох потрібних.
Далі вибір іде між кількома стратегіями, і вони не взаємозамінні: кожна прискорює свій клас запитів і впирається у свою межу.
| Варіант | Коли доречний | Обмеження |
|---|---|---|
| Точковий індекс за одним полем | Фільтр за високоселективним полем, у вибірці одиниці документів | Сортування за другим полем іде в пам'ять і на великому наборі розриває запит |
| Складений індекс | Сталий набір умов плюс сортування за тими самими полями | Порядок полів жорсткий: запит з іншим набором полів індекс не задіє |
| Покривний індекс | Фільтр і проєкція повністю лежать в індексі, документ не читається | Зростає розмір індексу та ціна кожного запису; список полів зашитий в індекс |
| Розділення читання | Звітні й аналітичні вибірки, які не повинні заважати основному навантаженню | Відставання вторинних вузлів і нестрога узгодженість; сам запит не прискорюється |
| Рознесення даних | Важкі вкладені масиви, які читаються рідко, але роздувають документ | Ускладнюються запис і узгодженість, додаються додаткові звернення |
На практиці варіанти комбінують: складений покривний індекс закриває регулярну звітність, а важкі вивантаження виводимо на окремий контур читання.
Обмеження кожного кроку проговорюємо до впровадження — інакше прискорення вибірки обертається зростанням часу запису й розсинхроном між вузлами.
Етапи робіт: від аудиту запитів до передачі налаштувань
Оптимізація починається не зі створення індексів, а з інвентаризації: потрібно зрозуміти, які звернення до бази витрачають час і чому. Інакше правка перетворюється на здогад, який прискорює один запит і непомітно уповільнює десять інших.
-
Профілювання запитів — вмикаємо збір повільних операцій і групуємо їх за спільним шаблоном. Так видно сталі вузькі місця, а не поодинокі викиди; звернення застосунку та аналітики позначаємо окремо, у них різна ціна помилки.
-
Розбір плану — для кожного шаблону дивимося, який індекс вибирає планувальник, на якому кроці губляться документи й де замість вибірки за ключем іде повний перебір колекції.
-
Правка індексів і схеми звернень — складені індекси збираємо під реальний порядок полів у фільтрі та сортуванні, додаємо покривні там, де застосунок забирає лише частину полів. У конвеєрі агрегації переносимо фільтрацію на початок, до групування та розгортання масивів.
-
Ревізія моделі даних — вкладеність і дублювання: іноді правка структури документа прибирає цілу пачку важких звернень.
-
Перевірка під навантаженням — проганяємо змінений варіант на реалістичному профілі: та сама частка читання й запису, ті самі конкуруючі операції. Порівнюємо план, кількість переглянутих документів і час відповіді до та після.
-
Фіксація та передача налаштувань — віддаємо перелік індексів з обґрунтуванням, правила звернення до колекцій, метрики для моніторингу й короткі інструкції, щоб команда підтримувала результат без нас.
Індекси, схеми та регламенти: що залишається в проєкті
Оптимізація запитів живе не у звіті, а в артефактах репозиторію. Якщо склад індексів і правила доступу залишаються в голові інженера, наступний розробник додасть дублювальний індекс або видалить потрібний — і деградація повернеться за пару релізів.
- Опис індексів — склад і порядок полів, селективність, під який сценарій читання створений. Створення оформлено міграцією, а не ручною командою на робочому сервері.
- Схеми колекцій і валідатори — обов'язкові поля, допустимі типи, заборона різнорідних структур в одній колекції, щоб запити залишалися передбачуваними.
- Правила звернення до даних — де читання йде лише за індексованим полем, які вибірки без фільтра вважаються дефектом, які операції допустимі в гарячому шляху.
- Політики доступу та ролі — окремі облікові записи для сервісів, мінімально необхідні права, відсутність адміністративних прав у застосунку.
- Регламент супроводу — як часто знімаються плани виконання, за якої розбіжності з еталоном запит іде на доопрацювання і хто це перевіряє.
- Еталонні плани та контрольні запити — набір вибірок з очікуваною поведінкою, на якому перевіряють регрес після змін у схемі.
- Інструкція для чергового — що дивитися при зростанні затримок, які метрики і в якому порядку перевіряти.
Усі артефакти версіонуються поруч із кодом і передаються вашій команді. Через місяць після передачі новий інженер додасть індекс, не зламавши наявні, а черговий знайде причину деградації за інструкцією — без звернення до нас.
Кейс: прискорили каталог без переписування логіки
Що було
Каталог виріс із кількох тисяч позицій до десятків тисяч, і час відповіді на видачу категорії поїхав: на піку вона доходила до трьох-чотирьох секунд, 95-й процентиль виходив за шість, частина запитів відвалювалася через перевищення часу очікування.
Профіль запитів показав дві причини. Фільтри за категорією, брендом і наявністю йшли без складеного індексу, тому кожен запит читав колекцію повністю, а конвеєр агрегації для вітрини спочатку збирав пов'язані документи по всьому каталогу і лише потім відсікав зайве фільтром.
Що змінили
Розібрали план виконання реальних запитів на MongoDB: видно, де йде повний прохід по колекції, а де індекс є, але не задіяний через порядок полів.
Зібрали складені індекси під фактичні комбінації фільтрів: категорія й наявність — провідними полями, ціна та дата — хвостом під сортування.
У конвеєрі перенесли фільтрацію до з'єднання з пов'язаними колекціями й залишили проєкцію лише потрібних полів — у другу стадію потрапляють десятки документів замість усього каталогу. Логіку видачі та формат відповідей не чіпали.
Що стало
Час відповіді скоротився приблизно у вісім разів, 95-й процентиль повернувся до часток секунди, перевищення часу очікування зникли. Обсяг даних, який читає один запит, упав на порядок — за тієї самої кількості товарів і без переписування логіки.
Каталог відтоді виріс ще, а час відповіді залишився таким самим: запас тримається за рахунок того, що кожен новий фільтр проходить такий самий розбір плану до випуску.
Що робити, якщо оптимізація MongoDB не дала ефекту
Правки внесено, індекси побудовано, а запити все одно повільні — отже, вузьке місце не там, де його шукали.
Другий захід починається не з нових індексів, а з розбору планів уже наявних: планувальник MongoDB показує, чи використовується індекс, чи колекція читається повним перебором.
Порядок полів складеного індексу — перша часта помилка. Індекс прискорює вибірку, лише якщо його префікс збігається з умовами фільтра; якщо першим стоїть низькоселективне поле або поля переставлено, планувальник відмовляється від індексу. Перестановка полів — правка в конфігурації, а виграш вимірюється кратно.
Конкуренція за ресурси дає схожу картину: швидкий запит на навантаженому вузлі чекає черги, довгі операції тримають блокування й витісняють інших. Обмежуємо час виконання, виносимо важкі агрегації в окремий контур — відгук приходить до норми без переписування самих запитів.
Невдале рознесення даних між вузлами збирає все навантаження на одному, поки решта простоюють.
Перезбирання ключа рознесення або винесення зростаючої колекції в окремий контур прибирає перекіс, але потребує плану міграції та перевіреного відкату — інакше виправлення виявиться дорожчим за початкову проблему.
Повторний аудит — не разова правка, а цикл: замір, гіпотеза, зміна, знову замір під робочим навантаженням. Без нього оптимізація залишається набором здогадів, які неможливо ні підтвердити, ні спростувати.
Технічні сумніви: відповідаємо на часті запитання
Оптимізація запитів упирається не лише в сам запит, а й у те, що навколо: як правки відобразяться на записі, чи потрібно чіпати схему документів, що зміниться при шардуванні та де перевіряти результат. Нижче — запитання, які найчастіше надходять на етапі оцінки.
Чи не уповільнять нові індекси запис?
Уповільнять, і це закладається в розрахунок. Кожна вставка й оновлення підтримують усі індекси колекції, тому ми дивимося на співвідношення читань і записів та на розмір індексу в робочій множині.
Індекси, які статистика звернень не підтверджує, прибираємо — зазвичай саме вони й створюють зайве навантаження на запис.
Чи можна обійтися без зміни схеми?
У більшості випадків так. Порядок полів у складеному індексі, часткові та TTL-індекси, переписана послідовність стадій агрегації дають приріст без правки документів.
Міняти структуру пропонуємо лише там, де перебудова вибірки не закриває завдання, — і тоді зі зворотною сумісністю та планом перенесення.
Що буде при шардуванні?
Ключ шарда визначає, чи потрапить вибірка в один шард, чи розійдеться по всіх. Якщо в умові запиту ключа немає, частина роботи йде марно. Ми перевіряємо плани виконання, додаємо ключ у префікс індексу або коригуємо умову вибірки, щоб маршрутизація була точною.
На якому середовищі перевіряємо результат?
Тільки на зіставному обсязі даних: на порожній базі планувальник вибере інший план, і заміри будуть хибними. Розгортаємо копію з обезліченим зрізом, знімаємо плани до та після, фіксуємо час відгуку й кількість переглянутих документів — це і є доказ ефекту.
Обговоримо задачу: повернемося з оцінкою та планом робіт
Оптимізація MongoDB починається не з правки окремих запитів, а з картини навантаження: які вибірки гальмують, які індекси реально використовуються, де агрегаційні конвеєри проходять по колекції повністю.
Щоб повернутися з оцінкою обсягу та планом етапів, нам достатньо кількох артефактів — їх черговий інженер збирає за один прохід по системі.
- Список проблемних операцій: час відгуку та частота звернень за добу.
- Вивід explain за ними — план виконання, частка відсічених документів, використаний індекс.
- Схема колекцій: типи полів, обсяги, розподіл за репліками.
- Поточні індекси та статистика звернень до них — одразу видно, які працюють вхолосту.
- Профіль навантаження: співвідношення читань і записів, пікові години, характер сплесків.
- Обмеження контуру: вікно обслуговування, доступні репліки для важких завдань, вимоги до узгодженості даних.
У відповідь прийде не перелік загальних порад, а план етапів із пріоритетами: що дає ефект одразу, що потребує перебудови індексів і підготовки, якими метриками заміряємо результат.
Далі узгодимо порядок і стартуємо з ділянки, де віддача максимальна — опишіть задачу, і повернемося з оцінкою.







