<
  • Главная
Статьи

MongoDB як дзеркало світової СУБД-революції

  1. Тест вставки, або вигнання з раю
  2. Запити, або зішестя в ад
  3. запит Q1
  4. запит Q2
  5. запит Q3
  6. запит Q4
  7. запит Q5
  8. хронометраж запитів
  9. За кадром
  10. Висновки, або вперед у минуле

Дана замітка послужила основою для однойменної глави книги " СУБД для програміста. Бази даних зсередини ".

* * *

Кілька епізодів з життя NoSQL очима YesSQL. Тема спливла в результаті дослідження технічних засобів для проекту переробки існуючої системи. Пуркуа б і не па?

Для тесту було вибрано сценарій дозволяє:

  • оцінити придатність СУБД до інтенсивної вставці даних від безлічі пристроїв
  • оцінити простоту і продуктивність запитів до отриманої таким чином базі даних

Тест вставки, або вигнання з раю

Тест інтенсивної вставки даних від безлічі датчиків я обмежив 10 мільйонами записів. Реальні обсяги на порядки вище, але навіщо проводити довгий час за процесом, якщо раптом і на такій кількості виникнуть проблеми.

На сайті MongoDB була завантажена остання стабільна версія 2.0.2 для 64-розрядної Windows. В якості альтернативи MongoDB виступав MS SQL Server 2008 R2 Developer Edition, теж 64-розрядний (якщо у вас такого немає, тест піде і на безкоштовному SQL Server Express 2005 і вище). Комп'ютер для обох тестів використовувався слабенький, рівня звичайної робочої станції під Windows 7, двоядерний Intel 2.6 GHz з одним повільним диском (5400 rpm, 300 Gb), але з 6 гігабайтами пам'яті.

Дані вставляються в колекцію MongoDB і таблицю SQL Server, відповідно, мають однакову структуру. Відповідні скрипти можна завантажити ( MongoDB , SQL Server ).

Для SQL-скрипта треба зробити пояснення. YesSQL - це світ транзакцій. Якщо ви будете тупо через підрядник вставляти в таблицю дані, як ніби перед вами плоский файл, то швидкість буде в рази нижче, ніж в разі файлу. Тому запишіть у себе в конспекті: для інтенсивної порядкової вставки в реляційну СУБД додаток повинен спочатку накопичити масив рядків, потім почати транзакцію, вставити ці кілька рядків в таблицю і закрити транзакцію.

В скрипті обраний пакет всього в 10 рядків, який збільшує швидкість вставки в 5 разів у порівнянні з порядковим вбивання даних.

MongoDB, цитую does not use traditional locking or complex transactions with rollback , as it is designed to be lightweight and fast and predictable in its performance . Загалом, згоден, якщо просто через підрядник вставляти записи в таблицю, то ми не-транзакційність виявиться швидше в будь-якому випадку.

Для просунутих поясню, що пакетна вставка (BULK INSERT) не завжди прийнятна за логікою програми. Стандартне використання - масовий імпорт даних. Власне, для BULK-копіювання і тестувати особливо нічого, воно або є в СУБД, чи ні. У MongoDB і SQL Server ця функціональність присутня.

За підсумками вставки вийшли два графіка і цифри за обсягом отриманої бази даних.

Колекція з 10 мільйонів документів зайняла 3,95 гігабайт, база даних SQL Server - 0,5 Гб (без компресії), тобто в 8 разів менше.

швидкість вставки

Хоча час вставки документа приблизно в 3 рази вище, ніж рядки в таблицю (пачками по 10), воно може бути цілком прийнятним для логіки додатка. Мене більше зацікавив тимчасовий провал в продуктивності (горб на другому графіку) і невгамовне бажання MongoDB нажер всю оперативну пам'ять: 4 Гб проти 1 Гб SQL Server при виставленому ліміті в 3 Гб.

