Wprowadzenie do NoSQL, część I

Autor: Piotr Zieliński

Spis treści:

Wstęp

Relacyjne bazy danych takie jak SQL Server, MySql czy Oracle są bardzo rozpowszechnione i używane przez wielu programistów. Większość programistów jest świadomych, na czym polega normalizacja baz danych oraz jak poprawnie je projektować. Istnieje jednak druga szkoła, bardzo popularna w ostatnich latach, a mianowicie NoSQL. W sytuacji rozproszonych systemów, nierelacyjne bazy danych pełnią bardzo ważną funkcję. Wiele programistów, zwłaszcza na początku NoSQL, podchodziło bardzo niechętnie do tego, ale wraz z rozpowszechnieniem się BigData, NoSQL stało się codziennością dla bardzo wielu systemów. Bazy relacyjne oraz nierelacyjne można stosować razem w tym samym systemie. Zwykle wyszukiwanie jest łatwiejsze w RDBMS stąd czasami zachodzi potrzeba stosowania dwóch rozwiązań jednocześnie.

NoSQL, a RDBMS

Najlepiej zacząć od przykładu porównującego NoSQL z relacyjną bazą danych. NoSQL jest systemem baz danych, który nie posiada relacji oraz zwykle nie ma zdefiniowanego języka zapytań SQL. Dane w NoSQL nie mają z góry określonej postaci (schema), jak to ma miejsce w RDBMS.

Załóżmy, że piszemy system do przechowywania informacji o pracownikach firmy. Ponadto, każdy pracownik, może mieć skojarzonych kilka numerów kontaktowych. W podejściu relacyjnym, zwykle takie zadanie zrealizowałby się np. za pomocą dwóch tabel:

ID_EMPLOYEE (PK) FirstName LastName Salary DepartmentId (FK)
1 Piotr Zielinski 9999 1
2 Tekst Tekst 9999 2

Tabela 1: Tabela Employees

 

ID_PHONE (PK) ID_EMPLOYEE (FK) Number ID_TYPE (FK)
1 1 4242424 1
2 1 1234234 2
3 1 2525 3

Tabela 2: Tabela Phones

Pierwsza tabela (Employees) zawiera informacje o pracownikach takie jak imię, nazwisko, wynagrodzenie oraz klucz wskazujący na dział, w którym pracuje. Druga (PhoneNumbers) zawiera numery telefonu, jakie posiada pracownik.

Widać tutaj przynajmniej kilka relacji. Pierwsza z nich występuje między Employees a Phones. Zrealizowana jest za pomocą klucza obcego ID_EMPLOYEE, który wskazuje na konkretny wiersz w tabeli Employees. Analogicznie sytuacja wygląda z ID_DEPARTMENT oraz ID_TYPE

Powyższe podejście jest specyficzne dla RDBMS. Każda informacja musi być odpowiednio rozłożona między kolumny i tabele. Nie można przechowywać np. kilku informacji w tej samej kolumnie, ponieważ nie jest to zgodne z tzw. pierwszą postacią normalną.

Przyjrzyjmy się teraz, jak powyższe zadanie mogłoby być zrealizowane w NoSQL:

ID_EMPLOYEE: 1 FirstName: Piotr LastName: Zielinski Salary:99999 HomePhone:2425 WorkPhone:4242
ID_EMPLOYEE: 2 FirstName: Test LastName: Test Salary:99999 HomePhone:123  
ID_EMPLOYEE: 3 FirstName: Test1 LastName: Test2 Salary:104141    

Tabela 3: Przykładowa tabela w NoSQL

Powyższa tabela ma kilka bardzo ważnych cech:

  • Każdy wiersz może zawierać różne „kolumny”. W przeciwieństwie do baz relacyjnych, nie ma narzuconego formatu dla każdego wiersza w danej tabeli. Jak widać na przykładzie, jeden wiersz może zawierać dane o telefonie domowym a drugi o telefonie służbowym.
  • W NoSQL nie ma prawdziwych relacji, wymuszonych przez silnik bazodanowy. Oczywiście można tworzyć pola przechowujące „klucze” podstawowe i obce, ale to programista jest odpowiedzialny za ich utrzymanie i egzekwowanie. Bazy NoSQL są zatem tak naprawdę systemami nierelacyjnymi.

