Здравствуйте, меня зовут Дмитро Карлівський і я ... антіконформіст, тобто людина, яка не тримається за свої звички і завжди готовий їх поміняти, якщо в цьому є необхідність. Наприклад, як і багато розробників, я починав вивчення баз даних з реляційних. Хоча реляційна алгебра і досить красива в своїй простоті, я постійно ловив себе на думці, що намагаюся впихнути круглу фігуру в квадратний отвір і виходило якось не герметично 🙂
Ні, я не буду розповідати вам про MongoDB або ще якусь неповноцінну «вбивцю SQL». Статей на тему «SQL vs NoSQL» порівнюють насправді реляційні СУБД з документні і так повно:
- Популярність: Oracle, MongoDB, Redis, HBase, OrientDB.
- Функціональність: OrientDB, Oracle, MongoDB, HBase, Redis.
- Швидкість: дуже сильно залежить від завдання, даних і реалізації програми. Я переглянув купу бенчмарков, всюди все по різному.
Сили тисяч розробників спрямовані на те, щоб досить прості моделі предметної області розташувати в табличках так, щоб це було швидко, гнучко і не дуже складно. Виходить погано. Пишуться величезні ORM фреймворки, зубодробильні SQL запити, створюються величезні індекси, і дублюються дані.
Ось, скільки статей на одному тільки Хабре написано про проблему, яка є виключно в РСУБД зважаючи спроби упіхнуть все різноманіття моделей предметної області в прямокутні таблиці:
Всі рішення зводяться до трьох основних:
Таблиця суміжності. Нащадок зберігає посилання на батька. Тут не зберігається порядок нащадків (щоб його зберігати потрібно вводити додаткову нумерацію, по якій сортувати, що уповільнює як вставки так і вибірки). Потрібні рекурсивні запити або денормализация таблиць смежностей.
Рекурсивний запит поддерева:
Запит поддерева по денормалізованной таблиці суміжності:
Матеріалізований шлях. Нащадок зберігає номери всіх предків (зі збереженням їх порядку). У гіршому випадку зміна ієрархії призводить до оновлення даних всіх вузлів. При відсутності відповідного АПИ з боку СУБД, вимагає фігурної маніпуляції над рядками, що не у всіх випадках прийнятно за швидкістю. Не допускає входження одного нащадка в кілька предків.
Запит поддерева з використанням ordpath:
Вкладені інтервали. Кожен вузол зберігає два числа, які визначають його точне положення в ієрархії. У гіршому випадку зміна ієрархії призводить до оновлення даних всіх вузлів. Не допускає входження одного нащадка в кілька предків. Щодо складні алгоритми вимагають підвищеної акуратності.
Як бачимо, кожен тип рішення має свої серйозні обмеження на представимо їм модель і ефективність різних типів запитів. А знаєте чому немає такого великого числа ґрунтовних статей про зберігання дерев, наприклад, в графових СУБД? Та тому, що там в принципі немає цих проблем, так як дерево - це окремий випадок графа. Так що питання «як же мені так примудритися і зберегти в базу ієрархію» в графових базах взагалі не варто.
Запит поддерева в графі:
Так, нереляційні СУБД, не дивлячись на загальну назву «NoSQL», теж можуть підтримувати Structured Query Language, розширюючи його своїми операторами 🙂
Багато SQL-профі тут зазвичай заявляють, мовляв дерева і тим більше графи в предметних областях майже не зустрічаються. Але варто трохи вийти із зони комфорту як тут же побачиш, що будь-яка предметна область насправді представляє з себе граф - набір сутностей, між якими є різноманітні відносини. Якщо відносини ці 1-к-1 або хоча б 1-ко-многим і при цьому пов'язують лише різнотипні суті, то такі моделі відносно легко лягають на реляційну модель (якщо не враховувати різні види Джойна з милицями у вигляді індексів). Але зазвичай все не так. У багатьох місцях можна зустріти відносини багато-до-багатьох. У РСУБД для кожного такого ставлення доводиться заводити окрему таблицю і кілька індексів до неї, а це ускладнення архітектури, роздування даних і уповільнення роботи з ними.
Деякі, особливо «передові» програмісти, пропонують зберігати кожен тип моделей у своїй СУБД. «Важливі дані» в реляційних, дерева в графових, а примітивні взагалі в словниках. Але подібні підходи виду «всякої задачі свій інструмент» лише додають головного болю (і як наслідок багів різного ступеня тяжкості) на тему консистентності даних в різних частинах програми.
Програмна розробка - штука дуже динамічна. Сьогодні майстер у вас повинен мати одну професію і ви просто даєте йому текстове поле вписати її. Завтра буде потрібно, щоб він міг вказати професію лише із запропонованих вами і ви даєте йому селект для вибору потрібної, зберігаючи id професії в модель майстра (один-ко-многим). Після завтра буде потрібно, щоб він міг вибрати кілька професій разом (багато-до-багатьох). А через тиждень вам терміново потрібно реалізувати вже ієрархічний каталог професій. У реляційної СУБД складність кожного наступного переходу значно перевершує попередній. З графовой - ви більше часу витратите на обговорення, ніж на реалізацію. Так що при старті проекту має сенс брати найбільш гнучкий інструмент, який дозволить вам не втрачати темп розробки в процесі зміни бізнес вимог (або кращого розуміння оних). Так, спеціалізовані інструменти можуть в деяких випадках бути швидше і саме в цих випадках, якщо в цьому є необхідність. варто займатися такого роду оптимізацією.
Часто тлумачні SQL-розробники беруть якусь MongoDB, про яку говорять на кожному розі, і намагаються приміряти до свого проекту, але розібравшись з нею, не розуміючи, крутять пальцем біля скроні. Безглузді так і залишаються на MongoDB, мириться з відсутністю транзакцій і відносин між документами, заради міфічної швидкості і можливості засунути в документ будь-якої json.
Давайте розвіємо кілька типових міфів з приводу NoSQL, про який судять по найбільш гучним представникам - MongoDB і Redis:
1. Вони не контролюють структуру записуваних користувачем даних. У відсутності схем є і плюси (можна робити міграції даних у фоновому режимі, можна зберігати не тільки кортежі примітивів), але і мінуси (потрібно уважно стежити що записуєш в базу, неефективне зберігаються дані). У OrientDB знайшли розумний компроміс: ви можете вказати схему і зазначені там поля будуть затверджувати відповідно до неї під час запису і не будуть витрачати місце на імена полів, а не зазначені валідованого не будуть, але будуть займати трохи більше місця. Тут важливо зазначити, що зміна схеми не призводить до зміни самих документів - просто ви не зможете змінити їх поки не приведете до нового формату.
2. Вони не задовольняють вимогам ACID (Атомарність, Узгодженість, Ізольованість, Надійність). OrientDB цим вимогам задовольняє. Більш того, вона з коробки вміє партіцірованіе і майстер-майстер реплікацію, так що підтримує в тому числі і розподілені транзакції. При цьому ви можете регулювати баланс між узгодженістю і швидкістю відповіді:
writeQuorum визначає число вузлів, які повинні підтвердити запис, перш ніж СУБД поверне відповідь, про успішне завершення транзакції.
readQuorum визначає число вузлів, які повинні підтвердити актуальність даних, перш ніж дані повернуться у відповідь на запит.
За замовчуванням всі транзакції синхронні (чекаємо відповіді всіх реплік), а читання відповідно відбувається без підтвердження актуальності (за її не потребою в цьому випадку).
Примітною особливістю є прозора підтримка map-reduce: якщо вузол містить не всі дані, то він сам робить запит до решти вузлів і зливає їх відповіді. Клієнт може працювати з партіцірованной базою даних як з цілісною. Є навіть автобалансіровщік, розкидає документи в кластери за різними стратегіями.
Для порівняння підходів роботи з графовой і реляційної СУБД, давайте створимо просту бізнес сутність - персону:
Тут все просто і майже однаково. Тепер додамо відношення «друзі»:
Недоліки РСУБД вже починають вилазити - нам потрібна була додаткова таблиця і унікальний індекс на ній. Індекс цей з одного боку гарантує, що у нас не буде дублікатів зв'язків, а з іншого дозволяє швидко знаходити друзів за ідентифікатором користувача.
Виведемо основні дані про друзів одного з користувачів:
У РСУБД ми не можемо зберігати посилання між записами - тільки їх ідентифікатори. Тому з'являється хитра конструкція, яка пояснює як дані з однієї таблиці співвідносяться з іншого. У графовой ж ми просто розгортаємо посилання.
А тепер знайдемо друзів його друзів:
У РСУБД запит помітно ускладнився. По хорошому його потрібно ускладнити ще сильніше, щоб дані друзів першого рівня не дублювалися для кожного друга другого рівня.
Продовжувати можна довго, але суть, я думаю, вже зрозуміла. Трохи більше подробиць про відмінності реляційного підходу і графового можна почерпнути з презентації "How Graph Databases started the Multi Model revolution".
На останок зазначу, що в реляційних базах дані зберігаються таблицями, але вони вкрай неефективні при запитах, тому до них додають автогенеріруемое дерево - індекс. Причому індекси намагаються робити покривають, тобто не вимагають звернення до власне таблиць за даними. Так ось, якщо вирізати таблиці і дозволити індексному дереву мати цикли, то ми отримаємо ні що інше як графову СУБД, де вся база даних - це один великий максимально ефективний індекс.
Досить теорії. Що на практиці-то?
Останній рік я займався розробкою бекенд для проекту SKEDDY (Пошук майстрів і запис до них на послуги). Звучить начебто не складно, проте число бізнес сутностей зараз вже дорівнює 20 (20 таблиць сутностей, якби я використовував РСУБД): person, mail, phone, social, token, application, profession, service, meeting, assessment, album, image, notification, place, track, payment, article, aspect, facet, salon.
Завдяки графовой моделі даних, відображення на неї бізнес моделі відбувається легко і просто - один раз написаний простою узагальнений код для синхронізації з базою даних, дозволив програмувати в термінах бізнес сутностей без зайвих турбот про оптимізацію запитів до СУБД. Власне боротьба з AngularJS відняла куди більше часу, але це вже зовсім інша історія ...
Тепер про недоліки:
Документація досить не погана для основного функціоналу, але деякі аспекти в ній відображені вкрай слабо. Наприклад, інформацію про хуках доводиться збирати по крупицях.
Більше щось на розум нічого не приходить 🙂
PS: Мене можна захантіть в Пітері бо SKEDDY так і не злетів 🙂
Тільки зареєстровані користувачі можуть брати участь в опитуванні. Увійдіть. будь ласка.