Чи можна обмежити обсяг використовуваної оперативної пам'яті? Відповідь MongoDB - не можна. Запит на зміни висить більше року на трекері і не зустрів у розробників ніякого розуміння. У загальному випадку рішення полягає в створенні виділеного віртуального сервера під СУБД. Якісь неофіційні поради можна знайти пошуком ( наприклад ). Словом, залишається сподіватися на краще в майбутньому.

Тому для подальших тестів мені доводиться робити перезапуск mongod для очищення пам'яті. У SQL Server для аналогічного ефекту просто очищаємо буфери і кеш (DBCC).

О, цей кеш! У другій частині ви побачите, наскільки він ефективний.

Запити, або зішестя в ад

Прийшов час застосувати отриману таблицю за призначенням, навантаживши СУБД різними запитами.

Загальне враження від MongoDB: я потрапив в епоху навіть не FoxPro 2, де вбудований SQL вже був, а кудись в Clipper 87, який побачив світ влітку 1987 року. "Кліппер" у всій красі, з його нескінченними обходами таблиць в циклах, явним вибором поточного індексу для пошуку, накладеннями фільтрів, ручним підсумовуванням і іншими давно забутими «принадами».

У MongoDb немає вбудованих функцій агрегації. Для знаходження сум або середніх величин потрібно фактично написати свій аналог стандартних в SQL функцій SUM () або AVG () на мові JavaScript або використовувати більш загальний метод MapReduce.

Справедливості заради, треба сказати, що починаючи з версії SQL Server 2005 особливо обдаровані програмісти можуть писати свої нестандартні функції агрегації на C #. Але я порадив би спочатку добре подумати про необхідність цього кроку.

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

Як впорядкувати результати запиту з угрупованням? Відповідь - ніяк, зробіть сортування на клієнті. Діалог нижче залишаю без цензурних коментарів.
Q: How can I sort on the resultset of the group by operation?
A: Group currently just returns an object, so you should be able to sort easily client side

В результаті код запитів з коротким і ясним синтаксисом декларативного мови SQL перетворюється в довгу кашу з імперативних інструкцій і параметрів-декларацій В результаті код запитів з коротким і ясним синтаксисом декларативного мови SQL перетворюється в довгу кашу з імперативних інструкцій і параметрів-декларацій. "Еволюцію" можна простежити нижче на прикладі використаних в тесті запитів. Добрі люди навіть намалювали цю картинку трансформації MySQL-запиту в MongoDB.

запит Q1

Пошук мінімального і максимального значення дат в таблиці / колекції.

SQL Server MongoDB SELECT MIN (measureDate), MAX (measureDate) FROM dbo. measuresData var minDate = new Date (1900, 1, 1, 0, 0, 0, 0); var maxDate = new Date (2100, 1, 1, 0, 0, 0, 0); db. measuresData .group ({key: {}, reduce: function (obj, prev) {if (obj. measureDate .getTime () <prev. minMsec) prev. minMsec = obj. measureDate .getTime (); else if (obj. measureDate .getTime ()> prev. maxMsec) prev. maxMsec = obj. measureDate .getTime ();}, initial: {minMsec: maxDate. getTime (), maxMsec: minDate. getTime ()}, finalize: function (out) {out. minMeasureDate = new Date (out. minMsec); out. maxMeasureDate = new Date (out. maxMsec);}}). forEach (printjson);

Варіант MongoDB змушує працювати з поданням дат у вигляді числа мілісекунд від 01/01/1970, тому що пряме порівняння дат в функції викликає помилку TypeError: this [ "get" + UTC + "FullYear"] is not a function nofile_b: 2

"Кліпперний" досвід став у нагоді в MongoDB. Якщо побудувати індекс по елементу "measureDate", то можна знайти крайні значення відносно простим способом: впорядкувати за індексом в порядку зростання і взяти перший елемент, повторивши процедуру в порядку убування значень.

db. measuresData .ensureIndex ({measureDate: 1}); db. measuresData .find ({}, {measureDate: 1}) .sort ({measureDate: 1}) .limit (1); db. measuresData .find ({}, {measureDate: 1}) .sort ({measureDate: - 1}) .limit (1);