Patrząc na przykład z telefonami, łatwo domyślić się jednego ze scenariuszów, w których NoSQL ma przewagę nad RDBMS. Jeśli dana informacja może różnić się w zależności od wiersza, wtedy łatwiej efekt osiągnąć w NoSQL, ponieważ, ten typ bazy skaluje się również horyzontalnie. W RDBMS niedopuszczalne jest, aby dodać dodatkową kolumnę tylko po to, aby jeden z wierszy z niej korzystał. W NoSql nie ma tak naprawdę kolumn, a są to, w zależności od konkretnego typu, np. pary kluczy i wartości. Informacje takie jak adres (każdy kraj ma inny format adresu), dokumenty (różne struktury), spersonizowane encje są zwykle łatwiejsze w utrzymaniu, jeśli przechowuje je się w NoSQL.

Teoria CAP

W celu wyjaśnienia NoSQL, warto również przypomnieć sobie CAP – Constistency, Avaibility, Partition Tolerance. Teoria określa, że systemy bazodanowe mogą wyłącznie spełniać dwa warunki: CP (constistency, partition tolerance), AP (availability, partition tolerance) albo CA (constistency, availbility).


Rysunek 1: Właściwości baz danych według teorii CAP.

Windows Azure Storage został zaprojektowany, aby osiągnąć zarówno wysoką spójność jak i dostępność (wszystkie trzy elementy CAP, zamiast tylko dwóch). Więcej szczegółów znajduje się tutaj.

CAP nie zostało na początku bardzo dokładnie zdefiniowane, dlatego istnieje wiele różnych, sprzecznych ze sobą interpretacji. W 2012 roku na szczęście autor teorii (Eric Brewer) uściślił wspomniane warunki. Spójność (consistency) oznacza, że wszystkie węzły będą miały takie same wartości. Rozważmy system bazodanowy, składający się z dwóch węzłów:


Rysunek 2: Rozproszony system bazodanowy.

Jeśli system spełnia zasady spójności, wtedy zawsze, obaj użytkownicy otrzymają takie same dane. Oznacza to, że jakiekolwiek modyfikacje na bazie A, muszą zostać zreplikowane w bazie B.

Kolejna cecha to dostępność (availability). Według CAP, jeśli system cechuje się dostępnością (availability) to operacje zapisu i odczytu są możliwe zawsze, gdy tylko oczywiście poszczególne węzły są online. Innymi słowy, gdy istnieje przerwa w komunikacji między A oraz B, to użytkownicy wciąż będą mogli korzystać z baz.

Ostatnia cecha (partition tolerance) oznacza, że całość systemu powinna działać, mimo, że są przerwy w komunikacji między niektórymi węzłami.

Jeśli system ma być odporny na przerwy w połączeniu to naturalnie, istnieją wyłącznie dwa przeciwstawne podejścia, aby to osiągnąć:

  1. Można zrezygnować ze spójności (systemy AP). Wtedy (rysunek 3), klienci mogą odczytywać dane z węzłów A oraz B, niezależnie od siebie. Oczywiście, gdy użytkownik zapisze coś do bazy A, to nie zostanie to zsynchronizowane z bazą B. W praktyce możliwy będzie scenariusz, że użytkownicy odczytujący te same encje z baz A oraz B, uzyskają różne wyniki. Naturalnie spowodowane jest to brakiem połączenia i poświęceniem spójności na rzecz dostępności. Po przywróceniu połączenia dane zostaną ponownie zsynchronizowane - brak spójności nie będzie permanentny w większości sytuacjach (tzw. eventual constistency). Z jednej strony, system będzie w stanie obsłużyć więcej zapytań (dostępność), z drugiej jednak brak spójności w wielu scenariuszach jest nie do zaakceptowania – ale o tym później.

  2. Druga alternatywa to zachowanie spójności, ale zrezygnowanie z dostępności (CP). Gdy istnieje przerwa w połączeniu, należy wyłączyć cały klaster. Odnosząc się znów do rysunku 3, bazy C,D wciąż będą dostępne ale A i B z kolei będą musiały zostać wyłączone. System cechować będzie się niższa przepustowością, ale dane będą zawsze spójne – te same we wszystkich węzłach.


