Основи проектування баз даних – порівняння PostgreSQL, Cassandra та MongoDB

Привіт, друзі. Перед відходом на другу частину травневих свят ділимося з вами матеріалом, який ми перевели напередодні запуску нового потоку за курсом «Реляційні СУБД».

Основи проектування баз даних – порівняння PostgreSQL, Cassandra та MongoDB

Розробники додатків витрачають багато часу на порівняння кількох операційних баз даних, щоб вибрати ту, яка найкраще підійде для передбачуваного робочого навантаження. Потреби можуть включати спрощене моделювання даних, транзакційні гарантії, продуктивність читання/запису, горизонтальне масштабування та відмовостійкість. За традицією, вибір починається з категорії бази даних, SQL або NoSQL, оскільки кожна категорія надає чіткий набір компромісів. Висока продуктивність з погляду низької затримки та високої пропускної спроможності зазвичай розглядається як вимога, що не допускає компромісів, і тому є необхідною для будь-якої бази даних з вибірки.

Мета цієї статті – допомогти розробникам додатків зробити правильний вибір між SQL та NoSQL у контексті моделювання даних програми. Ми розглянемо одну SQL базу даних, а саме PostgreSQL та дві NoSQL бази даних – Cassandra та MongoDB, щоб розповісти про основи проектування баз даних, такі як створення таблиць, їх заповнення, читання даних з таблиці та їх видалення. У наступній статті ми обов'язково розглянемо індекси, транзакції, JOIN'и, директиви TTL та проектування баз даних на основі JSON.

У чому відмінність SQL від NoSQL?

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

Враховуючи їхню монолітну/одновузлову архітектуру та використання моделі реплікації master-slave для надмірності, традиційні SQL бази даних не мають двох важливих особливостей – лінійної масштабованості запису (тобто автоматичного поділу на кілька вузлів) та автоматичної/нульової втрати даних. Це означає, що обсяг одержуваних даних не може перевищувати максимальну пропускну запис одного вузла. Крім цього, деяка тимчасова втрата даних має бути врахована при стійкості до відмови (в архітектурі без поділу ресурсів). Тут треба мати на увазі, що нещодавні комміти ще не позначилися на підлеглій (slave) копії. Оновлення без простою також важкодоступні в SQL базах даних.

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

NoSQL акцентується на досягненні високої продуктивності в розподіленому кластері і це є основним обґрунтуванням безлічі компромісів проектування баз даних, які включають втрату транзакцій ACID, JOIN і узгоджені глобальні вторинні індекси.

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

Наступна таблиця показує, як моделювання даних у NoSQL відрізняється від SQL.

Основи проектування баз даних – порівняння PostgreSQL, Cassandra та MongoDB

SQL та NoSQL: Чому потрібні обидві?

На реальних додатках з великою кількістю користувачів, таких як Amazon.com, Netflix, Uber та Airbnb, лежить виконання складних розносортних завдань. Наприклад, додаток для електронної торгівлі подібний до Amazon.com повинен зберігати легковажні, високо-критичні дані, такі як інформація про користувачів, продукти, замовлення, рахунки-фактури, поряд з важкими, але менш чутливими даними, такими як огляди продуктів, повідомлення служби підтримки , активність користувачів, відгуки та рекомендації користувачів. Звичайно, ці програми покладаються принаймні на одну SQL базу даних поряд з як мінімум однією базою даних NoSQL. У міжрегіональних та глобальних системах, NoSQL база даних працює як георозподілений кеш для даних, що зберігаються в довіреному джерелі, SQL базі даних, що працює в якомусь одному регіоні.

Як YugaByte DB об'єднує SQL і NoSQL?

Побудована на лог-орієнтованому змішаному двигуні для зберігання, авто-шардингу, шардингової розподіленої консенсусної реплікації та розподілених транзакціях ACID (натхнених Google Spanner), YugaByte DB є першою у світі базою даних з відкритим вихідним кодом, яка одночасно сумісна з No & ) та SQL (PostgreSQL). Як показано в таблиці нижче, YCQL, API YugaByte DB, сумісне з Cassandra, додає поняття одно- і багатоключові ACID транзакції та глобальні вторинні індекси в API NoSQL, тим самим відкриваючи еру транзакційних NoSQL баз даних. Крім того, YCQL, API YugaByte DB сумісне з PostgreSQL, додає поняття лінійного масштабування запису та автоматичної відмовостійкості до SQL API, являючи світові розподілені SQL бази даних. Оскільки база даних YugaByte DB є транзакційною по суті, то API NoSQL тепер можна використовувати в контексті критично важливих даних.

