Podstawy projektowania baz danych - porównanie PostgreSQL, Cassandra i MongoDB

Cześć przyjaciele. Przed wyjazdem na drugą część majowych wakacji dzielimy się z Wami materiałem, który przetłumaczyliśmy w oczekiwaniu na uruchomienie nowego streamu na kursie „Relacyjny system zarządzania bazą danych”.

Podstawy projektowania baz danych - porównanie PostgreSQL, Cassandra i MongoDB

Twórcy aplikacji spędzają dużo czasu na porównywaniu wielu operacyjnych baz danych, aby wybrać tę, która najlepiej odpowiada zamierzonemu obciążeniu. Potrzeby mogą obejmować uproszczone modelowanie danych, gwarancje transakcyjne, wydajność odczytu/zapisu, skalowanie poziome i odporność na błędy. Tradycyjnie wybór rozpoczyna się od kategorii bazy danych, SQL lub NoSQL, ponieważ każda kategoria przedstawia jasny zestaw kompromisów. Wysoka wydajność pod względem małych opóźnień i dużej przepustowości jest ogólnie postrzegana jako wymóg bezkompromisowy i dlatego jest niezbędna w przypadku każdej przykładowej bazy danych.

Celem tego artykułu jest pomoc twórcom aplikacji w dokonaniu właściwego wyboru pomiędzy SQL i NoSQL w kontekście modelowania danych aplikacji. Przyjrzymy się jednej bazie danych SQL, mianowicie PostgreSQL, oraz dwóm bazom danych NoSQL, Cassandra i MongoDB, aby omówić podstawy projektowania baz danych, takie jak tworzenie tabel, ich wypełnianie, odczytywanie danych z tabeli i usuwanie ich. W następnym artykule z pewnością przyjrzymy się indeksom, transakcjom, JOINom, dyrektywom TTL i projektowaniu baz danych w oparciu o JSON.

Jaka jest różnica między SQL i NoSQL?

Bazy danych SQL zwiększają elastyczność aplikacji dzięki gwarancji transakcyjnej ACID, a także możliwości wykonywania zapytań o dane przy użyciu funkcji JOIN w nieoczekiwany sposób w oparciu o istniejące znormalizowane relacyjne modele baz danych.

Biorąc pod uwagę ich monolityczną/jednowęzłową architekturę i wykorzystanie modelu replikacji master-slave w celu zapewnienia redundancji, tradycyjnym bazom danych SQL brakuje dwóch ważnych funkcji - skalowalności zapisu liniowego (tj. automatycznego podziału na wiele węzłów) oraz automatycznej/zerowej utraty danych. Oznacza to, że ilość odebranych danych nie może przekroczyć maksymalnej przepustowości zapisu pojedynczego węzła. Ponadto należy uwzględnić tymczasową utratę danych w ramach odporności na awarie (w architekturze typu „shared-nothing”). Tutaj należy pamiętać, że ostatnie zatwierdzenia nie zostały jeszcze odzwierciedlone w kopii slave. Aktualizacje bez przestojów są również trudne do osiągnięcia w bazach danych SQL.

Bazy danych NoSQL są zwykle dystrybuowane z natury, tj. w nich dane są podzielone na sekcje i rozproszone w kilku węzłach. Wymagają denormalizacji. Oznacza to, że wprowadzone dane również muszą zostać skopiowane kilkukrotnie, aby móc odpowiedzieć na konkretne, przesłane przez Ciebie zapytania. Ogólnym celem jest uzyskanie wysokiej wydajności poprzez zmniejszenie liczby fragmentów dostępnych podczas odczytu. Oznacza to, że NoSQL wymaga modelowania zapytań, podczas gdy SQL wymaga modelowania danych.

NoSQL koncentruje się na osiągnięciu wysokiej wydajności w rozproszonym klastrze i jest to podstawowe uzasadnienie wielu kompromisów w projektowaniu baz danych, które obejmują utratę transakcji ACID, JOIN i spójne globalne indeksy wtórne.