Rysunek 3: Dwa klastry. Jeden ma awarie w połączeniu, drugi z kolei jest w pełni online.

Systemy CP oraz AP zostały już wyjaśnione. Ostatnia kombinacja to CA, czyli spójność oraz dostępność danych. W przypadkach, gdy nie ma przerwy w połączeniu, wtedy oczywiście wszystkie dane są dostępne oraz spójne – nie występują wspomniane wcześniej problemy. Spójność jest łatwa do zagwarantowania, ponieważ możliwe jest nawiązanie komunikacji z każdą z baz. Analogicznie, dostępność nie jest niczym nadzwyczajnym w takim scenariuszu. Najprostsza forma CA to pojedyncza baza danych – pozostanie zawsze dostępna i spójna (partition tolerance nie ma w tym przypadku znaczenia).

Spójność danych

Powyższe rozważania wyraźnie wskazują, że aby osiągnąć wysoką dostępność danych (przepustowość) należy zrezygnować całkowicie lub przynajmniej w jakimś stopniu ze spójności. Różne bazy danych NoSQL, mają inne podejście, jeśli chodzi o spójność. Generalnie jednak, spójność jest poświęcana na rzecz wysokiej dostępności danych w przypadku NoSQL.

Ten fakt oznacza, że już na początku niektóre systemy nie będą dobrze pracowały w NoSQL. W przypadku np. kont bankowych nie do zaakceptowania jest, że informacje o stanie konta są różne w zależności od klastra. Podobnie w sklepach internetowych, stan na magazynie musi być zsynchronizowany, ponieważ w przeciwnym razie klient mógłby zamówić towar, który nie istnieje. Z drugiej jednak strony, istnieje ogromna liczba zastosowań, gdzie spójność nie jest krytycznym elementem. Oprogramowanie do przetwarzania np. danych pochodzących z czujników w systemach industrialnych nie musi zawsze polegać na zsynchronizowanych danych. Nic nie stanie się, gdy jeden klaster będzie zawierał np. jedną próbkę danych więcej niż drugi klaster. Różnica taka nie zepsuje stanu aplikacji ani nie przyniesie żadnych negatywnych efektów. Pomoże jednak zwiększyć to przepustowość systemu, co może mieć ogromne znaczenie, gdy próbek z czujników jest bardzo wiele.

Zapytania

W relacyjnych bazach danych, język SQL jest powszechnie wykorzystywany do tworzenia zapytań. Dzisiaj istnieje wiele narzędzi oraz IDE, które ułatwiają pracę z SQL. W przypadku NoSQL nie ma jednego, sformalizowanego języka zapytań. Zwykle programiści odpowiedzialni są za odczyt danych z baz. Każdy, konkretny silnik posiada inny mechanizm, w jaki można odczytać dane.

Z tego względu dużo trudniej jest tworzyć zaawansowane zapytania. W SQL jest codziennością tworzenie zagnieżdżonych zapytań, połączeń, agregacji i innych zaawansowanych konstrukcji, które przydają się np. w raportach.

NoSQL zdecydowanie nie został stworzony z myślą o szybkich zapytaniach. Indeksy można tworzyć tylko dla kluczy głównych, a nie jak w przypadku relacyjnych baz dla dowolnych kolumn. Spowodowane jest to głównie faktem, że każdy wiersz może mieć inny format. Z tego względu jest dość trudno napisać jedno ogólne zapytanie lub stworzyć klucz\indeks dla kolumny, które formalnie nie istnieją przecież (w zależności od typu baz, jest to np. para klucz-wartość).

