Індекси MongoDB та проблеми з ними. Специфіки Amazon Document DB

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті.

Вітаю, я Олег. Працюю розробником в компанії Easygenerator. У цій статті я зібрав основну інформацію про роботу та оптимізацію індексів в Mongo DB та специфіки Amazon Document DB щодо цього.

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

Так звана передчасна оптимізація (premature optimization) — витрата часу на оптимізацію чогось несуттєвого, яка призводить не тільки до нечитабельного коду, а й до погіршення працездатності програми після зміни вимог. Однак, коли виникає проблема, наприклад, перша сторінка з 16-ма записами пагінованого списку (довжиною в ~46 тис запитів) завантажується 40 секунд, розумієш, що потрібна зміна. Через те, що запит лише отримує документи з бази без додаткової логіки, вдалося швидко локалізувати проблему, яка виявилася надто повільним запитом до Amazon Document DB.

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

Рисунок 1. Проблемний запит

Дослідження проблеми

Коли маєш діло з такого роду проблемою, зразу звинувачуєш індекс, а тому потрібно переглянути план запиту, щоб зрозуміти, що саме не так. Для цього в MongoDB є команда explain(), яка видає ієрархічне зображення етапів виконання. Для того, щоб отримати час виконання кожного етапу, потрібно передати executionStats як перший параметр. На відміну від DocDB, монга також має третій варіант команди, що повертає усі можливі плани, а не тільки обраний.

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

Рисунок 2. Команда отримання плану виконання запиту

Рисунок 3. План проблемного запиту

Отриманий план виконання потрібно дивитися знизу вверх. Першим йде етап сканування COLLSCAN чи, як і в цьому випадку, вибірки за індексом IXSCAN. Бачимо, що саме вибірка займає 47 секунд, хоч і використовується індекс, отже, потрібно копати далі. Обраний індекс під назвою courseId_1 — індекс одного поля, що не покриває сортування за lastActivity.

Виконавши команду db.collection.getIndexes(), перевіряємо наявні індекси та бачимо, що є більш оптимальний варіант складного індексу courseId_1_lastActivity_1_isArchived_1, який чомусь не було обрано. Щоб розібратися чому, розглянемо основні критерії щодо яких оптимізатор запитів обирає індекс.

Критерії вибору індексу

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

  • Індекс вказаний за допомогою команди hint. В цьому випадку просто обирається вказаний індекс, навіть якщо він неоптимальний. Хоча є виключення про які дізнався в коментарях, наприклад, оператор $regex не підтримує використання індексу, якщо ми ігноруємо регістр
  • Індекс не схований. Для того, щоб протестувати швидкодію запитів без індексу (наприклад, щоб замінити чи видалити його пізніше) не потрібно його видаляти, тому що відбудова займе багато часу. Замість цього його можна сховати від планувальника запитів за допомогою команд hideIndex unhideIndex.
  • Він в пам’яті. Потрібно слідкувати, щоб усі індекси поміщалися в оперативну пам’ять. Не забувайте про те, що крім індексів усіх колекцій, також виділяється пам’ять під операції. Для того, щоб дізнатися розмір індексів однієї колекції, потрібно виконати db.courseResults.stats({ indexDetails: true, scale: 1000000000 }) (scale для зручності перегляду в Гб).

Рисунок 5. Розмір індексів

  • Відповідність правилу ESR (Equality, Sort, Range). Це правило вказує на необхідний порядку полів в складному індексі при його створені. Спочатку йдуть поля, які в запитах фільтруються за допомогою операторів повного збігу, наприклад $eq ($ne — оператор діапазону, тому не підходить), далі йдуть поля, за якими відбувається сортування (при чому сортування повинно відповідати сортуванню індексу в прямому або оберненому порядку, дивись наступний пункт), останніми йдуть оператори діапазону $gt, $gte , $lt, $lte, $ne. Цей порядок важливий, тому що у разі, коли ми захочемо використати оператор діапазону, а після цього відсортувати, серверу потрібно буде до-сортувати отримані результати в пам’яті, адже друге поле в індексі відсортоване відносно першого (знаю, звучить дуже заплутано). Наприклад, використовуємо оператор діапазону та отримуємо користувачів (дивись рисунок знизу) з id ca2 та xyz, далі сортуємо за оцінкою, але виходить, що для користувача xyz запис з балом 90 повинен йти перед записом користувача ca2 з оцінкою 75, отже не вистачає просто взяти документи за індексом, потрібно також виконувати дороге сортування в пам’яті. У випадку, коли ми робимо skip, limit, проблема швидкості може з’являтися на сотій сторінці.