Istnieje argument, że chociaż bazy danych NoSQL zapewniają liniową skalowalność zapisu i wysoką odporność na błędy, utrata gwarancji transakcyjnych sprawia, że ​​nie nadają się one do przechowywania danych o znaczeniu krytycznym.

Poniższa tabela pokazuje, czym modelowanie danych w NoSQL różni się od SQL.

Podstawy projektowania baz danych - porównanie PostgreSQL, Cassandra i MongoDB

SQL i NoSQL: dlaczego potrzebne są oba?

Aplikacje w świecie rzeczywistym z dużą liczbą użytkowników, takie jak Amazon.com, Netflix, Uber i Airbnb, mają za zadanie wykonywanie złożonych, wieloaspektowych zadań. Na przykład aplikacja e-commerce, taka jak Amazon.com, musi przechowywać lekkie, bardzo krytyczne dane, takie jak informacje o użytkowniku, produkty, zamówienia, faktury, wraz z ciężkimi, mniej wrażliwymi danymi, takimi jak recenzje produktów, wiadomości wsparcia, aktywność użytkownika, opinie i rekomendacje użytkowników. Naturalnie aplikacje te opierają się na co najmniej jednej bazie danych SQL i co najmniej jednej bazie danych NoSQL. W systemach międzyregionalnych i globalnych baza danych NoSQL działa jako rozproszona geograficznie pamięć podręczna dla danych przechowywanych w zaufanej źródłowej bazie danych SQL działającej w określonym regionie.

W jaki sposób YugaByte DB łączy SQL i NoSQL?

Zbudowany w oparciu o silnik pamięci masowej zorientowany na dzienniki, automatyczne dzielenie na fragmenty, rozproszoną replikację konsensusową na fragmenty i rozproszone transakcje ACID (inspirowane Google Spanner), YugaByte DB to pierwsza na świecie baza danych typu open source, która jest jednocześnie kompatybilna z NoSQL (Cassandra i Redis) i SQL (PostgreSQL). Jak pokazano w poniższej tabeli, YCQL, interfejs API YugaByte DB kompatybilny z Cassandrą, dodaje koncepcje jedno- i wielokluczowych transakcji ACID oraz globalnych indeksów wtórnych do interfejsu API NoSQL, rozpoczynając w ten sposób erę transakcyjnych baz danych NoSQL. Dodatkowo YCQL, interfejs API YugaByte DB kompatybilny z PostgreSQL, dodaje koncepcje liniowego skalowania zapisu i automatycznej odporności na błędy do interfejsu API SQL, udostępniając światu rozproszone bazy danych SQL. Ponieważ YugaByte DB ma charakter transakcyjny, API NoSQL można teraz używać w kontekście danych o znaczeniu krytycznym.

Podstawy projektowania baz danych - porównanie PostgreSQL, Cassandra i MongoDB

Jak już wspomniano w artykule „Przedstawiamy YSQL: rozproszony interfejs API SQL zgodny z PostgreSQL dla YugaByte DB”, wybór pomiędzy SQL lub NoSQL w YugaByte DB zależy całkowicie od charakterystyki podstawowego obciążenia:

  • Jeśli Twoim głównym obciążeniem są wieloklawiszowe operacje JOIN, wybierając YSQL, pamiętaj, że Twoje klucze mogą być rozproszone w wielu węzłach, co skutkuje większymi opóźnieniami i/lub niższą przepustowością niż NoSQL.
  • W przeciwnym razie wybierz jeden z dwóch interfejsów API NoSQL, pamiętając, że lepszą wydajność uzyskasz w wyniku zapytań obsługiwanych z jednego węzła na raz. YugaByte DB może służyć jako pojedyncza operacyjna baza danych dla złożonych aplikacji w świecie rzeczywistym, które wymagają jednoczesnego zarządzania wieloma obciążeniami.

