Scott Tiger Tech Blog

Blog technologiczny firmy Scott Tiger S.A.

Apache HBase

Autor: Piotr Karpiuk o 9. marca 2015

HBase to otwartoźródłowa, zaimplementowana w Javie, rozproszona baza danych inspirowana Googlowym BigTable. Jest częścią projektu Apache Hadoop i przechowuje dane w HDFS (Hadoop Distributed FileSystem). Jest to przykład tzw. kolumnowej NoSQLowej bazy danych, pozwalającej na przechowywanie w sposób odporny na awarie dużej ilości danych rzadkich (ang. sparse data) – które w relacyjnej bazie wymagałyby tabel z dużą ilością przeważnie pustych kolumn.

Pierwotnie HBase był tworzony z myślą o przetwarzaniu języka naturalnego, a od czasu przejęcia projektu przez Apache z bazy korzysta obecnie szereg firm (w tym Twitter, Stumbleupon, eBay, Yahoo!), a bodaj najbardziej spektakularne jest użycie tej bazy przez Facebooka do implementacji komunikatora internetowego Facebook Messenger.

Powszechna wiedza głosi, że sens użycia HBase pojawia się dopiero przy przetwarzaniu co najmniej 100 GB danych przy użyciu min. 5 maszyn w klastrze.

Autorzy dokumentacji używają pojęć nawiązujących do modelu relacyjnego: tabela, wiersz, kolumna – ale jest to bardzo kontrowersyjny pomysł. Osobie przyzwyczajonej do świata relacyjnych baz danych w miarę wgryzania się w HBase oczy będą się coraz bardziej otwierać ze zdumienia. W rzeczy samej, HBase wobec Oracla jest jak zły brat bliźniak, Bizarro, mr. Hyde czy Frankenstein.

Spójrzmy na poniższy rysunek, objaśniający model danych omawianej dzisiaj bazy:

W tabeli mamy dowolnie wiele wierszy, każdy identyfikowany unikalnym kluczem. Kolumny są pogrupowane w rodziny kolumn (zwane też superkolumnami). O ile rodzin kolumn zwykle jest kilka, o tyle samych kolumn mogą być miliony. HBase nie jest transakcyjna i zapewnia atomowość na poziomie rekordu (wiersza), przy czym mamy wersjonowanie: pamiętane są domyślnie 3 ostatnie wersje każdego rekordu opatrzone stemplami czasowymi. Sens istnienia superkolumn jest taki, że dla każdej z nich można zdefiniować inne parametry bazy danych, np. rodzaj kompresji danych (GZ, LZO), poziom redundancji, czas po jakim dane mają być usuwane, czy wersjonowanie. Modyfikacja parametrów superkolumny jest kosztowna – pociąga za sobą utworzenie nowej superkolumny z nową specyfikacją i skopiowanie wszystkich danych – dlatego warto ustawić parametry na docelowe zanim zacznie się wstawiać dane. Wartości kolumn nie mają typów i są traktowane jako ciągi bajtów.

W zasadzie, być może zamiast kurczowo trzymać się nomenklatury nazewniczej z relacyjnych baz danych lepiej byłoby spojrzeć na model danych HBase jak na 4-poziomową mapę asocjacyjną (kolejne poziomy to tabela, klucz, superkolumna i kolumna).

Nazwę kolumny określa się mianem kwalifikatora kolumny (ang. column qualifier). Połączenie klucza wiersza i pełnej nazwy kolumny (wraz z nazwą rodziny) zapisuje się w postaci table/family:qualifier.

Rekordy w bazie są posortowane wedle klucza i podzielone na rozłączne regiony, przy czym za każdy region odpowiedzialna jest inna maszyna klastra.

HBase nie posiada języka zapytań, nie ma indeksów. Zapewnione jest tylko skanowanie całej tabeli i bardzo szybki dostęp do rekordu po kluczu i do wartości kolumny w rekordzie, jak również Hadoopowy mechanizm MapReduce.

Facebook używa HBase jako głównego komponentu swojej infrastruktury komunikatów, zarówno do przechowywania komunikatów jak i odwróconego indeksu (ang. inverted index) na potrzeby wyszukiwania.
W tabeli implementującej indeks:

  • Kluczem wiersza jest ID użytkownika.
  • Kwalifikatorami kolumn są słowa występujące w komunikatach tego użytkownika.
  • Stemple czasowe są identyfikatorami komunikatów zawierających to słowo.

