Sharding w bazach danych

Co to jest Sharding?

Sharding jest wzorcem architektonicznym bazy danych związanym z partycjonowaniem horyzontalnym. Praktyka ta polega na rozdystrybuowaniu wierszy w tabeli na różne tabele zwane partycjami. Każda partycja ma ten sam schemat, te same kolumny, ale kompletnie różne wiersze. Dane znajdujące się w jednej partycji są unikalne i niezależne od danych z pozostałych partycji. Każda partycja nazywana jest Shardem, co w tłumaczeniu na polski oznacza odłamek. Poniższy opisuje sposób zarządzania takimi odłamkami bazy danych.

Sharding jest dobrym przykładem implementacji Shared Nothing Architecture.

Różnice między skalowaniem wertykalnym a horyzontalnym

W powyższym akapicie wspominałem o partycjonowaniu, a teraz piszę o skalowaniu. To nie jest to samo. O partycjonowaniu napiszę niżej. Pojęcia horyzontalny i wertykalny bardzo dobrze opisują to, co zamierzamy wdrożyć. Poniższe definicje na pewno pomogą w zrozumieniu zagadnienia.

Skalowanie wertykalne (scale-up) to podejście, w którym próbujemy zwiększać wydajność poprzez zwiększanie możliwości pojedynczej maszyny. Może to być zwiększanie mocy procesora, dodanie większej ilości pamięci lub inne tego typu zabiegi.

Mówiąc o skalowaniu horyzontalnym (scale-out) mamy na myśli dodawanie większej ilości równoległych maszyn czy budowanie klastra. Na każdej maszynie będzie uruchomiony takie samo oprogramowanie. W tym przypadku potrzebujemy dodatkowego mechanizmu, jakim jest load balancer, który zapewni, że każde żądanie trafi w odpowiednie miejsce.

Partycjonowanie wertykalne vs. horyzontalne

Mówiąc o partycjonowaniu mamy na myśli dane, a dokładniej sposób ich ułożenie w sensowny sposób. Z partycjonowaniem na pewno mieliście do czynienia w przypadku podziału dysków w systemie operacyjnym na mniejsze dyski.

Partycjonowanie wertykalne w bazach danych polega na tworzeniu tabel z mniejszą liczbą kolumn i używaniu innych tabel do zapisywania pozostałych danych. Ogólnie takie partycjonowanie polega na rozdzielaniu danych, które mógłby znajdować się w jednej tabeli na kilka innych. Dobrym motywem do wydzielenia może być większa częstotliwość korzystania (odczyt lub zapis) z niektórych danych. Dla przykładu możemy mieć tabelę z imionami i nazwiskami osób (częste wyszukiwania po tych danych) oraz drugą tabelę z ich numerami telefonów, adresem oraz innymi danymi.

Partycjonowanie horyzontalne polega na zapisywaniu różnych wierszy w różnych tabelach tego samego typu. Za przykład posłuży baza danych firm. Schemat bazy danych będzie wyglądał tak samo dla każdej instancji. Różnica będzie w podziale danych. W przypadku firm możemy przyjąć kryterium liczby pracowników np. mniej niż 100 pracowników, 100 – 10000 pracowników oraz powyżej 10000. Na tej podstawie możemy stworzyć 3 bazy danych z takim samym schematem, ale różnymi danymi. Takie partycjonowanie może być przydatne w systemach multitenant, gdzie dodatkową zaletą shardingu będzie brak konieczności tworzenia dodatkowych zabezpieczeń, aby uniknąć wyciekom danych między tenantami.

Zalety Shardingu

  • Mechanizm shardingu pozwala nam na skalowanie horyzontalne,
  • Zwiększenie czasu odpowiedzi zapytań – dzięki podziałowi danych zapytania nie muszą przeglądać wszystkich wierszy w tabeli,
  • Zmniejszenie ryzyka kompletnej awarii systemu – nawet w przypadku gdy któraś z maszyn przestanie działać, pozostałe nadal będą uruchomione. Może nie jest to idealne rozwiązanie, ale lepiej gdy aplikacja działa przynajmniej dla części użytkowników, niż miałaby wcale nie działać.
  • Może redukować koszty. Dużo implementacji tej architektury opiera się o niskokosztowe darmowe bazy danych, które nie wymagają drogiego hardware, aby działać wydajnie.

