Skip to content

Ściąga - NoSQL

1. Relacyjne kontra NoSQL, ACID kontra BASE

Section titled “1. Relacyjne kontra NoSQL, ACID kontra BASE”

Model relacyjny: tabele, związki 1:1, 1:N, M:N, klucze główne i obce, ograniczenia. Skaluje się głównie pionowo.

ACID (gwarancje transakcji w bazach relacyjnych):

  • Atomicity (niepodzielność): transakcja w całości albo wcale.
  • Consistency (spójność): po transakcji nie naruszone reguły integralności.
  • Isolation (izolacja): transakcje nie zakłócają się nawzajem w trakcie.
  • Durability (trwałość): zatwierdzone dane przetrwają awarię.

Poziomy izolacji: read uncommitted, read committed, repeatable read, serializable (od najsłabszego do najmocniejszego).

BASE (podejście baz NoSQL, kompromis wobec ACID):

  • Basically Available: dostępność nie zawsze pełna.
  • Soft state: stan może się zmieniać w czasie nawet bez nowych danych.
  • Eventually consistent: spójność osiągana ostatecznie, po pewnym czasie.

W NoSQL spójność jest często poświęcana na rzecz szybkości i dostępności.


W rozproszonym systemie da się spełnić tylko dwie z trzech cech:

  • Consistency: wszyscy widzą zawsze ten sam, najnowszy obraz danych.

  • Availability: każde żądanie dostaje odpowiedź (bez gwarancji aktualności).

  • Partition tolerance: system działa mimo problemów komunikacji między węzłami.

  • Bazy relacyjne (MySQL, PostgreSQL): bok CA, skalowanie pionowe.

  • Cassandra: AP, skalowanie poziome, wysoka dostępność, bez gwarancji najświeższych danych.

  • MongoDB: CP, rozproszenie z integralnością, ale przy awarii węzła część danych może być niedostępna.


3. Skalowanie: replikacja, sharding, map-reduce

Section titled “3. Skalowanie: replikacja, sharding, map-reduce”

Replikacja (kopie tych samych danych na wielu węzłach, cel: niezawodność i dostępność):

  • master-slave: zapis tylko przez master, on synchronizuje slave. Dobre przy przewadze odczytów.
  • peer-to-peer: zapis do dowolnego węzła, brak mastera, każdy synchronizuje z innymi. Większa dostępność, zgoda na niespójność.

Sharding (fragmentowanie): różne części danych na różnych serwerach (np. podział na kraje, dni, kategorie).

  • Zwiększa szybkość zapisu i odczytu.
  • Nie poprawia niezawodności ani bezpieczeństwa danych (to robi replikacja).

Map-Reduce: metodologia (i framework, np. Hadoop, Spark) przetwarzania dużych zbiorów danych w klastrach. Faza map (rozdział i wstępne przetwarzanie) i faza reduce (scalenie wyników). Idea zbliżona do master-slave.


Sześć podstawowych rodzajów:

  • Key-Value: Redis, Dynamo, Riak.
  • Document: MongoDB, CouchDB, Couchbase.
  • Column (wide column): Cassandra, HBase, Druid.
  • Graph: Neo4j, OrientDB.
  • Time Series: InfluxDB, Prometheus, TimescaleDB.

Modelowanie relacji w JSON:

  • Relacja 1:N w jednym pliku: zagnieżdżona tablica (array) wewnątrz dokumentu nadrzędnego.
  • Relacja modelowana w dwóch plikach: drugi plik z referencją do pierwszego (np. zamówienie trzyma klient_id).
  • Dokument odpowiada wierszowi w bazie relacyjnej, kolekcja odpowiada tabeli.

Agregacyjne modele danych: key-value, dokumentowe i rodzina kolumn są koncepcyjnie podobne i nazywa się je modelami zorientowanymi na agregacje. To odejście od modelu relacyjnego, w którym podstawową porcją danych jest wiersz i nie da się zagnieżdżać wierszy w wierszach. W modelach agregacyjnych jedna porcja danych (agregat) może mieć zagnieżdżoną, złożoną strukturę.

Normalizacja: w modelu relacyjnym dane rozbija się na wiele powiązanych tabel, by uniknąć redundancji (jeden klient zapisany raz, zamówienia osobno). W bazach agregacyjnych częściej idzie się w denormalizację, czyli powtórzenie danych wewnątrz jednego dokumentu dla szybkości odczytu.

