Cześć, przyjaciele. Przed wyjazdem na drugą część majowych świąt dzielimy się z Wami materiałem, który przetłumaczyliśmy w przeddzień uruchomienia nowej edycji kursu .

Programiści aplikacji spędzają dużo czasu na porównywaniu różnych operacyjnych baz danych, aby wybrać tę, która najlepiej pasuje do zakładanej obciążenia roboczego. Wymagania mogą obejmować uproszczone modelowanie danych, gwarancje transakcji, wydajność odczytu/zapisu, skalowanie horyzontalne oraz odporność na awarie. Tradycyjnie wybór rozpoczyna się od kategorii baz danych, SQL lub NoSQL, ponieważ każda kategoria oferuje wyraźny zestaw kompromisów. Wysoka wydajność w kontekście niskiego opóźnienia i dużej przepustowości jest zazwyczaj uważana za wymaganie, które nie dopuszcza kompromisów, dlatego jest niezbędna dla każdej bazy danych z wybory.
Celem tego artykułu jest pomoc programistom aplikacji w dokonaniu właściwego wyboru między SQL a NoSQL w kontekście modelowania danych aplikacji. Przyjrzymy się jednej bazie danych SQL, a 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, odczyt danych z tabeli oraz ich usuwanie. W następnym artykule na pewno przyjrzymy się indeksom, transakcjom, operacjom JOIN, dyrektywom TTL oraz projektowaniu baz danych opartych na JSON.
Czym różnią się SQL od NoSQL?
Bazy danych SQL zwiększają elastyczność aplikacji dzięki gwarancjom transakcji ACID oraz dzięki swojej zdolności do zapytywania danych za pomocą JOIN w nieoczekiwany sposób na bazie istniejących znormalizowanych modeli relacyjnych.
Biorąc pod uwagę ich monolityczną/ pojedynczo-węzłową architekturę oraz zastosowanie modelu replikacji master-slave dla zapewnienia nadmiarowości, tradycyjne bazy danych SQL brakuje dwóch istotnych cech – liniowej skalowalności zapisu (tzn. automatycznego podziału na kilka węzłów) oraz automatycznego/nulowej utraty danych. Oznacza to, że objętość danych odbieranych nie może przekroczyć maksymalnej przepustowości zapisu jednego węzła. Co więcej, pewna utrata danych w czasie musi być uwzględniana w kontekście odporności na awarie (w architekturze bez podziału zasobów). Należy pamiętać, że ostatnie zatwierdzenia jeszcze nie odzwierciedliły się w podrzędnej (slave) kopii. Aktualizacje bez przestojów są również trudne do osiągnięcia w bazach danych SQL.
Bazy danych NoSQL są z natury zazwyczaj rozproszone, co oznacza, że dane są dzielone na sekcje i rozdzielane pomiędzy kilka węzłów. Wymagają one denormalizacji. Oznacza to, że dane wprowadzone muszą być również kopiowane kilkakrotnie w celu odpowiedzi na konkretne zapytania, które wysyłasz. Ogólnym celem jest uzyskanie wysokiej wydajności poprzez zmniejszenie liczby shardów dostępnych podczas odczytu. Stąd wynika stwierdzenie, że NoSQL wymaga od Ciebie modelowania Twoich zapytań, podczas gdy SQL wymaga modelowania Twoich danych.
NoSQL koncentruje się na osiąganiu wysokiej wydajności w rozproszonym klastrze i jest to główne uzasadnienie wielu kompromisów w projektowaniu baz danych, które obejmują utratę gwarancji transakcji ACID, JOIN-y i zgodne globalne wtórne indeksy.
Istnieje przekonanie, że mimo iż bazy danych NoSQL zapewniają liniową skalowalność zapisu i wysoką odporność na awarie, utrata gwarancji transakcyjnych czyni je nieodpowiednimi dla krytycznych danych.
Tabela poniżej pokazuje, jak modelowanie danych w NoSQL różni się od SQL.