Основи проектування баз даних – порівняння PostgreSQL, Cassandra та MongoDB

Як раніше було сказано у статті "Introducing YSQL: A PostgreSQL Compatible Distributed SQL API для YugaByte DB", Вибір між SQL або NoSQL в YugaByte DB повністю залежить від характеристик основного робочого навантаження:

  • Якщо основне робоче навантаження – це багатоключові операції з JOIN'ами, то при виборі YSQL розумійте, що ваші ключі можуть бути розподілені по кількох вузлах, що призведе до більш високої затримки та/або зниження пропускної спроможності, ніж у NoSQL.
  • В іншому випадку виберіть будь-який з двох NoSQL API, пам'ятаючи про те, що ви отримаєте вищу продуктивність в результаті запитів, що обслуговуються з одного вузла за раз. YugaByte DB може бути єдиною операційною базою даних для реальних складних додатків, у яких необхідно керувати кількома робочими навантаженнями одночасно.

В основі лабораторії моделювання даних (Data modeling lab) у наступному розділі лежать сумісні з PostgreSQL та Cassandra API бази даних YugaByte DB на відміну від вихідних баз даних. Цей підхід підкреслює простоту взаємодії з двома різними API (на двох різних портах) одного і того ж кластера баз даних на відміну від використання повністю незалежних кластерів двох різних баз даних.
У наступних розділах ми познайомимося з лабораторією моделювання даних, щоб проілюструвати відмінність та деякі спільні риси даних даних.

Лабораторія моделювання даних

Встановлення баз даних

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

Сумісна з PostgreSQL & Cassandra, база даних YugaByte DB

mkdir ~/yugabyte && cd ~/yugabyte
wget https://downloads.yugabyte.com/yb-docker-ctl && chmod +x yb-docker-ctl
docker pull yugabytedb/yugabyte
./yb-docker-ctl create --enable_postgres

MongoDB

docker run --name my-mongo -d mongo:latest

Доступ через командний рядок

Давайте підключимося до баз даних за допомогою оболонки командного рядка для відповідних API.

PostgreSQL

psql - Це оболонка командного рядка для взаємодії з PostgreSQL. Для простоти використання YugaByte DB поставляється з psql у папці bin.

docker exec -it yb-postgres-n1 /home/yugabyte/postgres/bin/psql -p 5433 -U postgres

Кассандра

сqlsh - Це оболонка командного рядка для взаємодії з Cassandra та її сумісними базами даних через CQL (мова запитів Cassandra). Для зручності використання YugaByte DB поставляється з cqlsh у каталозі bin.
Зверніть увагу, що CQL був натхненний SQL і має аналогічні поняття таблиць, рядків, стовпців та індексів. Однак, як мова NoSQL, він додає певний набір обмежень, більшість з яких ми також розглянемо в інших статтях.

docker exec -it yb-tserver-n1 /home/yugabyte/bin/cqlsh

MongoDB

манго – це оболонка командного рядка взаємодії з MongoDB. Її можна знайти в каталозі bin інсталяції MongoDB.

docker exec -it my-mongo bash 
cd bin
mongo

Створення таблиці

Зараз ми можемо взаємодіяти з базою даних для виконання різних операцій за допомогою командного рядка. Почнемо зі створення таблиці, яка зберігає інформацію про пісні написані різними виконавцями. Ці пісні можуть бути частиною альбому. Також опціональні атрибути для пісні – рік випуску, ціна, жанр та рейтинг. Нам потрібно враховувати додаткові атрибути, які можуть знадобитися у майбутньому через поле «теги». Воно може зберігати напівструктуровані дані у вигляді пар ключ-значення.

PostgreSQL

CREATE TABLE Music (
    Artist VARCHAR(20) NOT NULL, 
    SongTitle VARCHAR(30) NOT NULL,
    AlbumTitle VARCHAR(25),
    Year INT,
    Price FLOAT,
    Genre VARCHAR(10),
    CriticRating FLOAT,
    Tags TEXT,
    PRIMARY KEY(Artist, SongTitle)
);	

Кассандра