Formaty danych: dane częściowo ustrukturyzowane zapisuje się w JSON, XML, YAML lub binarnie (BSON w MongoDB). JSON jest prostszy i krótszy od XML.


Podstawy administracji i bazy:

// lista baz
show dbs
// przełącz/utwórz bazę (powstaje po pierwszym zapisie)
use artur
// usuń bieżącą bazę
db.dropDatabase()
// lista kolekcji
show collections
// jawne utworzenie kolekcji
db.createCollection("zam")
// usuń kolekcję
db.zam.drop()

Wstawianie i odczyt:

db.zam.insert({ klient: "ABC", wartosc: 100, status: "A" })
// wszystkie dokumenty
db.zam.find()
// sformatowane
db.zam.find().pretty()
// pierwszy pasujący
db.zam.findOne({ klient: "ABC" })

Operatory porównania w find:

// równe
db.zam.find({ wartosc: 1000 })
// mniejsze
db.zam.find({ wartosc: { $lt: 1000 } })
// mniejsze lub równe
db.zam.find({ wartosc: { $lte: 1000 } })
// większe
db.zam.find({ wartosc: { $gt: 1000 } })
// większe lub równe
db.zam.find({ wartosc: { $gte: 1000 } })
// różne
db.zam.find({ wartosc: { $ne: 1000 } })

Projekcja (drugi argument find):

// tylko te pola (plus _id)
db.zam.find({}, { klient: 1, wartosc: 1 })
// ukryj _id
db.zam.find({}, { _id: 0 })

Kluczowe fakty:

  • Każdy dokument ma unikalne pole _id (z podkreślnikiem). Domyślnie generowany ObjectId, ale można podać własną wartość.
  • _id jest pokazywane domyślnie. Aby ukryć: { _id: 0 } w projekcji.
  • Aktualizacja: db.zam.update({_id:4}, {$set:{violations:3}}).

Replica Set (replikacja):

  • Zestaw serwerów: jeden Primary Node, reszta Secondary Nodes.
  • Po awarii Primary następuje automatyczny wybór nowego Primary spośród Secondary. Działanie jest możliwe bez ręcznej interwencji.
  • Gdy padną wszystkie Secondary, dawny Primary może przejść w stan Secondary i stracić możliwość zapisu.

MongoDB w CAP to CP (integralność ważniejsza niż pełna dostępność).


Klient: cqlsh. Każdy węzeł jest równouprawniony (architektura peer-to-peer, brak mastera).

Przestrzeń kluczy i tabele:

CREATE KEYSPACE firma
WITH REPLICATION = {'class':'SimpleStrategy', 'replication_factor':3};
USE firma;
DESC TABLES;
CREATE TABLE IF NOT EXISTS pracownik (
id int,
imie text,
nazwisko text,
data_ur date,
PRIMARY KEY (id)
);
INSERT INTO pracownik (id, imie, nazwisko, data_ur)
VALUES (1, 'Artur', 'Nowak', '1991-12-29');
SELECT * FROM pracownik;

Klucz główny (primary key):

  • Obowiązkowy partition key (przynajmniej jedna kolumna), opcjonalne clustering columns.
  • PRIMARY KEY (kraj, miasto): pierwsza kolumna to partition key (kraj), pozostałe to clustering key (miasto).
  • Composite partition key: PRIMARY KEY ((country, last_name), user_email) (podwójny nawias grupuje partition key).
  • Każda tabela musi mieć zdefiniowany klucz główny i należy do przestrzeni kluczy.

Replikacja i spójność:

  • RF (Replication Factor): liczba kopii danych. RF=1 to brak replikacji.
  • CL (Consistency Level): CL = floor(RF/2) + 1 dla quorum. Dla RF=3 quorum to 2, dla RF=15 to 8.
  • Poziomy: QUORUM, LOCAL_QUORUM, EACH_QUORUM i inne.

ALLOW FILTERING:

SELECT * FROM tab WHERE col1 = 1 ALLOW FILTERING;
  • Wymagane, gdy filtrujesz po kolumnie spoza klucza partycji. Bez tego zapytanie się nie wykona.
  • Powód: trzeba przejrzeć dane praktycznie we wszystkich węzłach (partycjach), co jest kosztowne.
  • Wniosek: projekt struktury tabeli musi uwzględniać przyszłe zapytania.
  • CQL nie obsługuje OR w WHERE.