SQL i NoSQL: Dlaczego potrzebujemy obu?
W przypadku rzeczywistych zastosowań z dużą liczbą użytkowników, takich jak Amazon.com, Netflix, Uber i Airbnb, realizowane są skomplikowane i zróżnicowane zadania. Na przykład, aplikacja do handlu elektronicznego, podobna do Amazon.com, potrzebuje przechowywać lekkie, krytyczne dane, takie jak informacje o użytkownikach, produktach, zamówieniach, fakturach, obok cięższych, ale mniej wrażliwych danych, takich jak recenzje produktów, wiadomości wsparcia, aktywność użytkowników, opinie i rekomendacje użytkowników. Naturalnie, aplikacje te polegają przynajmniej na jednej bazie danych SQL oraz co najmniej jednej bazie danych NoSQL. W systemach międzyregionalnych i globalnych, baza danych NoSQL działa jako geograficznie rozproszony cache dla danych przechowywanych w zaufanym źródle, bazie danych SQL działającej w danym regionie.
Jak YugaByte DB łączy w sobie SQL i NoSQL?
Zbudowana na log-orientowanym silniku mieszanym do przechowywania, automatycznym shardingu, rozproszonym konsensusie replikacji shardów oraz rozproszonych transakcjach ACID (inspirowanych Google Spanner), YugaByte DB jest pierwszą na świecie bazą danych o otwartym kodzie źródłowym, która jednocześnie jest kompatybilna z NoSQL (Cassandra & Redis) i SQL (PostgreSQL). Jak pokazano w poniższej tabeli, YCQL, API YugaByte DB kompatybilne z Cassandrą, dodaje koncepcje ACID transakcji jedno- i wielokluczowych oraz globalnych indeksów wtórnych do API NoSQL, otwierając tym samym erę transakcyjnych baz danych NoSQL. Ponadto YCQL, API YugaByte DB kompatybilne z PostgreSQL, dodaje koncepcje liniowego skalowania zapisu oraz automatycznej odporności na awarie do API SQL, ukazując światu rozproszone bazy danych SQL. Ponieważ baza danych YugaByte DB jest z natury transakcyjna, API NoSQL może teraz być używane w kontekście danych krytycznych.