Створення таблиці у Cassandra дуже схоже на PostgreSQL. Однією з основних відмінностей є відсутність обмежень цілісності (наприклад, NOT NULL), але це входить у зону відповідальності програми, а чи не NoSQL бази даних. Первинний ключ складається з ключа розділу (стовпець Artist у прикладі нижче) та набору стовпців кластеризації (стовпець SongTitle у наведеному нижче прикладі). Ключ розділу визначає який розділ/шард помісити рядок, а стовпці кластеризації вказують, як мають бути організовані дані всередині поточного шарда.

CREATE KEYSPACE myapp;
USE myapp;
CREATE TABLE Music (
    Artist TEXT, 
    SongTitle TEXT,
    AlbumTitle TEXT,
    Year INT,
    Price FLOAT,
    Genre TEXT,
    CriticRating FLOAT,
    Tags TEXT,
    PRIMARY KEY(Artist, SongTitle)
);

MongoDB

MongoDB організовує дані в бази даних (Database) (аналогічно Keyspace в Cassandra), де є колекції (Collections) (аналогічно таблицям), у яких лежать документи (Documents) (аналогічно рядкам таблиці). У MongoDB у принципі не потрібне визначення початкової схеми. Команда "use database", показана нижче, створює екземпляр бази даних під час першого виклику та змінює контекст для новоствореної бази даних. Навіть колекції не потрібно створювати явно, вони створюються автоматично, просто при додаванні першого документа до нової колекції. Зверніть увагу, що MongoDB за промовчанням використовує тестову базу даних, тому будь-яка операція рівня колекцій без вказівки конкретної бази буде виконуватися в ній за промовчанням.

use myNewDatabase;

Отримання інформації про таблицю
PostgreSQL

d Music
Table "public.music"
    Column    |         Type          | Collation | Nullable | Default 
--------------+-----------------------+-----------+----------+--------
 artist       | character varying(20) |           | not null | 
 songtitle    | character varying(30) |           | not null | 
 albumtitle   | character varying(25) |           |          | 
 year         | integer               |           |          | 
 price        | double precision      |           |          | 
 genre        | character varying(10) |           |          | 
 criticrating | double precision      |           |          | 
 tags         | text                  |           |          | 
Indexes:
    "music_pkey" PRIMARY KEY, btree (artist, songtitle)

Кассандра

DESCRIBE TABLE MUSIC;
CREATE TABLE myapp.music (
    artist text,
    songtitle text,
    albumtitle text,
    year int,
    price float,
    genre text,
    tags text,
    PRIMARY KEY (artist, songtitle)
) WITH CLUSTERING ORDER BY (songtitle ASC)
    AND default_time_to_live = 0
    AND transactions = {'enabled': 'false'};

MongoDB

use myNewDatabase;
show collections;

Внесення даних до таблиці
PostgreSQL

INSERT INTO Music 
    (Artist, SongTitle, AlbumTitle, 
    Year, Price, Genre, CriticRating, 
    Tags)
VALUES(
    'No One You Know', 'Call Me Today', 'Somewhat Famous',
    2015, 2.14, 'Country', 7.8,
    '{"Composers": ["Smith", "Jones", "Davis"],"LengthInSeconds": 214}'
);
INSERT INTO Music 
    (Artist, SongTitle, AlbumTitle, 
    Price, Genre, CriticRating)
VALUES(
    'No One You Know', 'My Dog Spot', 'Hey Now',
    1.98, 'Country', 8.4
);
INSERT INTO Music 
    (Artist, SongTitle, AlbumTitle, 
    Price, Genre)
VALUES(
    'The Acme Band', 'Look Out, World', 'The Buck Starts Here',
    0.99, 'Rock'
);
INSERT INTO Music 
    (Artist, SongTitle, AlbumTitle, 
    Price, Genre, 
    Tags)
VALUES(
    'The Acme Band', 'Still In Love', 'The Buck Starts Here',
    2.47, 'Rock', 
    '{"radioStationsPlaying": ["KHCR", "KBQX", "WTNR", "WJJH"], "tourDates": { "Seattle": "20150625", "Cleveland": "20150630"}, "rotation": Heavy}'
);

Кассандра

Загалом вираз INSERT у Cassandra виглядає дуже схоже на аналогічне у PostgreSQL. Однак є одна велика різниця в семантиці. У Cassandra INSERT фактично є операцією UPSERT, де до рядка додаються останні значення, якщо рядок вже існує.