Filtr Blooma:

  • Struktura danych, która szybko i pamięciowo oszczędnie wstępnie sprawdza, czy element należy do zbioru (np. czy email był już użyty).
  • Cassandra używa go do sprawdzenia, czy plik SSTable zawiera wiersz o danym kluczu, zanim sięgnie na wolny dysk.
  • Wada: dopuszcza fałszywe pozytywy (może powiedzieć, że element jest, choć go nie ma), ale nigdy fałszywych negatywów.

Baza typu klucz-wartość, działa w pamięci operacyjnej (RAM), z opcjonalnym zapisem na dysk dla bezpieczeństwa. Serwer redis-server, klient redis-cli.

Podstawowe polecenia:

# zapis (nadpisuje istniejącą wartość)
SET klucz "wartosc"
# odczyt (brak klucza zwraca (nil))
GET klucz
# usunięcie
DEL klucz
# czy klucz istnieje
EXISTS klucz
# zapis tylko gdy klucz nie istnieje
SETNX klucz wartosc
# zwiększ o 1 (na wartości liczbowej)
INCR licznik

Struktury danych: string, list (LPUSH, RPUSH, LPOP, RPOP), set (SADD, SPOP), sorted set (ZADD), hash (HSET).

Czas życia klucza (TTL):

SET "nasza:zmienna" "tekst"
# wygaśnie po 30 sekundach (klucz zostanie usunięty)
EXPIRE nasza:zmienna 30
# pozostały czas w sekundach
TTL nasza:zmienna
# pozostały czas w milisekundach
PTTL nasza:zmienna

Można też ustawić od razu: SET klucz wartosc EX 10 (10 sekund).

Transakcje:

# rozpoczęcie transakcji (kolejkowanie poleceń)
MULTI
SET a 1
INCR a
# wykonanie zakolejkowanych poleceń
EXEC
# wycofanie transakcji
DISCARD

Persystencja (persistence), dwa mechanizmy:

  • RDB (Redis Database): okresowe migawki (snapshot) bazy z RAM do pliku dump.rdb. Domyślnie włączony (np. save 900 1, save 300 10, save 60 10000).
  • AOF (Append Only File): zapis każdej operacji do logu (append-only), bezpieczniejszy. Domyślnie wyłączony.
  • Oba można włączać i wyłączać niezależnie.

HyperLogLog (PFADD, PFCOUNT):

  • Struktura do szybkiego liczenia liczby unikalnych elementów (kardynalność, count-distinct) przy małym zużyciu pamięci.
  • Wynik przybliżony, błąd standardowy poniżej 0.81 procenta. Dla 20000 unikalnych elementów wynik mieści się mniej więcej w przedziale plus minus 162.

Baza szeregów czasowych (time series). Wersja Core (OSS) i Enterprise.

Format danych: line protocol, składa się z:

  • table (measurement): napis identyfikujący grupę pomiarów, odpowiednik tabeli lub kolekcji.
  • tag set: pary klucz-wartość rozdzielone przecinkami, bez cudzysłowów (indeksowane metadane).
  • field set: pary klucz-wartość z właściwymi wartościami pomiaru. Typy: strings (w cudzysłowie), floats, integers, unsigned integers, booleans.
  • timestamp: znacznik czasu w formacie Unix timestamp, dokładność do 1 nanosekundy.

Unix timestamp: liczba sekund od 1 stycznia 1970 (RFC3339). 32-bitowy wariant wyczerpie się 19 stycznia 2038 (problem roku 2038).


Pułapki egzaminacyjne (szybkie odpowiedzi)

Section titled “Pułapki egzaminacyjne (szybkie odpowiedzi)”

MongoDB:

  • use baza przełącza na bazę i tworzy ją po pierwszym zapisie.
  • Listowanie baz: show dbs. Kolekcji: show collections.
  • Unikalny klucz dokumentu: pole _id. Można podać własną wartość.
  • Ukrycie identyfikatorów w wyniku: find({}, {_id:0}).
  • find({cena:{$ne:20}}) zwraca dokumenty z ceną różną od 20.
  • Dokument odpowiada wierszowi tabeli relacyjnej.
  • Po utracie Primary działanie jest możliwe (automatyczny wybór nowego Primary).
  • Błędny indeks: createIndex({klient:1, unique:true}) (opcja unique należy do drugiego argumentu).
  • MongoDB w CAP to CP.