Kiedy używać NoSQL?

Podstawowe różnice zostały już nakreślone. Bardzo poważnym błędem jest rozważanie NoSQL, jako zamiennika na relacyjne bazy danych. Z tego względu dochodzi do wielu niejasności i rozczarowań. Nierelacyjne bazy danych mają swoje specyficzne zastosowanie i należy przed ich wdrożeniem zdawać sobie sprawę z problemów, jakie mogą spowodować w przyszłości. Relacyjne bazy z kolei mają wciąż dostępnych dużo więcej narzędzi i z tego względu, łatwiej z nich korzystać. Innym problemem z NoSQL, jest fakt, że nie jest to aż tak popularna technologia i programiści również zwykle nie są zaznajomieni z konkretnymi implementacji. Relacyjne silniki takie jak SQL Server, Oracle czy MySQL są bardzo dobrze znane i każdy programista zna przynajmniej podstawowe zapytania SQL.

Aby odpowiedzieć na pytanie, kiedy używać NoSQL, warto również rozważyć, kiedy zdecydowanie lepszym wyborem jest RDBMS.

Jednym z problemów, wspomnianych już w poprzedniej sekcji są zapytania. Jeśli system będzie wymagał tworzenia szybkich i skomplikowanych zapytań, wtedy NoSQL może okazać się zbyt wolny. Wydajność zwykle jest niższa, jeśli należy przeszukiwać dane i wykonywać funkcje agregujące. Brak kolumn oraz jednolitego schematu baz powoduje, że Business Intelligence (BI) jest trudniejsze. Większość narzędzi BI oparta jest na kolumnach i SQL. Oczywiście są odpowiednie pluginy do NoSQL, ale zwykle jest to trudniejsze niż w przypadku SQL.

Bazy danych NoSQL nie spełniają ACID (atomicity, consistency, isolation, durability) i co najwyżej mogą osiągnąć kompromis określany BASE:

  • Basically available – bazy cechują się dość dużą dostępnością (teoria CAP).
  • Eventual consistency – jak zostało wcześniej wyjaśnione, NoSQL nie gwarantuje spójności danych. Jeśli nastąpi przerwa w systemie, może zdarzyć się, że dwa węzły mają inne wartości. Eventual consistency mówi, że po pewnym czasie węzły zostaną zsynchronizowane (po tym jak nastąpi ponowne połączenie). Oczywiście nie jest to pełna spójność, bo mogą wystąpić konflikty, jeśli użytkownik w międzyczasie zmodyfikował dane. W zależności od systemu, w różny sposób rozwiązuje się je – najprostsza metoda to nadpisanie najnowszą wartością.
  • Soft state – jest tak naprawdę pochodną „eventual consistency”, która gwarantuje, że po pewnym czasie dane zostaną zsynchronizowane. Naturalnie, że poskutkuje to zmianą stanu bazy, bez tak naprawdę ingerencji użytkownika – to implementacja silnika decyduje jak ewentualny konflikt zostanie rozwiązany. Czasami użytkownik ma na to wpływ, ale jest to już decyzja konkretnej implementacji bazy.

W praktyce oznacza to, że systemy transakcyjne, łatwiej zaimplementować w tradycyjnych bazach. Transakcja jest wymogiem wielu aplikacji biznesowych, co pokazuje, że NoSQL rzeczywiście nie ma aż tak szerokiego zastosowania, jak to czasami jest mówione. Wiele procesów biznesowych naturalnie wymaga pojęcia transakcji i izolacji od aktualnego stanu bazy.