Wady Shardingu

  • Zaprojektowanie systemu w taki sposób, aby wykorzystać mechanizm shardingu, jest skomplikowane. Nieprawidłowa implementacja może spowodować niespójność, a nawet utratę danych
  • Bazy danych mogą okazać się nieprawidłowo zbalansowane. To oznacza, że niektóre shardy mogą być bardziej eksploatowane niż inne. Mamy wtedy do czynienia z hotspotem.
  • Raz podzielona baza danych może być trudna do przywrócenia gdy jednak zdecydujemy się korzystać z jednej instancji.
  • Nie każda baza danych natywnie wspiera sharding.

Co to hotspot?

Hotspot to shard, który jest używany dużo częściej niż inne. Załóżmy, że w bazie danych mamy tabelę użytkowników i zdecydowaliśmy, że podział będzie odbywał się na podstawie nazwisk. Tak więc mamy dwie grupy: A-M oraz N-Z. Jakimś dziwnym sposobem 3/4 użytkowników ma nazwisko zaczynające się od litery z drugiej grupy. W tym przypadku hotspotem będzie ta instancja, która obsługuję użytkowników N-Z i nie jest to pożądane zjawisko.

Zapisz się na newsletter, aby otrzymywać informacje o nowych artykułach oraz inne dodatki.

Architektura

Key Based Sharding

Inna nazwa dla tego podejścia to hash based sharding. Polega ono na użyciu jakiegoś klucza (ID, IP, kod pocztowy, kod państwa itp) z nowo generowanej wartości i użycia go jako wsad do funkcji generującej hash. Na podstawie tej wygenerowanej wartości podejmowana jest decyzja, który shard ma obsługiwać te dane.

Key Based Sharding
Key Based Sharding

Próby dodania nowej maszyny mogą być nieco skomplikowane. W takim przypadku potrzebujemy wygenerować nowy klucz dla nowego sharda. Może się również okazać, że będziemy musieli przegenerować część lub nawet wszystkie klucze dla wartości z pozostałych shardów oraz odpowiednio te wartości przemigrować.

Zaletą tego podejścia jest równomierne rozłożenie elementów między maszynami, aby uniknąć hotspotów.

Range Based Sharding

To podejście cechuje się podziałem bazującym na pewnych zakresach. Może to być np. cena czy rozmiar.

Range based sharding
Range Based Sharding

Zaletą tego podejścia jest łatwość implementacji. Jeśli z góry wiemy, że mamy ustalenie gdzie trafiają które dane to wystarczy napisać odpowiedni kod, który sprawdzi ten warunek.

Wadą jest możliwość wystąpienia hotspotów. Nie jesteśmy w stanie zapewnić, że nasze zakresy będą równomiernie rozłożone między shardami.

Directory Based Sharding

Aby zaimplementować tę architekturę, najpierw trzeba utworzyć specjalną tabelę, która będzie zawierać klucze. Klucze w tabeli mówią nam, który shard zawiera dane, które nas interesują.

To podejście jest podobne do range based sharding. Różnica jest taka, że zamiast nie musimy ustalać za każdym razem, gdzie trafią dane, tabela z kluczami po prostu nam to “powie”.

Directory Based Sharding
Directory Based Sharding

Dużą zaletą tej architektury jest jej elastyczność. W przeciwieństwie do dwóch wyżej opisanych podejść możemy zastosować dowolny algorytm rozmieszczenia danych. Zwiększenie ilości maszyn również będzie łatwiejsze.

Minusem jest fakt, że przy każdym żądaniu musimy wykonać zapytanie do dodatkowej tabeli. Jeśli okaże się, że została ona nieoptymalnie zbudowana, to przy większych ilościach danych może spowalniać działanie.