запит Q2

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

SQL Server MongoDB (не виконує сортування) SELECT SUM (intVal), SUM (floatVal), stateId, groupId FROM dbo. measuresData GROUP BY stateId, groupId ORDER BY stateId, groupId db. measuresData .group ({key: {stateId: true, groupId: true}, reduce: function (obj, prev) {prev. sumIntVal + = obj. intVal; prev. sumFloatVal + = obj. floatVal;}, initial: {sumIntVal : 0, sumFloatVal: 0.0}}). forEach (printjson);

запит Q3

Підрахунок загального числа пристроїв і кількості унікальних пристроїв станом і їх групі.

SQL Server MongoDB (не виконує сортування) SELECT COUNT (deviceId), COUNT (DISTINCT deviceId), stateId, groupId FROM dbo. measuresData GROUP BY stateId, groupId ORDER BY stateId, groupId db. measuresData .group ({key: {stateId: true, groupId: true}, reduce: function (obj, prev) {prev. count ++;}, initial: {count: 0}, finalize: function (out) {out . distCount = db. measuresData. distinct ( "deviceId", {stateId: out. stateId, groupId: out. groupId}). length;}}). forEach (printjson);

Журнал ядра БД показує приблизно 35 секунд для функції finalize на кожен цикл обчислення distinct з приблизно 200 рядків. Терміново обриваємо запит.

Для отримання відповіді протягом хвилин потрібно побудувати індекс за елементами stateId і groupId, інакше запит виконується протягом майже 2 годин. Іншими словами без оптимізації (втручання адміністратора БД) простий adhoc-запит фактично не працює.

db. measuresData .ensureIndex ({stateId: 1, groupId: 1})

Індекс будується 141 секунду (аналогічний в SQL Server - за 20 секунд). Розмір БД виросте до 4,2 Гб.

Перезапускаємо запит. Перемога розуму, з індексом цикл обчислення distinct знижується до 300 мілісекунд (у 100 разів). Третій перезапуск, час циклу зросла до 1,2 секунди, потім починає знижуватися до 300 мсек. Загальний час: більше 15 хвилин! Повне враження, що замість осмисленого використання кеша, в пам'ять навмисно заноситься сміття, тільки заважає нормальній роботі движка БД.

запит Q4

Підрахунок сум цілих і речових значень за станом і групі пристроїв для заданого діапазону дат. Як діапазону обрана 1 хвилина приблизно в середині інтервалу між мінімальним і максимальним значеннями дат. Діапазон включає близько 500К рядків.

SQL Server MongoDB (не виконує сортування) SELECT SUM (intVal), SUM (floatVal), stateId, groupId FROM dbo. measuresData WHERE measureDate BETWEEN '20120208 22:54' AND '20120208 22:55' GROUP BY stateId, groupId ORDER BY stateId, groupId var date1 = new Date (ISODate ( "2012-02-08T15: 20: 00.000Z")); var date2 = new Date (ISODate ( "2012-02-08T15: 21: 00.000Z")); db. measuresData .group ({key: {stateId: true, groupId: true}, cond: {measureDate: {$ gte: date1, $ lt: date2}}, reduce: function (obj, prev) {prev. sumIntVal + = obj . intVal; prev. sumFloatVal + = obj. floatVal;}, initial: {sumIntVal: 0, sumFloatVal: 0.0}}). forEach (printjson)

запит Q5

Підрахунок загального числа пристроїв і кількості унікальних пристроїв станом і їх групі для того ж заданого діапазону дат.