Введення даних відбувається аналогічно PostgreSQL INSERT вище

.

MongoDB

Незважаючи на те, що MongoDB є NoSQL базою даних, подібно до Cassandra, її операція внесення даних не має нічого спільного з семантичною поведінкою в Cassandra. У MongoDB вставити () не має можливостей UPSERTщо робить його схожим на PostgreSQL. Додавання даних за замовчуванням без _idspecified призведе до додавання нового документа до колекції.

db.music.insert( {
artist: "No One You Know",
songTitle: "Call Me Today",
albumTitle: "Somewhat Famous",
year: 2015,
price: 2.14,
genre: "Country",
tags: {
Composers: ["Smith", "Jones", "Davis"],
LengthInSeconds: 214
}
}
);
db.music.insert( {
artist: "No One You Know",
songTitle: "My Dog Spot",
albumTitle: "Hey Now",
price: 1.98,
genre: "Country",
criticRating: 8.4
}
);
db.music.insert( {
artist: "The Acme Band",
songTitle: "Look Out, World",
albumTitle:"The Buck Starts Here",
price: 0.99,
genre: "Rock"
}
);
db.music.insert( {
artist: "The Acme Band",
songTitle: "Still In Love",
albumTitle:"The Buck Starts Here",
price: 2.47,
genre: "Rock",
tags: {
radioStationsPlaying:["KHCR", "KBQX", "WTNR", "WJJH"],
tourDates: {
Seattle: "20150625",
Cleveland: "20150630"
},
rotation: "Heavy"
}
}
);

Запит таблиці

Можливо, найбільш суттєва різниця між SQL та NoSQL з погляду складання запитів полягає у використанні формулювань FROM и WHERE. SQL дозволяє після висловлювання FROM вибирати кілька таблиць, а вираз з WHERE може бути будь-якої складності (включаючи операції JOIN між таблицями). Однак NoSQL має тенденцію накладати жорстке обмеження на FROM, і працювати тільки з однією вказаною таблицею, а в WHERE, завжди має бути вказаний первинний ключ. Це пов'язано із прагненням до підвищення продуктивності NoSQL, про яке ми говорили раніше. Це прагнення призводить до будь-якого зменшення будь-якої крос-табличного та крос-ключового взаємодії. Воно може призвести до великої затримки в міжвузловому зв'язку при відповіді запит і, отже, його найкраще уникати у принципі. Наприклад, Cassandra вимагає, щоб запити були обмежені певними операторами (дозволено лише =, IN, <, >, =>, <=) на ключах розділів, за винятком випадків запиту вторинного індексу (тут дозволено лише оператор =).

PostgreSQL

Далі будуть наведені три приклади запитів, які легко можуть бути виконані SQL базою даних.

  • Вивести усі пісні виконавця;
  • Вивести всі пісні виконавця, що збігаються з першою частиною назви;
  • Вивести всі пісні виконавця, які мають певне слово у назві та мають ціну менше 1.00.
SELECT * FROM Music
WHERE Artist='No One You Know';
SELECT * FROM Music
WHERE Artist='No One You Know' AND SongTitle LIKE 'Call%';
SELECT * FROM Music
WHERE Artist='No One You Know' AND SongTitle LIKE '%Today%'
AND Price > 1.00;

Кассандра

З перерахованих вище запитів PostgreSQL тільки перший працюватиме в Cassandra без змін, оскільки оператор LIKE не можна застосовувати до стовпців кластеризації, таких як SongTitle. У цьому випадку допускаються лише оператори = и IN.

SELECT * FROM Music
WHERE Artist='No One You Know';
SELECT * FROM Music
WHERE Artist='No One You Know' AND SongTitle IN ('Call Me Today', 'My Dog Spot')
AND Price > 1.00;

MongoDB

Як показано в попередніх прикладах, основним методом створення запитів до MongoDB є db.collection.find(). Цей метод явно містить у собі ім'я колекції (music (на прикладі нижче), тому запит за кількома колекціями заборонений.

db.music.find( {
  artist: "No One You Know"
 } 
);
db.music.find( {
  artist: "No One You Know",
  songTitle: /Call/
 } 
);

Зчитування всіх рядків таблиці