Co zoptymalizować, zanim zdecydujesz się na sharding?

Zanim zdecydujesz się wdrożyć sharding w swoim systemie, rozważ poniższe rozwiązania.

  • Zdalna baza danych – jeśli Twój system bazy danych jest zainstalowany na tym samym serwerze co aplikacja, możesz zwiększyć wydajność przez odciążenie maszyny i przeniesienie bazy na zupełnie inną. To nie jest skomplikowana operacja, ale powinna zwiększyć wydajność.
  • Implementacja pamięci podręcznej – implementacja cacheingu może być dobrym pomysłem gdy problemem w Twoim systemie są wolne odczyty.
  • Stworzenie repliki – chodzi po prostu o skopiowanie bazy danych tak, aby równocześnie działało więcej instancji. W tym przypadku mamy różne podejścia: master-slave, jedna instancja do zapisu a druga do odczytu itp.
  • Skalowanie wertykalne – być może dołożenie większej ilości zasobów sprzętowych do maszyny może spowodować zwiększenie wydajności Twojego systemu.

Podsumowanie

Sharding może wprowadzać większą złożoność do systemu oraz tworzyć potencjalne miejsca wytwarzania się błędów. Należy pamiętać, że sharding nie jest natywnie wspierany w każdym systemie baz danych. Mimo to jest to dobry sposób na zwiększenie wydajności.

Warto wspomnieć o architekturze mikroserwisów. Moim zdaniem, zanim zdecydujemy się na zbudowanie architektury mikrousług, warto zastanowić się, czy sharding nie rozwiąże problemów, z którymi będziemy się zderzać.

P.S. Być może zastanawiasz się, czemu na obrazku wyróżniającym dodałem jakiś budynek. Ta wieża wygląda jak odłamek czegoś większego i nazywa się The Shard.

Dziękuję za przeczytanie mojego artykułu. Zapraszam do poczytania innych wpisów oraz do kontaktu bezpośredniego lub na grupie na facebooku.

Neo4j – Wprowadzenie do prawdziwych relacji między danymi

Kiedy pierwszy raz usłyszałem o Neo4j, pomyślałem, że tego typu bazy danych są używane tylko do budowania silników rekomendacji. Moja percepcja zmieniła się kiedy zobaczyłem prezentację o GAAND Stack (GraphQL, Apollo, Angular, Neo4j Database). Podczas tego szkolenia zdałem sobie sprawę, że jest o wiele więcej przypadków użycia tego narzędzia. Zaraz po szkoleniu postanowiłem “zanurkować” nieco głębiej w tę technologię. Okazuje się, że Neo4j to potężne narzędzie między innymi dzięki szybkości oraz sposobowi reprezentacji danych.

Są dwa powody, dla których powstał ten artykuł. Pierwszym jest stworzenie kompletnej instrukcji tworzenia GRAND Stack (to samo co GAAND tylko z React.js zamiast Angulara). Drugim powodem jest ustrukturyzowanie wiedzy o Neo4j jako przygotowanie do profesjonalnego certyfikatu z tej technologii (* już posiadam certyfikat w momencie gdy tłumaczę artykuł z innego mojego bloga).

Co to jest grafowa baza danych?

Krótko mówiąc, grafowa baza danych to baza, która używa grafu do zapisywania danych i połączeń między nimi. Dokładnie tak samo jak w grafie takie bazy danych mają węzły (nodes) oraz krawędzie (edges), które mogą być jednostronnie (unidirectional) lub dwustronnie (bidirectional) skierowane.

Tak ustukturyzowane dane są bardzo łatwe do zrozumienia dla ludzi oraz pozwalają na szybkie wyszukiwanie dużo lepiej niż inne struktury danych.

Struktura bazy danych Neo4j

Przykładowy graf

Co to jest węzeł?

Węzeł (node) jest obiektem który reprezetuje pojedynczą encję. Węzeł może przechowywać dane w postaci właściwości (properties).

Węzeł z Neo4j

Co to jest relacja?