Рисунок 6. Візуалізація складного індексу

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

Рисунок 7. Правило сортування для складних індексів

  • Але не потрібно забувати, з якою звірюкою ми маємо діло.

Рисунок 8. Document DB — емуляція Mongo DB

Специфіки Doc DB

  • Відсутність можливості сховати індекс від планувальника запитів.
  • Підтримка роботи операторів з індексами. При використанні зі складним індексом, скоріш за все буде виконуватися prefix індексу (про це далі).

Рисунок 9. Використання індексів з операторами діапазону

Розв’язання проблеми

Крім того, що проблемний запит має оператор діапазону $ne, як виявилося, пізніше назва індексу не відповідає порядку полів (дивись нижче), тобто порушена відповідність правилу ESR. Це дуже підступна проблема, адже збиває з пантелику, яку до того ж неможливо виправити без перестворення індексу (що не є дешевою операцією, особливо в DocDB). Тому краще ніколи не давати ім’я індексу, таким чином воно буде створено автоматично.

{ name: 'courseId_1_lastActivity_1_isArchived_1',
  key: { courseId: 1, isArchived: 1, lastActivity: 1 },
  host: '...',,
  accesses: { ops: 272, since: 2022-05-08T02:25:57.000Z } }

Фактично, якби порядок полів відповідав назві індексу, то запит підпадав під правило prefix (бо оператори діапазону в DocDB ніколи не використовують індекс). Складні індекси можуть перевикористовуватися в запитах, які використовують частину цього індексу (index prefix). Але в цьому випадку, немає ніякого сенсу триматися за оператор $ne для булевого поля. Простіше мігрувати базу (створити відсутнє поле), та використовувати оператор $eq (не потрібно буде навіть перестворювати індекс, хоча шляпа з назвою так і залишиться).

Рисунок 11. Використання префіксу індексу

Після виправлення умови запиту таким чином, щоб можна було використовувати оператор рівності для поля isArchived, та після встановлення значення для поля isArhived документів, в який це поле відсутнє, індекс почав відповідати правилу ESR, тому саме він був обраний для запиту, як видно з іншого запиту explain().

Рисунок 12. Виправлений запит

Рисунок 13. План виконання виправленого індексу

Далі йде невеличкий теоретичний блок про індекси, варіації, підтримку та чекліст зі створення.

Індекси

У більшості випадків, коли розмовляємо про індекси, ми робимо це абстрактно, без вказування на те, як він побудований. Насправді, в більшості випадків це просте дерево (btree). Приклад індексу з MS SQL:

Рисунок 14. B+ Tree index

Монга підтримує наступні типи індексів:

  • Простий індекс single field. Покриває операції пошуку та сортування для одного поля.
  • Складний індекс compound. Покриває операції пошуку та сортування як для усієї послідовності полів або його префіксу, при цьому важливі використані операції та напрямок сортування полів запиту.
  • Hashed. В більшості випадків може бути використаний для шардінга за допомогою послідовного хешування. Знаючи значення поля, наприклад ідентифікатор юзера, можна порахувати хеш та подивитися, що значення хеша попадає в межі третього сервера бази даних, який і зберігає документ користувача. Це є один з варіантів масштабування, за яким ми розподіляємо одну велику колекцію по різним серверам бази (не працює в DocDB, доступна інша методика — primary/secondary реплікація). Для цього індексу скоріше за все (не знайшов в документації) використовується інша структура hash map, що не підтримує використання операторів діапазону для запиту (хеш майже однакових значень разюче відрізняється).
  • Індекси гео-запитів 2dsphere, 2d. Корисні для пошуку в певному радіусі...
  • Text, wildcard, regex — спрощена версія повнотекстового пошуку, яка дозволяє обробити текст необхідної мови, прибравши стоп слова (the, a, an), обрізавши слово до кореня (stemming), вказати роздільник. Але, Elastic все ж гнучкіший, адже дозволяє налаштовувати конвеєр обробки тексту під себе, та має інші методи обробки тексту, наприклад додавання синонімів (2 — «two»).