Читання всіх рядків — це окремий випадок того шаблону запиту, який ми розглядали раніше.

PostgreSQL

SELECT * 
FROM Music;

Кассандра

Аналогічно прикладу в PostgreSQL вище.

MongoDB

db.music.find( {} );

Редагування даних у таблиці

PostgreSQL

PostgreSQL надає інструкцію UPDATE зміни даних. Вона не має можливостей UPSERTтому виконання цієї інструкції завершиться помилкою, якщо рядка більше немає в базі даних.

UPDATE Music
SET Genre = 'Disco'
WHERE Artist = 'The Acme Band' AND SongTitle = 'Still In Love';

Кассандра

У Cassandra є UPDATE аналогічний PostgreSQL. UPDATE має ту ж семантику UPSERTподібно INSERT.

Аналогічно прикладу в PostgreSQL вище.

MongoDB
Операція update () MongoDB може повністю оновити існуючий документ або оновити лише певні поля. За умовчанням вона оновлює лише один документ із відключеною семантикою UPSERT. Оновлення кількох документів та поведінка аналогічна UPSERT можна застосувати, встановивши додаткові прапори для операції. Як, наприклад, у наведеному нижче прикладі відбувається оновлення жанру конкретного виконавця за його піснею.

db.music.update(
  {"artist": "The Acme Band"},
  { 
    $set: {
      "genre": "Disco"
    }
  },
  {"multi": true, "upsert": true}
);

Видалення даних із таблиці

PostgreSQL

DELETE FROM Music
WHERE Artist = 'The Acme Band' AND SongTitle = 'Look Out, World';

Кассандра

Аналогічно прикладу в PostgreSQL вище.

MongoDB

У MongoDB є два типи операцій для видалення документів. deleteOne() /deleteMany() и видалити (). Обидва види видаляють документи, але повертають різні результати.

db.music.deleteMany( {
        artist: "The Acme Band"
    }
);

Видалення таблиці

PostgreSQL

DROP TABLE Music;

Кассандра

Аналогічно прикладу в PostgreSQL вище.

MongoDB

db.music.drop();

Висновок

Суперечки про вибір між SQL та NoSQL вирують вже понад 10 років. Є два основні аспекти цієї суперечки: архітектура ядра бази даних (монолітний, транзакційний SQL проти розподіленого, нетранзакційного NoSQL) та підхід до проектування бази даних (моделювання даних у SQL проти моделювання ваших запитів у NoSQL).

З розподіленою транзакційною базою даних, такою як YugaByte DB, дебати щодо архітектури бази даних можуть бути легко розвіяні. У міру того, як обсяги даних стають більшими, ніж те, що може бути записано в один вузол, повністю розподілена архітектура, яка підтримує лінійну масштабованість запису з автоматичним шардингом/ребалансуванням, стає необхідною.

Крім того, як сказано в одній із статей Google Cloud, транзакційні, строго узгоджені архітектури тепер ширше застосовуються забезпечення кращої гнучкості у створенні, ніж нетранзакційні, зрештою узгоджені архітектури.

Повертаючись до обговорення проектування баз даних, справедливо сказати, що обидва підходи до проектування (SQL та NoSQL) необхідні для будь-якої складної реальної програми. Підхід SQL «моделювання даних» дозволяє розробникам легше задовольняти мінливі бізнес-вимоги, у той час як підхід NoSQL «моделювання запитів» дозволяє тим же розробникам оперувати великими обсягами даних, маючи невелику затримку та високу пропускну здатність. Саме з цієї причини YugaByte DB надає SQL та NoSQL API у загальному ядрі, а не пропагує якийсь один із підходів. Крім того, забезпечуючи сумісність із популярними мовами баз даних, включаючи PostgreSQL та Cassandra, YugaByte DB гарантує, що розробникам не доведеться вивчати іншу мову, щоб працювати з розподіленим строго узгодженим ядром бази даних.

У цій статті ми розібралися, як основи проектування баз даних різняться у PostgreSQL, Cassandra та MongoDB. У наступних статтях ми поринемо у передові концепції проектування, такі як індекси, транзакції, JOIN'и, директиви TTL та JSON-документи.

Бажаємо вам відмінно провести вихідні, що залишилися, і запрошуємо на безкоштовний вебінар, що пройде вже 14 травня.

Джерело: habr.com

Додати коментар або відгук