Relacja to niekoniecznie trafne tłumaczenie angielskiego słowa relation, ale chodzi o połączenie między węzłami. W grafie będzie to po prostu krawędź. Jak sama nazwa mówi, jest ona elementem bazy danych reprezentującą związek, jaki zachodzi między dwoma węzłami. Relacja, podobnie jak węzeł, może mieć właściwości oraz musi mieć dokładnie jeden typ.

Relacje w Neo4j

Co to jest typ relacji?

Typ relacji definiuje jaką rolę jeden węzeł pełni względem innego i wyjaśnia, dlaczego dwa węzły są ze sobą połączone.

Typ relacji w Neo4j

Co to jest etykieta?

Etykieta (label) jest używana do przypisywania węzłów do różnych grup. Węzeł może mieć dowolną ilość etykiet.

O etykietach możemy myśleć jak o nazwach tabel z relacyjnej bazy danych takiej jak np. MySQL. Etykiety definiują rodzaje węzłów. W poniższym przypadku mamy dostępne dwa typy: Person oraz Movie.

Etykiety w Neo4j

Co to jest właściwość?

Właściwości są właściwie zwykłymi danymi, które mogą być przechowywane przez węzły lub relacje.

Właściwości w Neo4j

Co to jest traversal?

Używam angielskiej nazwy, bo jej polski odpowiednik – przejście – brzmi trochę śmiesznie. Grafowe bazy danych korzystają z path traversal, aby wykonać zapytanie, o których dane potrzebujemy. Traversing (przechodzenie) grafu oznacza odwiedzanie poszczególnych węzłów, podążając zdefiniowanymi krawędziami. Algorytm przechodzi po krawędziach zgodnie z regułami ustawionymi w zapytaniu.

Co to jest index?

Index – podobnie jak w innych bazach danych – pozwala nam zwiększyć wydajność pobierania danych. Baza danych tworzy kopię danych i zapisuje je w możliwie najbardziej efektywny sposób. Powoduje to większe zużycie pamięci i nieco wolniejsze zapisy danych.

Co to jest constraint?

W bazach danych programiści mogą tworzyć pewne ograniczenia zabezpieczające system przed wprowadzaniem niepoprawnych danych. O to właśnie dbają konstrukcje zwane constraintami. Programista definiuje reguły a baza danych przed zatwierdzeniem danych, sprawdza ich poprawność.

Język zapytań w Neo4j – Cypher

Cypher jest językiem zapytań używanym w Neo4j. Dla osób, które miały okazję korzystać z SQL ten język będzie wyglądał znajomo. Cypher nieco przypomina mi streamy w Javie, ponieważ pisząc zapytania, przypominają one coś w rodzaju strumienia. Czytanie zapytania od lewej do prawej przypomina czytanie zdania w języku naturalnym.

Ten język zapytań używa ASCII-Art do tworzenia wzorców, które czynią Cypher bardziej czytelnym. Po spojrzeniu na kod, od razu wiemy, co jest węzłem, co relacją oraz jak zamierzamy użyć tych informacji.

Podstawowe zapytania

Pobieranie danych

MATCH (actor { name: 'Charlie Sheen' })-[:ACTED_IN]->(movie)<-[:DIRECTED]-(director)
RETURN movie.title, director.name

Powyższy kod “mówi” bazie danych, aby zwróciła tytuły filmów oraz imiona reżyserów, w których jednym z aktorów był Charlie Sheen.

Zauważ, że używamy relacji jednokierunkowej poprzez wpisanie strzałki (-[:RELATION_TYPE]->). Ta strzałka precyzyjnie wyjaśnia związek między węzłami.

Tworzenie węzła

CREATE (a:Artist { Name : "Strapping Young Lad" })

Możemy również tworzyć wiele węzłów naraz – używając jednej komendy – oddzielając je przecinkami

CREATE (a:Album { Name: "Killers"}), (b:Album { Name: "Fear of the Dark"}) 
RETURN a,b

lub przez użycie oddzielnych instrukcji CREATE