Laboratorium modelowania danych w następnej sekcji opiera się na bazach danych YugaByte DB zgodnych z PostgreSQL i Cassandra API, w przeciwieństwie do natywnych baz danych. Podejście to kładzie nacisk na łatwość interakcji z dwoma różnymi interfejsami API (na dwóch różnych portach) tego samego klastra bazy danych, w przeciwieństwie do stosowania całkowicie niezależnych klastrów dwóch różnych baz danych.
W kolejnych sekcjach przyjrzymy się laboratorium modelowania danych, aby zilustrować różnice i niektóre cechy wspólne omawianych baz danych.

Laboratorium Modelowania Danych

Instalacja bazy danych

Biorąc pod uwagę nacisk na projektowanie modeli danych (a nie na złożone architektury wdrażania), zainstalujemy bazy danych w kontenerach Docker na komputerze lokalnym, a następnie będziemy z nimi wchodzić w interakcję za pomocą odpowiednich powłok wiersza poleceń.

Baza danych YugaByte DB kompatybilna z PostgreSQL i Cassandrą

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

Dostęp do wiersza poleceń

Połączmy się z bazami danych za pomocą powłoki wiersza poleceń dla odpowiednich interfejsów API.

PostgreSQL

Psql to powłoka wiersza poleceń do interakcji z PostgreSQL. Aby ułatwić obsługę, YugaByte DB zawiera psql bezpośrednio w folderze bin.

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

Cassandra

cqlsh to powłoka wiersza poleceń umożliwiająca interakcję z Cassandrą i jej kompatybilnymi bazami danych za pośrednictwem języka CQL (Cassandra Query Language). Aby ułatwić obsługę, YugaByte DB jest wyposażony w cqlsh w katalogu bin.
Należy zauważyć, że CQL został zainspirowany językiem SQL i ma podobne koncepcje tabel, wierszy, kolumn i indeksów. Jednak jako język NoSQL dodaje pewien zestaw ograniczeń, z których większość omówimy również w innych artykułach.

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

MongoDB

Mongo to powłoka wiersza poleceń do interakcji z MongoDB. Można go znaleźć w katalogu bin instalacji MongoDB.

docker exec -it my-mongo bash 
cd bin
mongo

Utwórz tabelę

Teraz możemy wchodzić w interakcję z bazą danych, aby wykonywać różne operacje za pomocą wiersza poleceń. Zacznijmy od stworzenia tabeli przechowującej informacje o piosenkach napisanych przez różnych artystów. Te utwory mogą stanowić część albumu. Opcjonalne atrybuty utworu to także rok wydania, cena, gatunek i ocena. Musimy uwzględnić dodatkowe atrybuty, które mogą być potrzebne w przyszłości poprzez pole „tagi”. Może przechowywać dane częściowo ustrukturyzowane w postaci par klucz-wartość.

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

Tworzenie tabeli w Cassandrze jest bardzo podobne do PostgreSQL. Jedną z głównych różnic jest brak ograniczeń integralności (np. NOT NULL), ale za to odpowiada aplikacja, a nie baza danych NoSQL. Klucz podstawowy składa się z klucza partycji (kolumna Artysta w poniższym przykładzie) i zestawu kolumn grupujących (kolumna SongTitle w poniższym przykładzie). Klucz partycji określa, w której partycji/fragmencie należy umieścić wiersz, a kolumny grupowania wskazują, w jaki sposób dane powinny być zorganizowane w bieżącym fragmencie.

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 organizuje dane w bazy danych (baza danych) (podobnie jak Keyspace w Cassandrze), w których znajdują się kolekcje (podobnie jak tabele) zawierające dokumenty (podobnie jak wiersze w tabeli). W MongoDB w zasadzie nie ma potrzeby definiowania początkowego schematu. Zespół „użyj bazy danych”pokazany poniżej tworzy instancję bazy danych przy pierwszym wywołaniu i zmienia kontekst nowo utworzonej bazy danych. Nawet kolekcje nie muszą być tworzone bezpośrednio; są tworzone automatycznie, po prostu podczas dodawania pierwszego dokumentu do nowej kolekcji. Należy pamiętać, że MongoDB domyślnie korzysta z testowej bazy danych, więc każda operacja na poziomie kolekcji bez określenia konkretnej bazy danych będzie domyślnie na niej uruchamiana.