Модифікації індексів

  • TTL — видаляє запис після спливання часу. Точність часу видалення не гарантована. Потрібно зменшувати кількість записів, що повинна бути видалена за короткий час. Тобто якщо у нас це лог, в який кожну секунду потрапляють записи, які нам потрібно видалити через 7 днів після створення, потрібно робити групування, наприклад в бакети погодинно або подобово, та створювати індекс для них. В іншому випадку під час видалення буде досить сильне навантаження. Amazon також рекомендує групувати записи в колекції та видаляти їх повністю, бо це не навантажує систему (IO cost) та зменшує ціну. Монга також каже використовувати Time series колекцій, що по суті також групують записи в бакети по часу.
  • Unique — забороняє повтори.
  • Partial, Sparse (конкретна версія partial індексу) — якщо для більшості запитів не потрібно повертати якусь частину колекції (наприклад, заархівовані записи), ми можемо використати partial індекс та вказати умову, за якої документ буде індексуватися. Це може стати в пригоді, коли потрібно створити unique індекс, але не всі документи мають унікальне поле (наприклад, в деяких воно відсутнє, що також вважається за дуплікацію). Таким чином, також можна створити два індекси для різних типів запитів. Sparse — індексує тільки ті документи, які мають індексоване поле, цього можна досягти вказавши відповідну умову при створенні partial індексу. (Ці індекси не підтримуються в DocDB).
  • Індексування масивів (multikey index) — індексується кожен елемент/документ масиву. Після цього ми можемо шукати документи, які мають потрібні нам піддокументи у внутрішньому масиві (наявна можливість вказання декількох умов до одного й того ж елементу масиву за допомогою оператора $elemMatch, який не підтримує індекс в DocDB).

Специфіки Doc DB

Підтримка індексів

Рисунок 15. Підтримка індексів в Document DB

Чекліст зі створенню індексу

  • Потрібно створювати індекси, що мають високу вибірковість. Тобто не на полях з декількома можливими значеннями, як, наприклад, boolean, або на тих полях, для яких використовуються оператори діапазону $gt, $gte, $lt, $lte.
  • Перевірити відповідність складних індексів правилу ESR.
  • Якщо запит отримує тільки поля документа, що знаходяться в індексі (зазвичай складному), то не буде виконуватися операція отримання самого документа, дані будуть повернуті напряму з індексу (covered queries).
  • Індекс — відсортована структура. Тобто створення індексу (в тому числі, складного з правильним напрямком сортування для кожного поля) пришвидшує запити з операцією сортування.
  • При створенні складних індексів, краще не давати йому ім’я, в такому випадку ім’я буде згенеровано автоматично і відповідатиме порядку полів та напрямку сортування. Тому воно не буде вводити в оману. Ім’я неможливо буде змінити без видалення індексу.
  • Краще перевикористовувати індекси, бо вони займають багато місця та повинні поміщатися в оперативну пам’ять, а також тому що індекси збільшують час створення та оновлення документів.
  • db.collection.aggregate([{$indexStats:{}}]).pretty() — для перегляду статистики використання індексів
…
{ name: 'courseId_1_lastActivity_1_isArchived_1',
  key: { courseId: 1, isArchived: 1, lastActivity: 1 },
  host: '...',,
  accesses: { ops: 272, since: 2022-05-08T02:25:57.000Z } }
{ name: '_id_',
  key: { _id: 1 },
  host: '...',
  accesses: { ops: 411342, since: 2022-05-08T02:25:57.000Z } }
  • Якщо запити не мають наміру шукати по всіх документах колекції, наприклад у яких немає певного поля чи які були заархівовані створенням boolean поля, то рекомендується використання partial, sparse індексів, які не індексують всю колекцію (не працює в Doc DB), тому зменшують розмір індексу, а також час модифікації неіндексованих записів.
  • Щоб контролювати прогрес побудови індексу, потрібно виконати команду db.currentOp(true).inprog.forEach(function(op){ if(op.msg!==undefined) print(op.msg) }) (Mongo DB). Щоб зупинити побудову, потрібно виконати команду dropIndex().
  • Є два способи побудови індексу, foreground-версія та background. У той час, коли перша отримує lock на всю колекцію, друга дозволяє працювати з нею під час побудови, хоч це і повільніше. В останніх версіях монги, ці способи об’єднали, тому колекція блокується в початку і в кінці побудови, а під час роботи з колекцією можна працювати як завжди.
  • Під час побудови індексу використовується диск, тому потрібно слідкувати, щоб вистачало місця. Також для пришвидшення операції рекомендується заскейлити екземпляр бази на час побудови індексу.