CREATE (a:Album { Name: "Piece of Mind"}) 
CREATE (b:Album { Name: "Somewhere in Time"}) 
RETURN a,b

Tworzenie relacji

MATCH (a:Actor),(b:Movie)
WHERE a.Name = "John Tree" AND b.Name = "The neo4j movie"
CREATE (a)-[r:ACTED_IN]->(b)
RETURN r

Jak widać powyżej, do tworzenia związków między węzłami używane jest to samo słowo kluczowe. Jedyne co trzeba dodać to informacje o węzłach, które mają być ze sobą powiązane.

Rożnica w porównaniu do SQL

Jeśli znasz SQL, prawdopodobnie zobaczysz wiele podobieństw między tymi językami zapytań. Klauzule takie jak WHERE, UNION, ORDER BY oraz CREATE istnieją w obydwu językach. Główną różnicą jest brak instrukcji JOIN dzięki temu, że Neo4j jest zbudowana w zupełnie inny sposób niż klasyczne relacyjne bazy danych.

Transakcje w Neo4j

Neo4j wspiera ACID, aby w pełni wspierać integralność danych oraz zapewnić dobre zachowanie transakcji.

Wszystkie operacje na danych takie jak dostęp do grafu, indexów czy schematu powinniśmy wykonywać w transakcji.

Ważne do zapamiętania:

  • Dane pobrane podczas przeglądania grafu nie są z żaden sposób chronione przed modyfikacją przez inną transakcję,
  • Mogę wystąpić niepowtarzalne odczyty (non-repeatable) – podczas transakcji zakładane są tylko blokady zapisu,
  • Istnieje możliwość manualnego założenia blokad na węzły oraz relacje, aby osiągnąć wyższy poziom izolacji,
  • Wykrywanie zakleszczeń (deadlock) jest mechanizmem wbudowanym w systemie zarządzania transakcjami.

Aby przeczytać więcej o transakcjach w Neo4j, odwiedź tę stronę.

Poziom Izolacji

Transakcje w Neo4j używają poziomu READ_COMMITED. To oznacza, że transakcje nie widzą żadnych niezatwierdzonych zmian z innych transakcji. Dodatkowo Java API udostępnia możliwość doprecyzowania blokad na węzłach oraz relacjach. Blokady dają możliwość symulowania wyższych poziomów izolacji poprzez zakładanie i zdejmowanie blokad.

Zapisz się na newsletter, aby otrzymywać informacje o nowych artykułach oraz inne dodatki.

Badanie zapytań w Neo4j

EXPLAIN

Komenda EXPLAIN umożliwia nam sprawdzenie planu wykonania zapytanie bez potrzeby uruchamiania kodu. Aby wykonać plan zapytania, wystarczy poprzedzić nasze zapytanie słowem kluczowym EXPLAIN. Taka konstrukcja zwróci nam pusty wynik i nie spowoduje wprowadzenia żadnych zmian na bazie danych.

Poniżej umieściłem wynik następujacego zapytania

EXPLAIN MATCH p=()-[r:ACTED_IN]->() RETURN p LIMIT 25
Plan zapytania
Plan zapytania w Neo4j

PROFILE

Aby sprawdzić, co w naszym zapytaniu wykonuje większość pracy, możemy użyć komendy PROFILE na początku zapytania. Ta komenda uruchamia zapytanie i śledzi ile wierszy wyników przeszło rzez poszczególne operatory. Dodatkowo sprawdzany jest czas, jaki operator potrzebował na interakcję z bazą danych, aby otrzymać dane.

Przykład:

PROFILE MATCH p=()-[r:ACTED_IN]->() RETURN p LIMIT 25
Plan Profilu

Nazewnictwo w Neo4j

Etykieta węzła

Do nazywania etykiet węzłów używamy CamelCase

Poprawna nazwaNiepoprawna nazwa
VehicleOwnervehicle_owner
NetworkNodenetworkNode

Nazwa relacji/związku

Do nazywania relacji używamy wielkich liter, gdzie słowa oddzielone są od siebie znakiem “podłogi” (underscore)