SQL Server MongoDB (не виконує сортування) SELECT COUNT (deviceId), COUNT (DISTINCT deviceId), stateId, groupId FROM dbo. measuresData WHERE measureDate BETWEEN '20120208 22:54' AND '20120208 22:55' GROUP BY stateId, groupId ORDER BY stateId, groupId var date1 = new Date (ISODate ( "2012-02-08T15: 20: 00.000Z")); var date2 = new Date (ISODate ( "2012-02-08T15: 21: 00.000Z")); db. measuresData .group ({key: {stateId: true, groupId: true}, cond: {measureDate: {$ gte: date1, $ lt: date2}}, reduce: function (obj, prev) {prev. count ++; }, initial: {count: 0}, finalize: function (out) {out. distCount = db. measuresData. distinct ( "deviceId", {stateId: out. stateId, groupId: out. groupId}). length;}} ). forEach (printjson);

хронометраж запитів

Для MongoDB результати без оптимізації жахливі, а з оптимізацією, де вона можлива, від "гірше" до "просто погані". З боку SQL Server вся оптимізація звелася до побудови кластерного індексу по полю measureDate (точніше, перестроювання замість наявного по первинного ключу measureId). У запитах Q2 і Q3 необхідно повне сканування даних, тому оптимізація стандартними засобами (індексація) для SQL не проводилася.

Другі і треті запуски добре ілюструють роботу кеша СУБД.

SQL ServerMongoDBЗапуски / час, сек123123

Q1 7 1 + 1 190 185 189 Q2 8 3 3> Параметри 7200 Q3 26 20 20 345 341 349 Q4 8 1 + 1 22 22 22 Q5 15 10 12 141 141 141 Після оптимізації Q1 0 0 0 0 0 0 Q2 8 3 3 322 800 619 Q3 немає ні Q4 1 + 1 1 12 12 13 Q5 7 7 7 85 84 85

За кадром

За кадром залишився цікаве питання: "Хто виконує ява-скрипти, ядро ​​монго або ж її клієнт mongo shell?". Сподіваюся на перше, тому що в другому випадку мова взагалі йде про НЕ СУБД, а про менеджера записів (record manager), на зразок пріснопам'ятного BTrieve з локальних Novell-мереж 90-х років.

Висновки, або вперед у минуле

Крім складності написання найпростіших запитів, неприйнятного навіть після оптимізації часу відгуку, неекономного фізичного зберігання, некерованого використання оперативної пам'яті і багато чого іншого, що я просто не зміг охопити в невеликому тесті, MongoDB фактично не вміє використовувати кеш запитів. Все перераховане вже не дає можливості віднести СУБД до розряду "універсальних, просто з іншою моделлю даних".

Ні, хлопці, модель тут вторинна, а про універсальність, як і про промислове вирішенні говорити поки передчасно. Для використання NoSQL потрібно мати серйозні підстави.

Простіше сказати, що не є причиною вибору NoSQL.

  • Відсутність компетенцій в області баз даних і надія обійтися без надалі;
  • Протест квазімонополіі "Big-3" (Oracle, IBM, Microsoft). Для цього є PostgreSQL, Firebird і, із застереженнями, MySQL;
  • Використання замість технічних аргументів маркетингові терміни "мода", "сучасне протягом", "прогресивний напрямок" і т.д. Насправді, моделі даних NoSQL використовувалися ще в дореляціонную епоху.

Пуркуа б і не па?
Чи можна обмежити обсяг використовуваної оперативної пам'яті?
Як впорядкувати результати запиту з угрупованням?
Q: How can I sort on the resultset of the group by operation?


Новости
  • Виртуальный хостинг

    Виртуальный хостинг. Возможности сервера распределяются в равной мере между всеми... 
    Читать полностью

  • Редизайн сайта

    Редизайн сайта – это полное либо частичное обновление дизайна существующего сайта.... 
    Читать полностью

  • Консалтинг, услуги контент-менеджера

    Сопровождение любых интернет ресурсов;- Знание HTML и CSS- Поиск и обновление контента;-... 
    Читать полностью

  • Трафик из соцсетей

    Сравнительно дешевый способ по сравнению с поисковым и контекстным видами раскрутки... 
    Читать полностью

  • Поисковая оптимизация

    Поисковая оптимизация (англ. search engine optimization, SEO) — поднятие позиций сайта в результатах... 
    Читать полностью