W ten sposób wykorzystywane jest wersjonowanie.

W przykładach poniżej będziemy używać powłoki napisanej w języku JRuby, ale są też inne sposoby komunikacji z bazą (najpopularniejszy jest Thrift):

Nazwa Metoda połączenia Dojrzałość
Shell Bezpośrednia Tak
Java API Bezpośrednia Tak
Thrift Protokół binarny Tak
REST HTTP Tak
Avro Protokół binarny Nie

Czytaj więcej »

Tags: , ,
Napisany w Bazy danych, Cloud computing | Brak komentarzy »

Riak

Autor: Piotr Karpiuk o 3. marca 2015

Jedną z najczęściej spotykanych w świecie programistów strukturą danych jest słownik (mapa, tablica asocjacyjna, hasz), w którym każdemu kluczowi przyporządkowujemy jakąś wartość i oferujemy szybki (w czasie niemal stałym) dostęp do wartości poprzez klucz. W dzisiejszym świecie taka struktura może przybierać duże rozmiary — wystarczy zauważyć że całą światową sieć WWW możemy w pewnym uproszczeniu traktować jak wielki rozproszony słownik, w którym łańcuchowi (URL) przyporządkowujemy wartość (dokument HTML, obrazek, film itp.). Po jakie rozwiązanie technologiczne sięgnęlibyśmy, aby we własnym centrum danych lub w chmurze zrobić trwałą kopię całej sieci WWW lub jej części?

NoSQL-owe bazy danych typu key-value, takie jak omawiany dziś Riak wydają się być stworzone właśnie do takich wielkich zadań, a wiele rozwiązań czerpią z klasycznego już produktu Amazon Dynamo.

Szybkie podsumowanie głównych cech Riaka:

  • Najważniejszy nacisk:
    • masowa obsługa wielu żądań dostępu, z małym opóźnieniem (czyli maksymalna dostępność),
    • skalowalność (pozioma, czyli realizowana poprzez dostawianie kolejnych maszyn do klastra),
    • odporność na awarie (brak pojedynczego miejsca narażonego na awarię, duża tolerancja awarii pojedynczych maszyn); nawet w razie rozpadu klastra na kilka części (ang. split brain) dane mogą być wciąż zapisywane w sąsiednich maszynach innych niż pierwotnie dla tych danych przeznaczone, minimalizując negatywny wpływ na funkcjonalność bazy.
  • Z zasady bazy tego rodzaju nie wnikają czym są przechowywane wartości, traktując je jak ciąg bitów — w praktyce najczęściej będą to dokumenty tekstowe (TXT, JSON, XML), binarne (Word, Excel, PDF) i multimedia (filmy, obrazki).
  • Jak możemy przeczytać na Wikipedii, Riak jest obecnie używany w tysiącach firm na świecie, w szczególności w 25% firm z listy Fortune 50, np. AT&T, Comcast, UK National Health Services (NHS), czy The Weather Channel.
  • Dostępna jest wersja otwartoźródłowa.
  • Riak jest zaimplementowany w języku Erlang, a funkcje składowane, funkcje map/reduce i triggery mogą być pisane w Erlangu lub JavaScripcie (baza ma wbudowany silnik JavaScriptu SpiderMonkey, ten sam co w przeglądarce Mozilla Firefox).
  • Najprostszy jest dostęp do bazy poprzez interfejs REST, co pozwala przeglądać rekordy i wykonywać proste zapytania z poziomu dowolnej przeglądarki internetowej — wystarczy odpowiednio skonstruować URLa. Oficjalne sterowniki pozwalają na dostęp do bazy z języków programowania Java, Ruby, Erlang i Python, ale społeczność skupiona wokół Riaka udostępnia sterowniki również dla wielu innych języków.

Tak jak znakomita większość baz NoSQL, Riak nie ma wbudowanej obsługi transakcji (jest to cena jaką trzeba płacić za skalowalność i wydajność) i zapewnia atomowość wyłącznie dla operacji CRUD (Create, Read, Update, Delete) wykonywanych na pojedynczych rekordach (para klucz-wartość).