Специфіки DocDB

  • Побудова не почнеться, доки не будуть завершені всі запити, що запущені до початку побудови.
  • Якщо відбувається побудова background індексу, в Doc DB, не можна розпочинати побудову нового або видалення іншого в тій же колекції, тому що побудова зупиниться з помилкою.

Рисунок 16. Застереження паралельного виконання операцій над індексами в Document DB

Ресурси

Сподобалась стаття? Натискай «Подобається» внизу. Це допоможе автору виграти подарунок у програмі #ПишуНаDOU

👍ПодобаєтьсяСподобалось10
До обраногоВ обраному4
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

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

Гарна стаття ) маленька ремарка — B-tree не просте дерево, а на стероїдах :D — дуже versatile хрінь.

Partial indexes вирішують проблему не тількі унікальних ключів, але і розмір індексу також, і в деяких випадках серйозно збільшують write performance.

P.S. AWS Document DB — зло )

Індекс вказаний за допомогою команди hint. В цьому випадку просто обирається вказаний індекс, навіть якщо він неоптимальний.

Ага, щазз... Ця скотиняка не зрозуміло чому ігнорить пошук по $regex з паттерном аналогом StartingWith навіть якщо явно вказати hint() з індексом ім’я якого відповідає імені філда (ще один їх додатковий реквайрмент).
Ну як «незрозуміло» — індекс неоптимальний в плані того що левова частка записів на момент пошуку має досить однорідний ключ, explain plan в цьому смислі все відображає. Але чому ця скотиняка вирішила «що краще знає», коли їй сказали «копати до обіду» — оце дратує (немає ніякиї гарантій що коли будуть залиті решта записів і розподіл по ключу зміниться пошук буде юзати індекс).
Ми вийшли з ситуації дещо змінивши структуру колекції (додавши додаткові поля) і перейшовши на пошук по eq. Добре що сторадж зараз дешевий і це практично нічого не коштує.

Цікава поведінка. В документації написано, що $regex оператор не підтримує індекс, якщо ми ігноруємо регістр

www.mongodb.com/...​or/query/regex/#index-use

Скорочу до змісту: проблема в самій ідеології, що поле сортування в запиті є більш пріоритетним, аніж поле обмеження даних критеріями «більше» та «менше». Тому ця проблема не має загального вирішення.

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

Зрозуміло що є питання, для чого тобі саме тут був SORT. Бо ти фактично і замовив тяжкий запит, де спочатку треба все відсортувати, а вже потім накладати ліміт. Це неочевидно, що LIMIT застосовується в останню чергу, що це є агрегатна функція по суті, хоча і вміє працювати в пайплайні.

Але питання от у чому: а якого біса оптимізатор взагалі поліз змінювати пайплайн, якщо він в тебе чітко прописаний саме як пайплайн? Тобто, спочатку ID, потім критерій фільтрації (як розумію, не індексний), потім все інше. Чи краще переформулюю питання: а чи пайплайн взагалі мінявся? В тебе якщо запитати по ID, то виходить ≈43К записів, правильно?

Тобто, в тебе проблема із самою організацією даних. Так, дійсно, ти можеш створити та використати правильний індекс, який повертатиме 16 записів, а не 43К, за рахунок підбору правильних критеріїв. Але ти забув дрібничку — витрати на перебудову та підтримку цього індексу. Тобто, ти просто перепоховав проблему, перенісши витрати із операції читання на операцію запису. І вони ростимуть, і складність буде значно вищою за логарифмічну, бо ж значення 1 поля змусить перенести запис в іншу ділянку індексу, це не проста операція. Особливо якщо розуміти, що індекси займають дофіга і місця, і оперативи, і ця маніпуляція є просто катастрофічною для SSD, якщо ці зміни відбуваються часто.