Cassandra:

  • RF=15, quorum CL = floor(15/2)+1 = 8.
  • W PRIMARY KEY (kategoria, punkty) druga kolumna to clustering key.
  • Filtrowanie po kolumnie spoza klucza partycji wymaga ALLOW FILTERING.
  • ALLOW FILTERING jest kosztowne, bo trzeba przejrzeć wszystkie węzły.
  • Każdy węzeł jest równouprawniony (peer-to-peer).
  • CQL nie obsługuje OR w WHERE.
  • Każda tabela musi mieć klucz główny.
  • Migracja 4 tabel po 10, 20, 30, 40 rekordów do grafu (Neo4j): 100 węzłów i 4 etykiety.

Redis:

  • EXPIRE zmienna 30: klucz usuwany po 30 sekundach.
  • Klucz z czasem życia: SET zm wart EX 10.
  • Błędne polecenie: SET mykey (brak wartości).
  • GET na nieistniejącym kluczu zwraca (nil).
  • Rozpoczęcie transakcji: MULTI. Wykonanie: EXEC. Wycofanie: DISCARD.
  • Redis działa w RAM, z opcjonalnym zapisem na dysk (RDB lub AOF), konfiguracja w redis.conf.
  • RDB i AOF to mechanizmy persystencji Redis.
  • Wartości to napisy: SET key 2+4 zapisze tekst 2+4. SET key a po SET key 1 i INCR da 3.
  • SET klucz "moje dane" poprawne (spacja w cudzysłowie), SET klucz moje dane błędne.
  • HyperLogLog: błąd standardowy poniżej 0.81 procenta.

Ogólne:

  • Replikacja poprawia niezawodność przechowywania danych.
  • Sharding zwiększa szybkość, ale nie poprawia niezawodności i bezpieczeństwa.
  • Map-reduce to metodologia przetwarzania dużych zbiorów w środowisku rozproszonym.
  • Istota NoSQL: spójność bywa mniej istotna niż szybkość działania.
  • W modelu relacyjnym każda kolumna zawiera dane tego samego typu.
  • Relacja 1:N w jednym pliku JSON: zagnieżdżona tablica (array).

Baza grafowa: węzły (nodes) i relacje (krawędzie). Interfejs przeglądarkowy pod http://localhost:7474. Polecenia systemowe wydaje się z dwukropkiem (:help, :sysinfo, :play cypher).

Zapis w Cypherze:

  • (nawiasy okrągłe) to węzły.
  • [nawiasy kwadratowe] to relacje.
  • {nawiasy klamrowe} to właściwości.
  • Etykieta po dwukropku grupuje węzły, np. (p:Person).
  • Kierunek relacji oznaczają znaki mniejszości i większości.
// tworzenie węzłów
CREATE (friend:Person {name: 'Jennifer'});
CREATE (friend:Person {name: 'Mark'});
// tworzenie relacji między istniejącymi węzłami
MATCH (jennifer:Person {name: 'Jennifer'})
MATCH (mark:Person {name: 'Mark'})
CREATE (jennifer)-[rel:IS_FRIENDS_WITH]->(mark);
// MATCH to odpowiednik SQL-owego SELECT, szuka wzorca
// wszystkie osoby (zmienna p wymagana do RETURN)
MATCH (p:Person) RETURN p;
MATCH (p:Person {name: 'Tom Hanks'}) RETURN p;
// wszystkie dane
MATCH (n) RETURN n;
// odpowiednik SELECT * FROM Movie
MATCH (m:Movie) RETURN m;
// filtr WHERE i inne klauzule: WHERE, ORDER BY, SKIP, LIMIT, AND
MATCH (p:Person) WHERE p.unitPrice > 10 RETURN p;
// dodanie/zmiana właściwości
MATCH (p:Person {name: 'Jennifer'}) SET p.birthdate = date('1980-01-01');
// usunięcie właściwości
// lub REMOVE n.data_ur
MATCH (n {name: 'Piotr'}) SET n.data_ur = null;
// usunięcie relacji
MATCH (n {name: 'B'})-[r:XYZ]->() DELETE r;
// usunięcie węzła wraz z relacjami
MATCH (n) DETACH DELETE n;

Kierunki relacji: -[r]-> w prawo, <-[r]- w lewo, -[r]- bez kierunku. Nie da się postawić strzałek po obu stronach (<-[r]-> jest błędne).

Fakty: węzły mogą (nie muszą) mieć właściwości i etykiety. Import z CSV pozwala wczytać zarówno węzły, jak i relacje. Przy migracji z bazy relacyjnej liczba węzłów to suma rekordów, a liczba etykiet to liczba tabel (4 tabele po 10, 20, 30, 40 rekordów dają 100 węzłów i 4 etykiety).