Poprawna nazwaNiepoprawna nazwa
ACTED_INacted_in
OWNED_BYownedBy

Nazwa właściwości

Do nazywania właściwości używamy loweCamelCase.

Poprawna nazwaNiepoprawna nazwa
firstNamefirst_name
amountOfStudentsAMOUNT_OF_STUDENTS

Porównanie do relacyjnej bazy danych

Zakładając hipotetyczną sytuację, że chcemy przemigrować dane z bazy relacyjnej do Neo4j, musielibyśmy myśleć o poszczególnych wierszach jak o węzłach. Mając tę analogię, nazwa tabeli byłaby etykietą węzła. Właściwości w węźle byłby po prostu danymi z poszczególnych wierszy. Nazwa każdej kolumny z kluczem obcym może być wzięta pod uwagę podczas budowania związków między węzłami.

Protokół komunikacyjny w Neo4j – Bolt

Bolt jest nieustandaryzowanym protokołem open-source stworzonym na potrzeby baz danych. Protokół ten jest zorientowany na komunikaty (znów dziwne tłumaczenieni z statement-oriented). Mówiąc prościej, oznacza to, że klient może wysłać komunikaty zawierające ciągi znaków wraz ze zbiorem parametrów. Serwer będzie odpowiadał wiadomościami oraz opcjonalnym strumieniem danych. Neo4j używa tego protokołu a domyślny port to 7687.

Neo4j Bloom

Bloom jest aplikacją dostępną w Graph Platform, która umożliwia użytkownikowi nawiązać wizualną interakcję z danymi w postaci grafu. W prostych słowach jest to aplikacja internetowa, która wizualnie przedstawia graf, z którym pracujemy.

Bloom

Aby zobaczyć więcej o Neo4j Bloom, zachęcam do obejrzenia poniższego wideo.

Licencja

Są dwa typy licencji. Community jest w pełni działającą bazą danych, która może być używana do projektów open-source, projektów wewnątrz organizacji lub do aplikacji uruchamianych na prywatnych urządzeniach. Enterprise udostępnia większą dostępność oraz skalowalność do komercyjnego użycia.

Baza Neo4j wspiera startupy. Aby otrzymać licencję Enterprise dla startupu, wystarczy dołączyć do programu dla startupów oraz spełnić opisane tam wymagania. Zobaz więcej tutaj.

Podsumowanie

Z mojej perspektywy grafowe bazy danych są idealnym wyborem gdy musimy zamodelować prawdziwe zależności lub jakiekolwiek bardziej złożone związki między obiektami. Wyszukiwanie w grafach jest niesamowicie szybkie, co jest obecnie wielką zaletą. Struktura danych w grafie jest o wiele łatwiejsza do wyobrażenia niż w jakiejkolwiek dokumentowej czy tabelarycznej bazie danych.

W Neo4j bardzo lubię sposób operowania na danych. Cypher jest intuicyjnym językiem, który dokładnie pokazuje, co chcemy zrobić. Cypher swoją czytelność zawdzięcza użyciu ASCII-Art. Ten język jest czysty, a napisany w nim kod możemy czytać jak zwykłe zdanie. Dodatkowo zachwycony jestem narzędziem Bloom. Dzięki idealnej wizualizacji grafów oraz dobremu interfejsowi praca z nim jest intuicyjna i przyjemna.

Neo4j nie jest najtańszym narzędziem, ale w przypadku gdy szybkość pobierania danych ma znaczenie, może okazać się idealnym wyborem. Dzięki temu, że bazy grafowe są elastyczne i łatwe w utrzymaniu, dają nieproporcjonalnie dużo zalet w niektórych typach projektów.

Aby podsumować, chciałbym wszystkim polecić Neo4j jako bazę danych do wszystkich projektów, które mogłyby skorzystać z dobrodziejstwa path traversal i innych algorytmów grafowych, jak również z elastycznych relacji i modelowania danych.

Artykuł napisany na podstawie mojego bloga EagerToIt oraz własnych doświadczeń. Zapraszam do kontaktu i dyskusji.