ЯК ТРЕБА: створити окрему таблицю, та усе що isArchived — безжально викидати туди. І вже там можна навіть із індексами економити, допустивши що час запиту на читання буде 40 секунд. Бо ті запити дуже рідкісні. Та й там навряд чи ти станеш запитувати без додаткових критеріїв на кшталт дати, або тебе чимось не влаштує натуральний порядок чи якийсь простенький індекс, який не буде змушений сильно перебудовуватися.

Викидати архівні дані можна як одразу, так і в нічному чи тижневому циклі обслуговування. Навряд чи в тебе за тиждень копиться багато записів, тому якщо первинний запит на читання (до сортування та фільтрації) повертатиме не 16 рядків, а скажімо 40 — ніхто не помітить різниці. Якщо ж сервіс має вираженний холостий період у певний час доби, то краще кронтабити щоночі обслуговувати. Краще перед бекапами, але не на північ (де в серверів своїх кронтабів до біса). В чому твій виграш:
1) Запис до бази одним запитом. Якщо пишеться багато, можеш навіть індекси відключати та перебудовувати. Це залежить від того, скільки мегатон помиїв в тих базах.
2) Витрати на операції вводу-виводу значно нижчі, коли вони не конкурують. Та й тобі на них начхати, скільки часу це займає.

Ну і найголовніше: якщо «сміттєвих» даних дійсно багато, ти можеш взагалі викинути цю таблицю на HDD та не засирати нею SSD. Буває й таке, що у три яруси складають дані, тобто активна таблиця (те що зараз користується), умовно застарілі дані (до яких ти сам визначаєш логікою, коли їх треба доєднати до запиту), і древнє сміття (наприклад, старше 4 місяців), до якого взагалі рідко звертаються. Так ти зекономиш на операціях перебудови індексів та на ризиках збоїв під час того самого вводу-виводу. А вони не нульові!

Я можливо неясно сформулював проблему, а саме пагінований список (~46 тис записів) не підтягується за прийнятний час (після виправлення це займає <1 секунди як видно на рисунку 13). Проблема була в індексі та запиті.

Так, все вірно. Оптимізатор запитів (слідуючи правилу ESR) спочатку сортує, а потім фільтрує за допомогою операцій діапазону, але в даній статі не було використано ніяких пайплайнів. Агрегація в монзі доволі гнучка та дає можливість як декларативного так і імперативного підходу написання. В даному випадку використовувалися запити `find`, що — декларативні (як SELECT в SQL, ми ж не кажемо що спочатку фільтруй а потім вибирай), тобто оптимізатор запитів сам вирішує в якій послідовності йдуть операції (це видно з двох запитів `explain` на рисунку 3 та 13, де в обох витримана правильно послідовність — фільтрування, сортування, фільтрування по діапазону). Для імперативних запитів, оптимізатор не потрібен апріорі, але потрібно витрачати багато часу на переписування на підтримку цих запитів (наприклад як зі старими ієрархічними базами). Це, в свою чергу, означає, що між запитами `find().skip().limit().sort()` та `find().skip().sort().limit()` немає різниці.

Так, я згоден що архівовані запити можна виносити в окрему колекцію, але простіше створити частковий індекс, який просто не буде індексувати непотрібні записи (нажать Document DB не підтримує цього). В нашій вибірці архівовані записи складають ~10%, тому це не така велика проблема.

Так, додавати новий запис в середину індекса не прикольно, як ви вже сказали. Я не знаю як монга (й особливо Document DB) це оброблює під капотом та чи націлена вона на послідовність читання індекса з диску (логічно було б сказати так, бо це може пришвидшити операції отримання записів). Проте, навіть за умови, що усі архівовані записи ми виносили б в іншу колекцію, це б не вирішило проблему додавання нових записів в довільні частини індексу. Чому? course_id — один з найголовніших ідентифікаторів колекції, який ми отримуємо з іншої реляційної бази, де, на відміну від монги, використовуються UUIDv4 або як ще їх називають GUID, тобто не зберігається часова послідовність вставки (в монзі, ObjectId, має Unix timestamp як 4 перших байти, тому вставка в індекс послідовна). B-Tree+ індекс не так залежить від розташування на диску, я впевнений якийсь сервіс бази може в на задньому плані розташовувати сторінки в потрібній послідовності на диску. Також самі SSD мають достатньо мізків, щоб таким займатися

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