use myNewDatabase;

Uzyskiwanie informacji o stole
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)

Cassandra

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;

Wprowadzanie danych do tabeli
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}'
);

Cassandra

Ogólny wyraz INSERT w Cassandrze wygląda bardzo podobnie do tego w PostgreSQL. Istnieje jednak jedna duża różnica w semantyce. W Cassandrze INSERT jest właściwie operacją UPSERT, gdzie do wiersza dodawane są ostatnie wartości, jeśli wiersz już istnieje.

Wprowadzanie danych jest podobne do PostgreSQL INSERT powyżej

.

MongoDB

Mimo że MongoDB jest bazą danych NoSQL, taką jak Cassandra, operacja wstawiania nie ma nic wspólnego z semantycznym zachowaniem Cassandry. W MongoDB wstawić() nie ma możliwości UPSERT, co czyni go podobnym do PostgreSQL. Dodawanie domyślnych danych bez _idspecified spowoduje dodanie nowego dokumentu do kolekcji.

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"
}
}
);

Zapytanie o tabelę

Być może najbardziej znaczącą różnicą między SQL i NoSQL pod względem konstrukcji zapytań jest używany język FROM и WHERE. SQL pozwala na wyrażenie po FROM wybierz wiele tabel i wyrażenie za pomocą WHERE może mieć dowolną złożoność (w tym operacje JOIN między stołami). Jednak NoSQL ma tendencję do nakładania poważnych ograniczeń FROMi pracuj tylko z jedną określoną tabelą oraz in WHERE, należy zawsze określić klucz podstawowy. Wiąże się to ze zwiększeniem wydajności NoSQL, o którym mówiliśmy wcześniej. To pragnienie prowadzi do wszelkiej możliwej redukcji wszelkich interakcji między tabelami i kluczami. Może wprowadzić duże opóźnienie w komunikacji między węzłami podczas odpowiadania na żądanie i dlatego ogólnie najlepiej go unikać. Na przykład Cassandra wymaga, aby zapytania były ograniczone do niektórych operatorów (tylko =, IN, <, >, =>, <=) na kluczach partycji, z wyjątkiem żądania indeksu dodatkowego (dozwolony jest tutaj tylko operator =).

PostgreSQL

Poniżej znajdują się trzy przykłady zapytań, które można łatwo wykonać w bazie danych SQL.

  • Wyświetl wszystkie utwory danego artysty;
  • Wyświetl wszystkie utwory artysty, które pasują do pierwszej części tytułu;
  • Wyświetl wszystkie utwory danego artysty, które mają w tytule określone słowo i mają cenę niższą niż 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;

Cassandra

Z wymienionych powyżej zapytań PostgreSQL tylko pierwsze będzie działać w Cassandrze bez zmian, ponieważ operator LIKE nie można zastosować do grupowania kolumn, takich jak SongTitle. W takim przypadku dozwolone są tylko operatorzy = и 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

Jak pokazano w poprzednich przykładach, główną metodą tworzenia zapytań w MongoDB jest db.kolekcja.znajdź(). Ta metoda jawnie zawiera nazwę kolekcji (music w przykładzie poniżej), dlatego wykonywanie zapytań do wielu kolekcji jest zabronione.

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

Czytanie wszystkich wierszy tabeli

Odczyt wszystkich wierszy jest po prostu szczególnym przypadkiem wzorca zapytania, który omawialiśmy wcześniej.

PostgreSQL

SELECT * 
FROM Music;

Cassandra

Podobny do powyższego przykładu PostgreSQL.

MongoDB

db.music.find( {} );

Edycja danych w tabeli

PostgreSQL

