Scott Tiger Tech Blog

Blog technologiczny firmy Scott Tiger S.A.

Archiwum dla Marzec 3rd, 2015

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 »