Ще забув додати. В OLTP базах вибірковість дуже важлива. 46 тисяч записів хоч і не мала цифра, але складає <1% усіх записів, що є гарним показником вибірковості індексу

1% — то взагалі не показник. Він означає що статистично за 100 запитів ти отсканиш усю базу. Мало того, в рандомному порядку (читай відсканиш кілька разів). Замість того щоб розслайсити горизонтально таблицю та взагалі не питати те, що запитується досить рідко.

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

Я так розумію, що кількість запитів, де isArchived = false, вже є тим самим 1%, а то й менше. Тому замість щоразу сканити усю базу, чи навіть увесь індекс, не простіше зробити окрему таблицю під архівовані записи? Те що зараз архівовані записи складають малу долю — це лише зараз. Хоча... в чому проблема, чому їх так мало? Мало ж бути більше.

Звісно у Монго ти можеш згодувати partialFilterExpression самому індексу. Це буде звичайно не повним вирішенням проблеми, індекс перебудовуватиметься при кожній зміні isArchived (можливо це й оптимізовано задля скороченя кількості записів, не перевіряв). Але так, це рішення для менш очевидних випадків, коли ймовірність запиту рідкісних даних не завжди очевидна аж до оптимізації в коді. Але DocDB буде часткові індекси підтримувати мабуть коли Україна всі борги поверне.

Не зрозумів, за що саме Амазон прийшле чек. Це досиить проста і примітивна операція. Чи то я просто не в темі, як тарифікує амазон? До того ж саме ця операція не потребує переводити сервіс в режим обслуговування, може бути виконана у гарячому режимі.

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

PS. А чому пагінація лише 16 записів? Зроби 100 чи 200 по дефолту. Хіба що ти із замовником посрався та навмисно йому підгадив, примушуючи юзера зберігати інфу в голові.

1% просто означає обрання правильних полів для індексу. Це не означає що за 100 запитів потрібно буде пройтися по всій колекції, бо кількість залежить від даних клієнта + деякі клієнти давно не заходили, деякі по 2 рази...

Також 1% означає що ти не пробігаєш увесь індекс на кожноу запиті. В цьому випадку індекс — збалансоване дерево, в якому досить просто знайти потрібний та наступні N записів без перебігу по всьому індексу.

Колекція дійсно не маленька, тому в майбутньому досить таки розумно розбити її. Архівування — це юзекйс для клієнтів, але, звичайно, старі дані рідко отримуються, тому їх можна кудись скинути. Наразі це не критична проблема яку потрібно вирішувати, бо, знову ж таки, правильний індекс отримує потрібні записи менш ніж на мілісекунду, до того ж використовується реплікація, тому читання не проблема. Так, колекція буде рости, буде рости індекс. Але не очікується, що курс буде мати настільки багато записів, що користувач побачить проблему. Вертикальний скейлинг може бути не дешевий, однак поки накидуєш оперативи, диск та cpu, та залишаєш вибірковість колекції низькою, проблем не буде. Це вже питання до компанії чи готові вони платити за амазон, чи за те, щоб ми зробили більш оптимальне рішення. Вони виставляють пріоритети, головне завдання розробника донести проблему. Можливу проблему потрібно тримати в голові (краще повище в стеку, бо й описаної в цій статті не було видно, до поки не попався курс з кількістю записів 46 тисяч)

Заскейлити горизонтально за допомогою шардінга було б добре, однак DocumentDB і цього не підтримує та додадуть напевно вже після того, як Америка борг поверне (доступна тільки primary/secondary реплікація).

Щодо цінника на амазон, то він бере за I/O та storage (ну і також за сам інстанс). Так, можна проганяти без зупинки додатка, особливо коли є репліка

Чому 16? Та фіг знає, як є так є (на фронті насправді нескінченний скролінг, термін «пагінація» я використовував для рівня сервісу)

Дякую, цікава стаття!

Підписатись на коментарі