PostgreSQL dostarcza instrukcje UPDATE zmienić dane. Ona nie ma żadnych możliwości UPSERT, więc ta instrukcja nie powiedzie się, jeśli wiersza nie ma już w bazie danych.

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

Cassandra

Cassandra ma UPDATE podobny do PostgreSQL. UPDATE ma tę samą semantykę UPSERT, tak jak INSERT.

Podobny do powyższego przykładu PostgreSQL.

MongoDB
operacja aktualizacja() w MongoDB może całkowicie zaktualizować istniejący dokument lub zaktualizować tylko niektóre pola. Domyślnie aktualizuje tylko jeden dokument z wyłączoną semantyką UPSERT. Aktualizowanie wielu dokumentów i podobne zachowanie UPSERT można zastosować, ustawiając dodatkowe flagi dla operacji. Na przykład w poniższym przykładzie gatunek konkretnego artysty jest aktualizowany na podstawie jego utworu.

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

Usuwanie danych z tabeli

PostgreSQL

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

Cassandra

Podobny do powyższego przykładu PostgreSQL.

MongoDB

MongoDB ma dwa rodzaje operacji usuwania dokumentów − usuńJeden() /usuńWiele() и usunąć(). Obydwa typy usuwają dokumenty, ale zwracają różne wyniki.

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

Usuwanie tabeli

PostgreSQL

DROP TABLE Music;

Cassandra

Podobny do powyższego przykładu PostgreSQL.

MongoDB

db.music.drop();

wniosek

Debata na temat wyboru pomiędzy SQL a NoSQL trwa już od ponad 10 lat. Debata ta obejmuje dwa główne aspekty: architekturę silnika bazy danych (monolityczny, transakcyjny SQL vs rozproszony, nietransakcyjny NoSQL) oraz podejście do projektowania baz danych (modelowanie danych w SQL vs modelowanie zapytań w NoSQL).

Dzięki rozproszonej transakcyjnej bazie danych, takiej jak YugaByte DB, można łatwo uciszyć debatę na temat architektury baz danych. Ponieważ wolumeny danych stają się większe niż to, co można zapisać w pojedynczym węźle, konieczna staje się w pełni rozproszona architektura obsługująca skalowalność zapisu liniowego z automatycznym fragmentowaniem/ponownym równoważeniem.

Poza tym, jak stwierdzono w jednym z artykułów Google CloudTransakcyjne, silnie spójne architektury są obecnie częściej wykorzystywane w celu zapewnienia większej elastyczności programowania niż nietransakcyjne, ostatecznie spójne architektury.

Wracając do dyskusji na temat projektowania baz danych, można śmiało powiedzieć, że oba podejścia do projektowania (SQL i NoSQL) są niezbędne w przypadku każdej złożonej aplikacji w świecie rzeczywistym. Podejście oparte na „modelowaniu danych” SQL umożliwia programistom łatwiejsze spełnianie zmieniających się wymagań biznesowych, podczas gdy podejście „modelowania zapytań” NoSQL umożliwia tym samym programistom działanie na dużych ilościach danych przy niskim opóźnieniu i dużej przepustowości. Z tego powodu YugaByte DB udostępnia interfejsy API SQL i NoSQL we wspólnym rdzeniu, zamiast promować jedno z podejść. Dodatkowo, zapewniając kompatybilność z popularnymi językami baz danych, w tym PostgreSQL i Cassandra, YugaByte DB gwarantuje, że programiści nie muszą uczyć się innego języka, aby pracować z rozproszonym, wysoce spójnym silnikiem bazy danych.

W tym artykule przyjrzeliśmy się, jak różnią się podstawy projektowania baz danych między PostgreSQL, Cassandra i MongoDB. W przyszłych artykułach zagłębimy się w zaawansowane koncepcje projektowe, takie jak indeksy, transakcje, JOIN, dyrektywy TTL i dokumenty JSON.

Życzymy udanego wypoczynku w weekend i zapraszamy bezpłatne webinariumktóre odbędzie się 14 maja.

Źródło: www.habr.com

Dodaj komentarz