Fiszki Online NoSQL (Preview)
Darmowy podgląd 15 z 44 dostępnych pytań
Wprowadzenie do NoSQL
Czym jest NoSQL i jakie cechy łączą bazy określane tym mianem?
NoSQL to nie jedna technologia ani jeden produkt, lecz luźna rodzina baz danych, które odeszły od dominującego przez dekady modelu relacyjnego. Sam termin powstał przypadkowo (jako hashtag na spotkanie społeczności) i nigdy nie doczekał się ścisłej definicji — opisuje raczej pewien ruch i zestaw wspólnych cech niż precyzyjną kategorię.
Bazy zaliczane do NoSQL zwykle mają kilka powtarzających się właściwości:
- Nie używają modelu relacyjnego ani (w większości) języka SQL,
- Dobrze działają na klastrach złożonych z wielu maszyn,
- Najczęściej są open-source,
- Powstały na potrzeby aplikacji webowych początku XXI wieku,
- Są schemaless — nie wymagają zdefiniowania schematu z góry.
Warto rozumieć NoSQL jako poszerzenie palety opcji przechowywania danych, a nie zastąpienie baz relacyjnych. Te ostatnie wciąż pozostają rozsądnym domyślnym wyborem dla wielu projektów.
↑ Powrót na góręNa czym polega problem niedopasowania impedancji (impedance mismatch)?
Niedopasowanie impedancji to rozbieżność między relacyjnym modelem danych a strukturami danych w pamięci aplikacji. Model relacyjny organizuje dane w tabele i wiersze, gdzie wartości w wierszu muszą być proste — nie można zagnieździć w nich rekordu ani listy. Tymczasem struktury w pamięci programu bywają znacznie bogatsze: zawierają zagnieżdżone obiekty, listy i kolekcje.
Efekt jest taki, że aby zapisać złożoną strukturę z pamięci do bazy relacyjnej, trzeba ją „rozłożyć" na wiele wierszy w wielu tabelach, a przy odczycie z powrotem złożyć. To źródło ciągłej frustracji programistów.
Problem łagodzą frameworki mapowania obiektowo-relacyjnego (ORM), takie jak Hibernate, ale go nie usuwają — ORM bywa nieszczelną abstrakcją i potrafi sam stać się źródłem kłopotów, zwłaszcza gdy zaczyna cierpieć wydajność zapytań. Bazy zorientowane na agregaty atakują ten problem u źródła, pozwalając zapisać złożoną strukturę w jednym kawałku, tak jak istnieje ona w pamięci.
↑ Powrót na góręModele danych zorientowane na agregaty
Czym jest agregat w kontekście baz NoSQL?
Agregat to zbiór powiązanych danych, które traktujemy jako jedną całość — jednostkę odczytu, zapisu i zarządzania spójnością. Pojęcie pochodzi z Domain-Driven Design, gdzie agregat to grupa obiektów, którą chcemy traktować razem.
Przykładowo zamówienie wraz z jego pozycjami, adresem dostawy i danymi płatności może tworzyć jeden agregat. Zamiast rozbijać te dane na osobne tabele (jak w modelu relacyjnym), przechowuje się je razem jako złożoną strukturę, zwykle identyfikowaną przez klucz.
Agregaty mają dwie ważne konsekwencje:
- Stanowią naturalną jednostkę dystrybucji — dane jednego agregatu trafiają na ten sam węzeł klastra, co ułatwia replikację i sharding,
- Stanowią jednostkę atomowości — operacje na pojedynczym agregacie są atomowe, ale operacje obejmujące wiele agregatów już nie.
Bazy klucz-wartość, dokumentowe i kolumnowe są zorientowane na agregaty. Bazy relacyjne i grafowe nazywamy „agregatowo-nieświadomymi" (aggregate-ignorant).
flowchart LR
subgraph "Model relacyjny (znormalizowany)"
K[("Klienci")]
Z[("Zamówienia")]
P[("Pozycje")]
A[("Adresy")]
K --> Z
Z --> P
Z --> A
end
subgraph "Model agregatowy"
AGG["Zamówienie #99<br/>+ pozycje<br/>+ adres dostawy<br/>+ płatność"]
end
↑ Powrót na górę
Jakie są główne kategorie baz NoSQL?
Wyróżnia się cztery podstawowe kategorie modeli danych w świecie NoSQL:
- Klucz-wartość (key-value) — najprostszy model, działający jak tablica mieszająca. Wartość jest dla bazy nieprzejrzystym blobem; dostęp wyłącznie po kluczu.
- Dokumentowe (document) — przechowują dokumenty (np. JSON, XML, BSON), których struktura jest dla bazy widoczna, co pozwala odpytywać po polach wewnątrz dokumentu i pobierać jego fragmenty.
- Kolumnowe / rodzin kolumn (column-family) — dane to wiersze z kluczem, w których kolumny grupuje się w rodziny kolumn (column families) wczytywane razem. Można je traktować jako dwupoziomową mapę.
- Grafowe (graph) — przechowują węzły i krawędzie z właściwościami; specjalizują się w danych o złożonych relacjach.
Pierwsze trzy są zorientowane na agregaty i powstały głównie z myślą o klastrach. Bazy grafowe są odmiennym przypadkiem — modelują małe rekordy z bogatymi połączeniami i zwykle działają na pojedynczym serwerze.
↑ Powrót na góręRelacje, grafy i brak schematu
Jak bazy zorientowane na agregaty radzą sobie z relacjami między agregatami?
Agregaty świetnie grupują dane czytane razem, ale dane bywają powiązane na różne sposoby. Klient i jego zamówienia mogą czasem być czytane razem (wtedy pasują do jednego agregatu), a czasem zamówienia przetwarza się niezależnie (wtedy lepiej, by były osobnymi agregatami z jakąś relacją między nimi).
Najprostszy sposób połączenia to osadzenie identyfikatora jednego agregatu w drugim — np. ID klienta w zamówieniu. Działa to dobrze, ale baza pozostaje nieświadoma tej relacji. Dlatego wiele baz (nawet klucz-wartość) udostępnia mechanizmy ujawniające relacje: bazy dokumentowe mogą indeksować zawartość, a niektóre bazy klucz-wartość pozwalają zapisać informacje o powiązaniach w metadanych i „chodzić" po linkach.
Kluczowy problem dotyczy aktualizacji: skoro atomowość obejmuje tylko jeden agregat, operacje na wielu powiązanych agregatach stają się niewygodne i grożą niespójnością po częściowej awarii. Dlatego dane bogate w relacje bywają sygnałem, że lepiej sprawdzi się baza relacyjna lub grafowa — choć trzeba pamiętać, że bazy relacyjne też słabo radzą sobie z bardzo złożonymi relacjami, bo wielokrotne joiny szybko stają się trudne i wolne.
↑ Powrót na góręModele dystrybucji danych
Dlaczego model jednego serwera (single server) bywa najlepszym wyborem?
Pojedynczy serwer to model dystrybucji polegający na… braku dystrybucji — cała baza działa na jednej maszynie obsługującej wszystkie odczyty i zapisy. Choć NoSQL kojarzy się z klastrami, bardzo często jest to opcja godna polecenia jako pierwsza.
Powód jest prosty: eliminuje całą złożoność, którą wprowadza rozpraszanie danych. Łatwiej go obsługiwać administratorom i łatwiej o nim rozumować programistom. Rozproszenie danych daje korzyści (większe wolumeny, większy ruch, wyższa dostępność), ale zawsze kosztem złożoności — więc nie warto go wprowadzać, jeśli korzyści nie są naprawdę przekonujące.
Model jednoserwerowy ma sens szczególnie wtedy, gdy model danych bazy NoSQL lepiej pasuje do aplikacji niż relacyjny. Oczywistym przykładem są bazy grafowe, które najlepiej działają właśnie na jednym serwerze. Jeśli głównie przetwarzamy agregaty, jednoserwerowa baza dokumentowa lub klucz-wartość może być korzystna po prostu dlatego, że ułatwia życie programistom.
↑ Powrót na góręNa czym polega replikacja master-slave i kiedy jest przydatna?
W replikacji master-slave dane kopiowane są na wiele węzłów. Jeden węzeł jest masterem (głównym) — to autorytatywne źródło danych, odpowiedzialne za przetwarzanie zapisów. Pozostałe to slave'y (podrzędne), które synchronizują się z masterem. Odczyty mogą iść zarówno do mastera, jak i do slave'ów.
Model ten jest najbardziej przydatny przy obciążeniu zdominowanym przez odczyty: dodając kolejne slave'y i kierując do nich odczyty, skalujemy się poziomo. Nie pomaga jednak przy intensywnych zapisach — master pozostaje wąskim gardłem.
Druga korzyść to odporność odczytów: gdy master padnie, slave'y nadal obsługują odczyty, a któryś z nich można szybko awansować na nowego mastera (ręcznie lub automatycznie przez wybór w klastrze). Dzięki temu master-slave bywa przydatny nawet bez potrzeby skalowania — jako konfiguracja jednego serwera z „gorącą" kopią zapasową.
Ciemna strona to niespójność: różni klienci czytający z różnych slave'ów mogą zobaczyć różne wartości, zanim zmiany dotrą wszędzie. W skrajnym przypadku klient nie odczyta nawet zapisu, który właśnie wykonał.
flowchart TD
C["Klient<br/>(zapisy)"] -->|write| M["Master"]
M -->|replikacja| S1["Slave 1"]
M -->|replikacja| S2["Slave 2"]
M -.->|read| R0["odczyt"]
S1 -.->|read| R1["odczyt"]
S2 -.->|read| R2["odczyt"]
↑ Powrót na górę
Spójność i twierdzenie CAP
Czym jest konflikt zapis-zapis i jak można go rozwiązać?
Konflikt zapis-zapis (write-write) powstaje, gdy dwie osoby aktualizują ten sam element danych w tym samym czasie. Bez kontroli współbieżności jedna aktualizacja może natychmiast nadpisać drugą, co daje utraconą aktualizację (lost update) — utrata spójności, bo druga zmiana opierała się na stanie sprzed pierwszej, a została zastosowana po niej.
Podejścia do utrzymania spójności dzielą się na dwa rodzaje:
- Pesymistyczne — zapobiegają konfliktom, najczęściej przez blokady zapisu (write locks): by zmienić wartość, trzeba najpierw zdobyć blokadę, a system dopuszcza tylko jednego klienta naraz.
- Optymistyczne — pozwalają konfliktom zaistnieć, ale je wykrywają. Typowy mechanizm to aktualizacja warunkowa (conditional update) — przed zapisem sprawdzamy, czy wartość nie zmieniła się od ostatniego odczytu.
Trzecia opcja to zapisanie obu wersji i odnotowanie konfliktu, a potem scalenie (jak w systemach kontroli wersji) — automatyczne lub z udziałem użytkownika.
Choć instynkt podpowiada podejście pesymistyczne, ma ono cenę: blokady poważnie obniżają responsywność i grożą zakleszczeniami (deadlocks). To fundamentalny kompromis między bezpieczeństwem a żywotnością (liveness).
↑ Powrót na góręNa czym polega twierdzenie CAP i jak należy je rozumieć?
Twierdzenie CAP mówi, że spośród trzech właściwości — Consistency (spójność), Availability (dostępność) i Partition tolerance (odporność na podział sieci) — można mieć tylko dwie. W kontekście CAP „dostępność" oznacza, że każdy działający węzeł, z którym da się porozumieć, potrafi odczytać i zapisać dane, a „podział" to rozerwanie komunikacji w klastrze na grupy, które się nawzajem nie widzą (split brain).
Popularne ujęcie „dwa z trzech" jest jednak mylące. Pojedynczy serwer jest systemem CA (spójny i dostępny, bez podziałów), ale system rozproszony i tak musi tolerować podziały sieci — one po prostu się zdarzają. Realny sens twierdzenia jest więc taki: gdy nastąpi podział sieci, trzeba wybrać między spójnością a dostępnością.
I nie jest to wybór zero-jedynkowy — można poświęcić odrobinę spójności dla odrobiny dostępności, dobierając kompromis do konkretnych potrzeb. Co więcej, często lepiej myśleć nie o parze spójność/dostępność, lecz o spójności kontra opóźnienie (latency): więcej węzłów zaangażowanych w operację daje lepszą spójność, ale wydłuża czas odpowiedzi. Dostępność można wtedy rozumieć jako granicę opóźnienia, którą jesteśmy w stanie zaakceptować.
(Wspomina się też o akronimie BASE — Basically Available, Soft state, Eventual consistency — jako przeciwwadze dla ACID, ale jest on dość naciągany, a sam jego twórca widział ACID i BASE jako spektrum, nie wybór binarny.)
flowchart TD
P{"Czy wystąpił podział sieci<br/>(partycja)?"}
P -->|Nie| OK["System może być<br/>spójny i dostępny"]
P -->|Tak| W{"Co wybierasz?"}
W -->|"Spójność (CP)"| CP["Odrzuć część żądań,<br/>utrzymaj jeden stan"]
W -->|"Dostępność (AP)"| AP["Odpowiadaj zawsze,<br/>godź się na rozbieżności"]
↑ Powrót na górę
Map-Reduce
Czym jest wzorzec map-reduce i jaki problem rozwiązuje?
Map-reduce to wzorzec organizacji obliczeń tak, by wykorzystać wiele maszyn w klastrze, jednocześnie minimalizując ilość danych przesyłanych przez sieć. Pomysł: przetwarzaj dane jak najbliżej miejsca, gdzie są przechowywane.
Problem bierze się stąd, że orientacja na agregaty utrudnia obliczenia nie pasujące do struktury agregatu. Jeśli agregatem jest zamówienie, a chcemy raport przychodu wg produktu za ostatni tydzień, musimy odwiedzić wiele agregatów rozsianych po klastrze. To typowa sytuacja dla map-reduce.
Obliczenie ma dwa etapy:
- Map — funkcja, która jako wejście dostaje pojedynczy agregat i zwraca zbiór par klucz-wartość. Każde wywołanie jest niezależne od pozostałych, więc operacje map można bezpiecznie zrównoleglić i uruchomić na węzłach, gdzie leżą dane.
- Reduce — funkcja, która bierze wiele wyjść map o tym samym kluczu i łączy ich wartości w jeden wynik (np. sumuje ilości i przychód dla danego produktu).
Programista pisze tylko te dwie funkcje, a framework zajmuje się uruchomieniem zadań na właściwych węzłach i zebraniem danych dla reduce. Nazwa pochodzi od operacji map i reduce z języków funkcyjnych.
flowchart LR
A1["Agregat 1"] --> M1["map"]
A2["Agregat 2"] --> M2["map"]
A3["Agregat 3"] --> M3["map"]
M1 --> SH["partycjonowanie<br/>i tasowanie wg klucza"]
M2 --> SH
M3 --> SH
SH --> R1["reduce<br/>(klucz X)"]
SH --> R2["reduce<br/>(klucz Y)"]
R1 --> OUT["Wynik"]
R2 --> OUT
↑ Powrót na górę
Bazy klucz-wartość
Czym jest baza klucz-wartość i jakie ma kluczowe cechy?
Baza klucz-wartość (key-value store) to najprostsza w użyciu baza NoSQL — w istocie rozproszona tablica mieszająca. Klient może tylko: pobrać wartość dla klucza (get), zapisać wartość pod kluczem (put) lub usunąć klucz (delete). Wartość jest blobem, którego baza nie interpretuje — to aplikacja odpowiada za zrozumienie, co zostało zapisane.
Najważniejsze cechy:
- Spójność dotyczy tylko operacji na pojedynczym kluczu; w wersjach rozproszonych stosuje się model eventual consistency, a konflikty rozwiązuje się np. „ostatni zapis wygrywa" albo zwracając wszystkie wersje do rozstrzygnięcia przez klienta.
- Zapytania możliwe są wyłącznie po kluczu — nie da się odpytać po zawartości wartości (chyba że dorzuci się zewnętrzny silnik wyszukiwania).
- Skalowanie odbywa się zwykle przez sharding wg wartości klucza.
- Dzięki dostępowi wyłącznie po kluczu głównym mają świetną wydajność i łatwo się skalują.
Ponieważ projekt klucza jest kluczowy, dużo uwagi poświęca się temu, jak go generować (np. z identyfikatora użytkownika, e-maila, znacznika czasu). Niektóre bazy (jak Redis) pozwalają przechowywać bogatsze struktury — listy, zbiory, hasze.
↑ Powrót na góręBazy dokumentowe
Czym jest baza dokumentowa i czym różni się od bazy klucz-wartość?
Baza dokumentowa (document database) przechowuje i pobiera dokumenty — samoopisujące się, hierarchiczne struktury drzewiaste (np. JSON, BSON, XML), które mogą zawierać mapy, kolekcje i wartości skalarne. Dokumenty w jednej kolekcji są do siebie podobne, ale nie muszą mieć identycznej struktury — jeden może mieć pola, których inny nie ma, i to jest dozwolone.
Najlepiej myśleć o bazie dokumentowej jako o bazie klucz-wartość, w której wartość jest przejrzysta — baza widzi jej wnętrze. Stąd kluczowa różnica: możemy odpytywać dane wewnątrz dokumentu bez pobierania całości i introspekcji po stronie klienta. To zbliża te bazy do modelu zapytań znanego z baz relacyjnych.
W odróżnieniu od bazy relacyjnej nie ma tu pustych atrybutów — jeśli pola brak, zakładamy, że nie zostało ustawione lub jest nieistotne. Nowe atrybuty można dodawać bez definiowania ich z góry i bez zmiany istniejących dokumentów. Dane podrzędne (np. listę adresów) osadza się zwykle jako podobiekty wewnątrz dokumentu głównego, co daje łatwy dostęp i lepszą wydajność.
↑ Powrót na góręBazy typu kolumnowego (column-family)
Jak zorganizowane są dane w bazie typu column-family?
W bazie column-family dane to wiersze identyfikowane kluczem, w których kolumny grupuje się w rodziny kolumn (column families) — grupy danych zwykle czytanych razem (np. profil klienta osobno, historia zamówień osobno). Najlepiej myśleć o tym jako o dwupoziomowej mapie albo dwupoziomowym agregacie: pierwszy klucz wskazuje wiersz, a wiersz jest mapą bardziej szczegółowych wartości — kolumn.
Podstawowe pojęcia (na przykładzie typowej bazy z tej rodziny):
- Kolumna — para nazwa-wartość, przechowywana zawsze ze znacznikiem czasu (timestamp służy do wygasania danych, rozstrzygania konfliktów zapisu i radzenia sobie z nieświeżymi danymi).
- Wiersz — kolekcja kolumn powiązanych z kluczem; różne wiersze nie muszą mieć tych samych kolumn, a kolumny można dodawać do dowolnego wiersza w każdej chwili.
- Super column / super column family — kolumna, której wartością jest mapa kolumn (kontener kolumn).
Rozróżnia się wiersze „wąskie" (skinny — niewiele kolumn, te same w wielu wierszach, model rekordowy) i „szerokie" (wide — wiele różnych kolumn, modelujące listę, gdzie każda kolumna to element). Definiowanie nowych rodzin kolumn jest rzadkie i bywa kosztowne, podczas gdy dodawanie kolumn to codzienność.
↑ Powrót na góręBazy grafowe
Czym jest baza grafowa i dlaczego jest szybka w przechodzeniu relacji?
Baza grafowa (graph database) przechowuje węzły (nodes) i krawędzie (edges/relationships) między nimi. Węzły to encje z właściwościami (np. węzeł z właściwością name), a krawędzie mają typ, kierunek i również mogą mieć właściwości. Relacje są tu obywatelami pierwszej klasy — to z nich pochodzi większość wartości tego modelu.
Przeszukiwanie grafu nazywa się przechodzeniem (traversal) — pytamy np. „znajdź wszystkie węzły zatrudnione w firmie X, które lubią produkt Y". Ważna zaleta: wymagania przechodzenia można zmieniać bez modyfikacji węzłów i krawędzi.
Bazy grafowe są bardzo szybkie w nawigowaniu po relacjach, bo relacja nie jest obliczana w czasie zapytania, lecz utrwalona jako połączenie. Innymi słowy, większość pracy nad nawigacją przenosi się z czasu zapytania na czas zapisu. Bazy relacyjne realizują relacje przez klucze obce, ale joiny potrzebne do nawigacji bywają kosztowne, więc wydajność dla silnie połączonych danych jest słaba. Zwykle zaczyna się od wyszukania węzła startowego po indeksowanym atrybucie, a dalej idzie się po krawędziach.
↑ Powrót na góręPolyglot persistence i wybór bazy
Czym jest polyglot persistence?
Polyglot persistence to podejście, w którym do różnych potrzeb przechowywania danych używa się różnych technologii baz danych, zamiast wciskać wszystko do jednego silnika. Wynika z prostej obserwacji: różne bazy projektowano do rozwiązywania różnych problemów, a używanie jednej do wszystkiego zwykle prowadzi do nieoptymalnych rozwiązań.
Pojęcie jest analogiczne do „polyglot programming" — idei, że aplikację warto pisać w mieszance języków, bo różne języki nadają się do różnych zadań. Tak samo różne dane mają różne potrzeby.
Przykład z e-commerce: w jednej aplikacji można użyć:
- bazy klucz-wartość na dane sesji i koszyka (dostęp po identyfikatorze, dane przejściowe),
- bazy grafowej do rekomendacji („twoi znajomi też kupili"),
- bazy relacyjnej na potwierdzone, opłacone zamówienia.
Dane sesji, koszyka i zamówień mają przecież różne wymagania dostępności, spójności i kopii zapasowych — nie ma powodu, by traktować je tak samo. Polyglot persistence może obejmować całe przedsiębiorstwo albo pojedynczą aplikację, a nawet używanie wyspecjalizowanych baz relacyjnych do różnych celów.
flowchart TD
APP["Aplikacja e-commerce"]
APP -->|"sesja, koszyk"| KV["Baza klucz-wartość"]
APP -->|rekomendacje| G["Baza grafowa"]
APP -->|"zamówienia, płatności"| RDB[("Baza relacyjna")]
APP -->|"katalog, treści"| DOC["Baza dokumentowa"]
↑ Powrót na górę