Ponieważ w praktyce w bazie danych przechowuje się obiekty o różnej strukturze i/lub różnych typów, więc Riak pozwala podzielić przestrzeń kluczy na rozłączne klasy abstrakcji — co jest odpowiednikiem tabel w relacyjnej bazie danych, czy jednopoziomowej struktury katalogów w systemie plików. Takie przestrzenie nazw zwane są kubełkami (ang. buckets), a dalej również żartobliwie wiadrami.

Z uwagi na skalowalność nie ma też języka zapytań ad hoc, który przypominałby coś w rodzaju SQLa w relacyjnych bazach danych. Nie oznacza to jednak, że funkcjonalność odczytu sprowadza się jedynie do wyciągnięcia wartości na podstawie klucza:

  • Z każdym rekordem można związać metadane w postaci nagłówek-wartość, na których można założyć indeks wtórny (ang. secondary index) i wyszukiwać po konkretnej wartości nagłówka.
  • Zawartość bazy można przetwarzać wsadowo za pomocą dobrze znanego w świecie BigData mechanizmu MapReduce — niestety wymaga to pisania własnych funkcji map i reduce które nie są tak intuicyjne jak zapytania SQLa.
  • Riak jest bazą rozszerzalną i jednym z dostępnych modułów jest opcja tworzenia indeksu odwróconego (ang. inverted index) na wartościach typu JSON i XML, i mechanizmu pełnotekstowego przeszukiwania wartości (wykorzystywane jest tu narzędzie Apache Solr).
  • Z każdym rekordem można związać metadane w postaci linków do innych rekordów (klucz docelowego obiektu z etykietką powiązania), a Riak pozwala zapytać o wszystkie rekordy danego typu (lub z daną etykietką) dostępne poprzez linki z danego rekordu.

Odpowiednikami wyzwalaczy z relacyjnych baz danych są tzw. pre- i post commit hooks, definiowane na poziomie wiadra. Pierwsze mogą być pisane w JavaScripcie lub Erlangu i mogą przekształcić dane rekordu przed ostatecznym zapisem lub odrzucić operację, a drugie mogą być pisane tylko w Erlangu i wykonać dodatkową akcję (np. wysłanie maila lub zarejestrowanie czegoś w logu).

Odporność na awarie i związana z tym potrzeba redundancji danych, w połączeniu z wymogami wydajnościowymi rodzą dość ciekawe problemy synchronizacyjne. Ponieważ klienci mogą modyfikować rekordy na dowolnej maszynie klastra, łatwo może dojść do sytuacji gdy dwaj różni klienci zmodyfikują ten sam rekord, zduplikowany na kilku maszynach. Aby rozstrzygać tego rodzaju konflikty nie używa się stempli czasowych (byłoby to trudne do osiągnięcia w systemie rozproszonym, i wymagałoby zarządcy podatnego na awarie), lecz tzw. wektorów wersji (ang. vector clocks). W istocie dla każdej operacji na danych przechowuje się informacje takie jak kto modyfikował dany rekord i w jakiej kolejności. Przypomina to działanie systemów kontroli wersji takich jak Subversion czy Git.

Załóżmy że stopień redundancji każdego rekordu bazy wynosi N=3 (wartość domyślna), co znaczy że każdy rekord istnieje w 3 kopiach na 3 różnych maszynach klastra. Powstaje pytanie jak przebiega operacja modyfikacji rekordu przez klienta — czy kończy się ona sukcesem z chwilą przyjęcia zlecenia przez serwer, utrwalenia wartości na dysku, a może dopiero po rozesłaniu duplikatów do jednego lub dwóch innych serwerów? Otóż można o tym zadecydować na poziomie kubełka, a nawet przy każdej operacji odczytu/zapisu. Zauważmy że jeśli operacja modyfikacji będzie czekać na zapis rekordu przynajmniej na dwóch maszynach (W=2), to również późniejszy odczyt będzie wymagał odczytu i porównania zawartości rekordu z dwóch maszyn (R=2). Z kolei jeśli zażyczymy sobie aby zapis kończył się dopiero po utworzeniu wszystkich trzech kopii (W=3), wówczas mamy szybszy odczyt — wystarczy pobrać rekord z jednej maszyny (R=1). Ogólnie musi być spełniony warunek R+W>N.

Czytaj więcej »

Tags: , ,
Napisany w Bazy danych | Brak komentarzy »