Kiedy więcej używać NoSQL? W sytuacjach, gdy jest potrzebny wysoce skalowalny (horyzontalnie) silnik baz danych. NoSQL jest bardzo często łączony z innym pojęcie, Big Data. W systemach o ogromnej ilości danych jest potrzebna skalowalność. Zwykle relacyjne bazy skaluje się wertykalnie, czyli poprzez zwiększenie pamięci RAM czy CPU dla danego węzła. NoSQL dużo łatwiej skalować horyzontalnie, co oznacza, że doczepia się kolejny węzeł do klastra. Dlaczego NoSQL jest łatwiejszy w skalowaniu? Głównie dzięki faktowi, że poświecono spójność danych na rzecz dostępności. Łatwiej również utrzymać dane w rozproszonym środowisku, jeśli nie mają one z góry zdefiniowanych relacji. Brak transakcji oraz nie spełnianie zasad ACID, również znaczącą ułatwia rozproszenie danych. Innymi słowy, bazy NoSQL rezygnują z części funkcjonalności (na rzecz modelu BASE), co w sytuacjach, gdy nie są one niezbędne, umożliwia dużo wyższą skalowalność całego systemu. W Big Data, brak spójności nie jest problemem, ponieważ zwykle dane się tylko odczytuje, dodaje a nie modyfikuje już istniejące.

Gdy w bazie występują proste encje, wtedy również jest to argument, aby skorzystać z NoSQL. W sytuacji, gdy baza zawiera setki różnych tabel ze skomplikowanymi strukturami (poszczególne encje połączone są z wieloma innymi), prawdopodobnie, wymuszenie relacji będzie koniecznie, aby nie powstał bałagan w bazie. Istnieje jednak wiele przypadków, gdzie bazy mają proste encje, ale za to bardzo wiele muszą przechowywać informacji. Należy jednak pamiętać, że bazy NoSQL zwykle nie oferują tak silnego modelu jak SQL i trzeba liczyć się z przerwaniem sesji oraz utratą zapisu danych. Ze względu na to, że NoSQL nie posiada silnego schematu, pewne typy danych po prostu łatwiej zaimplementować w NoSQL. Jeśli jakaś encja ma różny zestaw właściwości, wtedy jest to nienaturalne dla relacyjnych baz, w których wymaga się jednolitego schematu dla wszystkich instancji.

Wszelkie gry online, które przetwarzają wiele żądań na sekundę, są dobrym scenariuszem na użycie NoSQL. W takich systemach, wydajność, niskie opóźnienia oraz maksymalna przepustowość są zdecydowanie ważniejsze niż spójność. Wiele elementów, np. karta gracza, również łatwiej zamodelować za pomocą struktury niż konkretnej tabeli w bazie danych, która wymaga z góry zdefiniowanych kolumn.

Jak widać, relacyjne i nierelacyjne bazy mają swoje silne i słabe strony. Z tego względu, w praktyce można połączyć oba rozwiązania, aby osiągnąć wydajną architekturę. Jedną z zalet jak i wad NoSQL jest luźny schemat bazy. Z tego względu dość ciężko przeszukiwać informacje ponieważ nie wiadomo w jakim są one formacie. Alternatywą jest przechowywanie metadanych lub indeksów w RDBMS tak, aby dostęp do NoSQL był jak najszybszy.

Podsumowanie

Pierwsza cześć artykułu stanowi ogólne wprowadzenie do tematyki. W drugiej części zostaną wytłumaczone różne typy baz NoSQL wraz z przykładowymi implementacjami. W artykule starałem się podkreślić, że NoSQL ma swoje specyficzne zastosowanie i zwykle potrzeby biznesowe wyraźnie wskazują, z których baz lepiej skorzystać. Pomimo, że rynek NoSQL nie jest tak dojrzały jak SQL, istnieje już wiele implementacji, które zostaną pokrótce pokazane w następnej części artykułu. Powstanie również kilka innych artykułów w tym cyklu, które będą wyjaśniały Big Data, programowanie rozproszone oraz różne biblioteki m.in. Hadoop.