Jak wcześniej wspomniano w artykule , wybór między SQL a NoSQL w YugaByte DB zależy całkowicie od charakterystyki podstawowego obciążenia roboczego:
- Jeśli podstawowe obciążenie robocze polega na wielokluczowych operacjach z JOINami, to przy wyborze YSQL należy pamiętać, że klucze mogą być rozproszone po kilku węzłach, co może prowadzić do wyższej latencji i/lub obniżenia przepustowości w porównaniu do NoSQL.
- W przeciwnym razie wybierz dowolne z dwóch interfejsów API NoSQL, pamiętając, że uzyskasz lepszą wydajność dzięki zapytaniom realizowanym z jednego węzła na raz. YugaByte DB może służyć jako jednolita baza danych dla rzeczywistych złożonych aplikacji, które muszą obsługiwać wiele obciążeń jednocześnie.
W podstawie laboratorium modelowania danych (Data modeling lab) w kolejnej sekcji leżą zgodne z PostgreSQL i Cassandra API bazy danych YugaByte DB w odróżnieniu od oryginalnych baz danych. Podejście to podkreśla prostotę interakcji z dwoma różnymi interfejsami API (na dwóch różnych portach) tej samej klastrze baz danych, w przeciwieństwie do używania całkowicie niezależnych klastrów dwóch różnych baz danych.
W kolejnych sekcjach zapoznamy się z laboratorium modelowania danych, aby zilustrować różnice i niektóre wspólne cechy omawianych baz danych.
Laboratorium modelowania danych
Instalacja baz danych
Biorąc pod uwagę nacisk na projektowanie modelu danych (a nie na skomplikowane architektury wdrożeniowe), zainstalujemy bazy danych w kontenerach Docker na lokalnym komputerze, a następnie będziemy się z nimi komunikować, używając odpowiednich powłok wiersza poleceń.
Zgodna z PostgreSQL i Cassandra baza danych 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_postgresMongoDB
docker run --name my-mongo -d mongo:latestDostęp przez wiersz poleceń
Połączmy się z bazami danych za pomocą powłoki wiersza poleceń dla odpowiednich API.
PostgreSQL
— to powłoka wiersza poleceń do interakcji z PostgreSQL. Dla uproszczenia użycia YugaByte DB dostarczane jest z psql od razu w folderze bin.
docker exec -it yb-postgres-n1 /home/yugabyte/postgres/bin/psql -p 5433 -U postgresCassandra
— to powłoka wiersza poleceń do interakcji z Cassandrą i jej zgodnymi bazami danych przez CQL (język zapytań Cassandry). Dla wygody YugaByte DB dostarczane jest z cqlsh w katalogu bin.
Zauważ, że CQL został zainspirowany SQL i ma podobne pojęcia tabel, wierszy, kolumn i indeksów. Jednak jako język NoSQL dodaje pewien zestaw ograniczeń, z których większość również uwzględnimy w innych artykułach.
docker exec -it yb-tserver-n1 /home/yugabyte/bin/cqlshMongoDB
– to powłoka poleceń do interakcji z MongoDB. Można ją znaleźć w katalogu bin instalacji MongoDB.
docker exec -it my-mongo bash
cd bin
mongoTworzenie tabeli
Teraz możemy interagować z bazą danych, aby wykonywać różne operacje przy użyciu wiersza poleceń. Zaczniemy od utworzenia tabeli, która przechowuje informacje o piosenkach napisanych przez różnych wykonawców. Te piosenki mogą być częścią albumu. Dodatkowe opcjonalne atrybuty dla piosenki to rok wydania, cena, gatunek i ocena. Musimy uwzględnić dodatkowe atrybuty, które mogą być potrzebne w przyszłości, za pomocą pola „tagi”. Może ono przechowywać półstrukturalne dane 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 Cassandra jest bardzo podobne do PostgreSQL. Jedną z głównych różnic jest brak ograniczeń integralności (np. NOT NULL), ale to należy do obowiązków aplikacji, a nie NoSQL bazy danych.. Klucz główny składa się z klucza partycji (kolumna Artist w poniższym przykładzie) oraz zbioru kolumn klastrowania (kolumna SongTitle w poniższym przykładzie). Klucz partycji określa, do którego partycji/shardu należy umieścić wiersz, a kolumny klastrowania wskazują, jak powinny być uporządkowane dane w obrębie bieżącego shardu.
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 bazach danych (Database) (analogicznie do Keyspace w Cassandra), gdzie są kolekcje (Collections) (analogicznie do tabel), w których znajdują się dokumenty (Documents) (analogicznie do wierszy w tabeli). W MongoDB zasadniczo nie trzeba definiować początkowej schemy. Polecenie „use database”, pokazane poniżej, tworzy przykładową bazę danych przy pierwszym wywołaniu i zmienia kontekst na nowo utworzoną bazę danych. Nawet kolekcje nie muszą być tworzone jawnie, są tworzone automatycznie po dodaniu pierwszego dokumentu do nowej kolekcji. Należy pamiętać, że MongoDB domyślnie używa bazy danych testowej, więc każda operacja na poziomie kolekcji bez wskazania konkretnej bazy danych będzie wykonywana domyślnie w niej.
use myNewDatabase;Uzyskiwanie informacji o tabeli
PostgreSQL
Muzyka
Tabela "public.music"
Kolumna | Typ | Kolacja | Nullable | Domyślny
--------------+-----------------------+-----------+----------+--------
artysta | zmienna znakowa(20) | | nie null |
tytuł_piosenki| zmienna znakowa(30) | | nie null |
tytuł_albumu | zmienna znakowa(25) | | |
rok | liczba całkowita | | |
cena | podwójna precyzja | | |
gatunek | zmienna znakowa(10) | | |
ocena_krytyków| podwójna precyzja | | |
tagi | tekst | | |
Indeksy:
"music_pkey" KLUCZ PRYMARNY, btree (artysta, tytuł_piosenki)Cassandra
OPISZ TABELĘ MUZYKA;
UTWÓRZ TABELĘ myapp.music (
artysta tekst,
tytuł_piosenki tekst,
tytuł_albumu tekst,
rok int,
cena float,
gatunek tekst,
tagi tekst,
KLUCZ PRYMARNY (artysta, tytuł_piosenki)
) Z PORZĄDKIEM KLASU WALCOWEGO (tytuł_piosenki ASC)
I domyślny_czas_do_życia = 0
I transakcje = {'włączone': 'fałsz'};MongoDB
użyj myNewDatabase;
wyświetl zbiory;Wprowadzanie danych do tabeli
PostgreSQL
WSTAW DO Muzyka
(Artysta, TytułPiosenki, TytułAlbumu,
Rok, Cena, Gatunek, OcenaKrytyków,
Tagi)
WARTOŚCI(
'No One You Know', 'Call Me Today', 'Somewhat Famous',
2015, 2.14, 'Country', 7.8,
'{"Kompozytorzy": ["Smith", "Jones", "Davis"],"CzasWsekundach": 214}'
);
WSTAW DO Muzyka
(Artysta, TytułPiosenki, TytułAlbumu,
Cena, Gatunek, OcenaKrytyków)
WARTOŚCI(
'No One You Know', 'My Dog Spot', 'Hey Now',
1.98, 'Country', 8.4
);
WSTAW DO Muzyka
(Artysta, TytułPiosenki, TytułAlbumu,
Cena, Gatunek)
WARTOŚCI(
'The Acme Band', 'Look Out, World', 'The Buck Starts Here',
0.99, 'Rock'
);
WSTAW DO Muzyka
(Artysta, TytułPiosenki, TytułAlbumu,
Cena, Gatunek,
Tagi)
WARTOŚCI(
'The Acme Band', 'Still In Love', 'The Buck Starts Here',
2.47, 'Rock',
'{"stacjeRadioweGrające": ["KHCR", "KBQX", "WTNR", "WJJH"], "datyTrasy": { "Seattle": "20150625", "Cleveland": "20150630"}, "rotacja": Heavy}'
);Cassandra
Ogólnie rzecz biorąc, wyrażenie INSERT w Cassandra wygląda bardzo podobnie do odpowiedniego w PostgreSQL. Istnieje jednak jedna ważna różnica w semantyce. W Cassandra INSERT jest w rzeczywistości operacją UPSERT, gdzie do wiersza dodawane są najnowsze wartości, jeśli wiersz już istnieje.
Wprowadzanie danych przebiega podobnie jak w PostgreSQL.
INSERTpowyżej
.
MongoDB
Mimo że MongoDB jest bazą danych NoSQL, podobnie jak Cassandra, jej operacja wprowadzania danych nie ma nic wspólnego z semantycznym zachowaniem w Cassandra. W MongoDB nie ma możliwości UPSERT, co sprawia, że jest podobne do PostgreSQL. Dodanie danych domyślnie bez _idspecified doprowadzi do dodania nowego dokumentu do kolekcji.
db.music.insert( {
artysta: "No One You Know",
tytułUtworu: "Call Me Today",
tytułAlbumu: "Somewhat Famous",
rok: 2015,
cena: 2.14,
gatunek: "Country",
tagi: {
Kompozytorzy: ["Smith", "Jones", "Davis"],
DługośćWSekundach: 214
}
}
);
db.music.insert( {
artysta: "No One You Know",
tytułUtworu: "My Dog Spot",
tytułAlbumu: "Hey Now",
cena: 1.98,
gatunek: "Country",
ocenaKrytyka: 8.4
}
);
db.music.insert( {
artysta: "The Acme Band",
tytułUtworu: "Look Out, World",
tytułAlbumu:"The Buck Starts Here",
cena: 0.99,
gatunek: "Rock"
}
);
db.music.insert( {
artysta: "The Acme Band",
tytułUtworu: "Still In Love",
tytułAlbumu:"The Buck Starts Here",
cena: 2.47,
gatunek: "Rock",
tagi: {
stacjeRadioweGrające:["KHCR", "KBQX", "WTNR", "WJJH"],
datyTrasy: {
Seattle: "20150625",
Cleveland: "20150630"
},
rotacja: "Heavy"
}
}
);
Zapytanie do tabeli
Możliwe, że najbardziej znaczącą różnicą między SQL a NoSQL z punktu widzenia składni zapytań jest użycie sformułowań Z FROM i WHERE. SQL umożliwia po wyrażeniu Z FROM wybór wielu tabel, a wyrażenie z WHERE może być dowolnej złożoności (w tym operacje JOIN między tabelami). Jednak NoSQL ma tendencję do nałożenia ścisłego ograniczenia na Z FROM, i działa tylko z jedną określoną tabelą, a w WHERE, zawsze musi być podany klucz podstawowy. Jest to związane z dążeniem do zwiększenia wydajności NoSQL, o którym mówiliśmy wcześniej. To dążenie prowadzi do wszelkiego rodzaju ograniczeń w interakcji między tabelami i kluczami. Może to prowadzić do dużych opóźnień w komunikacji między węzłami podczas odpowiadania na zapytania, dlatego najlepiej unikać go w zasadzie. Na przykład, Cassandra wymaga, aby zapytania były ograniczone do określonych operatorów (dozwolone tylko =, IN, , =>, <=) na kluczach partycji, z wyjątkiem przypadków zapytania za pomocą indeksu wtórnego (gdzie dozwolony jest tylko operator =).
PostgreSQL
Następnie przedstawimy trzy przykłady zapytań, które z łatwością mogą być wykonane w bazie danych SQL.
- Wyświetl wszystkie utwory artysty;
- Wyświetl wszystkie utwory artysty, które pasują do pierwszej części tytułu;
- Wyświetl wszystkie utwory artysty, które zawierają określone słowo w tytule i mają cenę mniejszą 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
Spośród podanych powyżej zapytań, tylko pierwsze z nich będzie działać w Cassandrze bez zmian, ponieważ operator LIKE nie może być stosowany do kolumn klasteryzacji, takich jak SongTitle. W tym przypadku dozwolone są tylko operatory = i 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, podstawową metodą tworzenia zapytań w MongoDB jest . Ta metoda zawiera w sobie nazwę kolekcji (music w poniższym przykładzie), dlatego zapytanie na wielu kolekcjach jest zabronione.
db.music.find( {
artist: "No One You Know"
}
);
db.music.find( {
artist: "No One You Know",
songTitle: /Call/
}
);Odczyt wszystkich wierszy tabeli
Odczyt wszystkich wierszy to po prostu szczególny przypadek szablonu zapytania, który omawialiśmy wcześniej.
PostgreSQL
SELECT *
FROM Music;Cassandra
Podobnie jak w powyższym przykładzie w PostgreSQL.
MongoDB
db.music.find( {} );Edycja danych w tabeli
PostgreSQL
PostgreSQL zapewnia instrukcję UPDATE do edytowania danych. Nie ma ona możliwości UPSERT, dlatego wykonanie tej instrukcji zakończy się błędem, jeśli wiersze nie istnieją już w bazie danych.
UPDATE Music
SET Genre = 'Disco'
WHERE Artist = 'The Acme Band' AND SongTitle = 'Still In Love';Cassandra
W Cassandrze istnieje UPDATE analogiczny sposób działania jak w PostgreSQL. UPDATE ma tę samą semantykę UPSERT, podobnie jak INSERT.
Podobnie jak w powyższym przykładzie w PostgreSQL.
MongoDB
Operacja W MongoDB można całkowicie zaktualizować istniejący dokument lub zaktualizować tylko określone pola. Domyślnie aktualizuje tylko jeden dokument z wyłączoną semantyką. UPSERT. Aktualizacja wielu dokumentów i zachowanie są podobne. UPSERT można zastosować, ustawiając dodatkowe flagi dla operacji. Jak w poniższym przykładzie, aktualizowany jest gatunek konkretnego wykonawcy na podstawie jego piosenki.
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
Podobnie jak w powyższym przykładzie w PostgreSQL.
MongoDB
W MongoDB istnieją dwa typy operacji do usuwania dokumentów — i . Oba typy usuwają dokumenty, ale zwracają różne wyniki.
db.music.deleteMany( {
artist: "The Acme Band"
}
);
Usuwanie tabeli.
PostgreSQL
DROP TABLE Music;Cassandra
Podobnie jak w powyższym przykładzie w PostgreSQL.
MongoDB
db.music.drop();Podsumowanie
Spory o wybór między SQL a NoSQL trwają od ponad 10 lat. Istnieją dwa główne aspekty tej dyskusji: architektura rdzenia bazy danych (monolityczny, transakcyjny SQL w porównaniu do rozproszonego, nie-transakcyjnego NoSQL) oraz podejście do projektowania bazy danych (modelowanie danych w SQL w porównaniu do modelowania zapytań w NoSQL).
W przypadku rozproszonej bazy danych transakcyjnej, takiej jak YugaByte DB, debaty dotyczące architektury bazy danych mogą być łatwo rozwiane. W miarę jak objętości danych przekraczają możliwości zapisania w jednym węźle, całkowicie rozproszona architektura, która wspiera liniową skalowalność zapisu z automatycznym shardowaniem/i rebalance'owaniem, staje się niezbędna.
Oprócz tego, jak wspomniano w jednym z artykułów , transakcyjne, ściśle spójne architektury są teraz szerzej stosowane dla lepszej elastyczności w programowaniu niż nie-transakcyjne, w końcu spójne architektury.
Wracając do dyskusji na temat projektowania baz danych, należy stwierdzić, że oba podejścia (SQL i NoSQL) są niezbędne w przypadku każdego złożonego zastosowania w rzeczywistości. Podejście SQL „modelowanie danych” umożliwia deweloperom łatwiejsze dostosowywanie się do zmieniających się wymagań biznesowych, podczas gdy podejście NoSQL „modelowanie zapytań” pozwala tym samym deweloperom operować na dużych zbiorach danych z niewielkim opóźnieniem i wysoką przepustowością. To właśnie dlatego YugaByte DB oferuje SQL i NoSQL API w jednym rdzeniu, a nie promuje jednego z tych podejść. Dodatkowo, zapewniając zgodność z popularnymi językami baz danych, takimi jak PostgreSQL i Cassandra, YugaByte DB gwarantuje, że deweloperzy nie będą musieli uczyć się innego języka, aby pracować z rozproszonym, ściśle spójnym rdzeniem bazy danych.
W tym artykule zbadaliśmy, w jaki sposób zasady projektowania baz danych różnią się w PostgreSQL, Cassandrze i MongoDB. W następnych artykułach zgłębimy zaawansowane koncepcje projektowania, takie jak indeksy, transakcje, JOIN-y, dyrektywy TTL i dokumenty JSON.
Życzymy Wam wspaniałego pozostałego weekendu i zapraszamy na , które odbędzie się już 14 maja.
Źródło: habr.com
