Cechy projektowania modelu danych dla NoSQL

Wprowadzenie

Cechy projektowania modelu danych dla NoSQL „Musisz biec tak szybko, jak potrafisz, żeby pozostać w miejscu,
a żeby gdzieś dotrzeć, trzeba biec co najmniej dwa razy szybciej!”
(c) Alicja w Krainie Czarów

Jakiś czas temu poproszono mnie o wygłoszenie wykładu analitycy naszą firmę na temat projektowania modeli danych, ponieważ siedząc nad projektami przez długi czas (czasem kilka lat) tracimy z oczu to, co dzieje się wokół nas w świecie technologii IT. W naszej firmie (tak się składa) wiele projektów nie wykorzystuje baz danych NoSQL (przynajmniej na razie), dlatego w moim wykładzie osobno poświęciłem im trochę uwagi na przykładzie HBase i starałem się zorientować prezentację materiału na te którzy nigdy ich nie używali, zadziałały. W szczególności zilustrowałem niektóre cechy projektowania modelu danych na przykładzie, który przeczytałem kilka lat temu w artykule „Wprowadzenie do projektowania schematu HB ase” autorstwa Amandeepa Khurany. Analizując przykłady, porównałem kilka opcji rozwiązania tego samego problemu, aby lepiej przekazać odbiorcom główne idee.

Niedawno „z niczego” zadałem sobie pytanie (szczególnie sprzyja temu długi majowy weekend w kwarantannie), na ile teoretyczne obliczenia będą pokrywały się z praktyką? Właściwie tak narodził się pomysł na ten artykuł. Programista, który pracuje z NoSQL od kilku dni, może nie dowiedzieć się z niego niczego nowego (i dlatego może od razu pominąć połowę artykułu). Ale dla analitycyMyślę, że dla tych, którzy nie pracowali jeszcze blisko z NoSQL, będzie to przydatne do zdobycia podstawowego zrozumienia funkcji projektowania modeli danych dla HBase.

Przykładowa analiza

Moim zdaniem, zanim zaczniesz korzystać z baz NoSQL, musisz dokładnie przemyśleć i rozważyć za i przeciw. Często problem można najprawdopodobniej rozwiązać za pomocą tradycyjnych relacyjnych systemów DBMS. Dlatego lepiej nie używać NoSQL bez ważnych powodów. Jeśli mimo wszystko zdecydowałeś się na użycie bazy danych NoSQL, powinieneś wziąć pod uwagę, że podejścia do projektowania są tutaj nieco inne. Zwłaszcza niektóre z nich mogą być niezwykłe dla tych, którzy wcześniej mieli do czynienia wyłącznie z relacyjnymi systemami DBMS (z moich obserwacji). Zatem w świecie „relacyjnym” zwykle zaczynamy od modelowania domeny problemowej, a dopiero potem, jeśli to konieczne, denormalizujemy model. W NoSQL my powinien od razu uwzględnić oczekiwane scenariusze pracy z danymi i początkowo denormalizować dane. Ponadto istnieje wiele innych różnic, które zostaną omówione poniżej.

Rozważmy następujący problem „syntetyczny”, nad którym będziemy nadal pracować:

Konieczne jest zaprojektowanie struktury przechowywania listy znajomych użytkowników jakiejś abstrakcyjnej sieci społecznościowej. Dla uproszczenia założymy, że wszystkie nasze połączenia są kierowane (jak na Instagramie, a nie na Linkedinie). Struktura powinna umożliwiać efektywne:

  • Odpowiedz na pytanie, czy użytkownik A czyta użytkownika B (wzorzec czytania)
  • Zezwalaj na dodawanie/usuwanie połączeń w przypadku zapisania/wypisania użytkownika A z użytkownika B (szablon zmiany danych)

Oczywiście istnieje wiele możliwości rozwiązania problemu. W zwykłej relacyjnej bazie danych najprawdopodobniej po prostu utworzylibyśmy tabelę relacji (być może typowaną, jeśli na przykład musimy przechowywać grupę użytkowników: rodzina, praca itp., Która zawiera tego „przyjaciela”) i zoptymalizować prędkość dostępu dodałaby indeksy/partycjonowanie. Najprawdopodobniej stół finałowy będzie wyglądał mniej więcej tak:

user_id
identyfikator_znajomego

Vasya
Pietia

Vasya
Оля

w dalszej części dla przejrzystości i lepszego zrozumienia zamiast identyfikatorów będę podawać nazwiska

W przypadku HBase wiemy, że:

  • możliwe jest efektywne wyszukiwanie, które nie skutkuje pełnym skanowaniem tabeli wyłącznie na klucz
    • w rzeczywistości dlatego pisanie zapytań SQL znanych wielu z takich baz danych jest złym pomysłem; technicznie rzecz biorąc, możesz oczywiście wysłać zapytanie SQL za pomocą złączeń i innej logiki do HBase z tej samej Impala, ale jak skuteczne będzie to ...

Dlatego jesteśmy zmuszeni użyć identyfikatora użytkownika jako klucza. I moja pierwsza myśl na temat „gdzie i jak przechowywać identyfikatory znajomych?” może pomysł, aby przechowywać je w kolumnach. Ta najbardziej oczywista i „naiwna” opcja będzie wyglądać mniej więcej tak (nazwijmy to Opcja 1 (domyślna)w celach informacyjnych):

Klucz wiersza
Głośniki

Vasya
1: Pietia
2: Ola
3: Dasza

Pietia
1: Masza
2: Wasia

Tutaj każda linia odpowiada jednemu użytkownikowi sieci. Kolumny mają nazwy: 1, 2, ... - zgodnie z liczbą znajomych, a w kolumnach zapisane są identyfikatory znajomych. Należy pamiętać, że każdy wiersz będzie miał inną liczbę kolumn. W przykładzie na powyższym rysunku jeden wiersz ma trzy kolumny (1, 2 i 3), a drugi tylko dwie (1 i 2) - tutaj sami wykorzystaliśmy dwie właściwości HBase, których nie mają relacyjne bazy danych:

  • możliwość dynamicznej zmiany składu kolumn (dodaj znajomego -> dodaj kolumnę, usuń znajomego -> usuń kolumnę)
  • różne wiersze mogą mieć różne kompozycje kolumn

Sprawdźmy naszą konstrukcję pod kątem zgodności z wymaganiami zadania:

  • Czytanie danych: aby zrozumieć, czy Vasya subskrybuje Olyę, będziemy musieli odjąć cała linia klawiszem RowKey = „Vasya” i sortuj wartości kolumn, aż „spotkamy” w nich Olyę. Lub iteruj po wartościach wszystkich kolumn, „nie spotykaj” Olyi i zwróć odpowiedź Fałsz;
  • Edycja danych: dodanie znajomego: w przypadku podobnego zadania musimy również odjąć cała linia za pomocą klucza RowKey = „Vasya” obliczyć całkowitą liczbę swoich znajomych. Ta łączna liczba znajomych jest nam potrzebna do określenia numeru kolumny, w której musimy wpisać identyfikator nowego znajomego.
  • Zmiana danych: usuwanie znajomego:
    • Trzeba odjąć cała linia klawiszem RowKey = „Vasya” i posortuj kolumny, aby znaleźć tę, w której zapisany jest znajomy do usunięcia;
    • Następnie po usunięciu znajomego musimy „przesunąć” wszystkie dane do jednej kolumny, aby nie powstały „luki” w ich numeracji.

Oceńmy teraz, jak produktywne będą te algorytmy, które będziemy musieli zaimplementować po stronie „aplikacji warunkowej”, używając O-symbolika. Oznaczmy rozmiar naszej hipotetycznej sieci społecznościowej jako n. Wtedy maksymalna liczba znajomych, których może mieć jeden użytkownik, wynosi (n-1). Dla naszych celów możemy dalej pominąć to (-1), ponieważ w kontekście stosowania symboli O jest to nieistotne.

  • Czytanie danych: konieczne jest odjęcie całej linii i iteracja po wszystkich jej kolumnach w limicie. Oznacza to, że górny szacunek kosztów wyniesie w przybliżeniu O(n)
  • Edycja danych: dodanie znajomego: aby określić liczbę znajomych, należy iterować po wszystkich kolumnach wiersza, a następnie wstawić nową kolumnę => O(n)
  • Zmiana danych: usuwanie znajomego:
    • Podobnie jak w przypadku dodawania - musisz przejść przez wszystkie kolumny w limicie => O(n)
    • Po usunięciu kolumn należy je „przesunąć”. Jeśli zaimplementujesz to „bezpośrednio”, wówczas w limicie będziesz potrzebować do (n-1) operacji. Ale tutaj i dalej w części praktycznej zastosujemy inne podejście, które zaimplementuje „pseudo-przesunięcie” dla ustalonej liczby operacji - to znaczy, że będzie na to poświęcany stały czas, niezależnie od n. Ten stały czas (dokładnie O(2)) można pominąć w porównaniu z O(n). Podejście ilustruje poniższy rysunek: po prostu kopiujemy dane z „ostatniej” kolumny do tej, z której chcemy usunąć dane, a następnie usuwamy ostatnią kolumnę:
      Cechy projektowania modelu danych dla NoSQL

W sumie we wszystkich scenariuszach otrzymaliśmy asymptotyczną złożoność obliczeniową O(n).
Pewnie już zauważyłeś, że prawie zawsze musimy przeczytać cały wiersz z bazy danych, a w dwóch przypadkach na trzy po prostu przejrzeć wszystkie kolumny i obliczyć całkowitą liczbę znajomych. Dlatego w ramach próby optymalizacji możesz dodać kolumnę „liczba”, która przechowuje całkowitą liczbę znajomych każdego użytkownika sieci. W tym przypadku nie możemy przeczytać całego wiersza, aby obliczyć całkowitą liczbę znajomych, ale odczytamy tylko jedną kolumnę „liczba”. Najważniejsze, aby nie zapomnieć o aktualizacji „liczby” podczas manipulacji danymi. To. poprawiamy się Opcja 2 (liczba):

Klucz wiersza
Głośniki

Vasya
1: Pietia
2: Ola
3: Dasza
liczba: 3 XNUMX

Pietia
1: Masza
2: Wasia

liczba: 2 XNUMX

W porównaniu z pierwszą opcją:

  • Czytanie danych: aby uzyskać odpowiedź na pytanie „Czy Wasia czyta Olyę?” nic się nie zmieniło => O(n)
  • Edycja danych: dodanie znajomego: Uprościliśmy wstawianie nowego znajomego, ponieważ teraz nie musimy czytać całej linii i iterować po jej kolumnach, a możemy jedynie uzyskać wartość kolumny „count” itp. natychmiast określ numer kolumny, aby wstawić nowego znajomego. Prowadzi to do zmniejszenia złożoności obliczeniowej do O(1)
  • Zmiana danych: usuwanie znajomego: Przy usuwaniu znajomego możemy także wykorzystać tę kolumnę do zmniejszenia ilości operacji I/O przy „przesuwaniu” danych o jedną komórkę w lewo. Jednak potrzeba iteracji po kolumnach, aby znaleźć tę, którą należy usunąć, nadal pozostaje, więc => O(n)
  • Z drugiej strony, teraz podczas aktualizacji danych musimy za każdym razem aktualizować kolumnę „count”, ale zajmuje to stały czas, który można pominąć w ramach symboli O

Ogólnie rzecz biorąc, opcja 2 wydaje się nieco bardziej optymalna, ale bardziej przypomina „ewolucję zamiast rewolucji”. Aby dokonać „rewolucji”, będziemy potrzebować Opcja 3 (kolumna).
Odwróćmy wszystko „do góry nogami”: przypiszemy nazwa kolumny identyfikator użytkownika! To, co zostanie zapisane w samej kolumnie, nie jest już dla nas ważne, niech będzie to cyfra 1 (ogólnie można w niej przechowywać przydatne rzeczy, np. grupę „rodzina/znajomi/etc.”). Takie podejście może zaskoczyć nieprzygotowanego „laika”, który nie miał wcześniejszego doświadczenia w pracy z bazami danych NoSQL, ale to właśnie takie podejście pozwala znacznie efektywniej wykorzystać potencjał HBase w tym zadaniu:

Klucz wiersza
Głośniki

Vasya
Pietia: 1
Ola: 1
Dasza: 1

Pietia
Masza: 1
Wasia: 1

Tutaj dostajemy kilka zalet na raz. Aby je zrozumieć, przeanalizujmy nową strukturę i oszacujmy złożoność obliczeniową:

  • Czytanie danych: aby odpowiedzieć na pytanie, czy Vasya subskrybuje Olyę, wystarczy przeczytać jedną kolumnę „Olya”: jeśli tam jest, to odpowiedź brzmi Prawda, jeśli nie – Fałsz => O(1)
  • Edycja danych: dodanie znajomego: Dodawanie znajomego: wystarczy dodać nową kolumnę „Identyfikator znajomego” => O(1)
  • Zmiana danych: usuwanie znajomego: po prostu usuń kolumnę Identyfikator znajomego => O(1)

Jak widać istotną zaletą tego modelu przechowywania jest to, że we wszystkich potrzebnych nam scenariuszach operujemy tylko jedną kolumną, unikając odczytywania całego wiersza z bazy danych, a ponadto wyliczania wszystkich kolumn tego wiersza. Moglibyśmy na tym poprzestać, ale...

Można się zdziwić i pójść nieco dalej ścieżką optymalizacji wydajności i ograniczenia operacji we/wy podczas uzyskiwania dostępu do bazy danych. Co by było, gdybyśmy przechowywali pełne informacje o relacjach bezpośrednio w samym kluczu wiersza? Oznacza to, że utwórz klucz złożony w postaci userID.friendID? W tym przypadku nie musimy w ogóle czytać kolumn linii (Opcja 4 (wiersz)):

Klucz wiersza
Głośniki

Wasia.Petya
Pietia: 1

Wasia.Olya
Ola: 1

Vasya.Dasha
Dasza: 1

Petya.Masza
Masza: 1

Petya.Vasya
Wasia: 1

Oczywiście ocena wszystkich scenariuszy manipulacji danymi w takiej strukturze, podobnie jak w poprzedniej wersji, będzie wynosić O(1). Różnica w porównaniu z opcją 3 będzie polegać wyłącznie na wydajności operacji we/wy w bazie danych.

No i ostatni „ukłon”. Łatwo zauważyć, że w opcji 4 klucz wiersza będzie miał zmienną długość, co może mieć wpływ na wydajność (tutaj pamiętamy, że HBase przechowuje dane jako zbiór bajtów, a wiersze w tabelach są sortowane według klucza). Ponadto mamy separator, który może wymagać obsługi w niektórych scenariuszach. Aby wyeliminować ten wpływ, możesz użyć skrótów z userID i znajomego, a ponieważ oba skróty będą miały stałą długość, możesz je po prostu połączyć, bez separatora. Wtedy dane w tabeli będą wyglądać tak (Opcja 5 (hasz)):

Klucz wiersza
Głośniki

dc084ef00e94aef49be885f9b01f51c01918fa783851db0dc1f72f83d33a5994
Pietia: 1

dc084ef00e94aef49be885f9b01f51c0f06b7714b5ba522c3cf51328b66fe28a
Ola: 1

dc084ef00e94aef49be885f9b01f51c00d2c2e5d69df6b238754f650d56c896a
Dasza: 1

1918fa783851db0dc1f72f83d33a59949ee3309645bd2c0775899fca14f311e1
Masza: 1

1918fa783851db0dc1f72f83d33a5994dc084ef00e94aef49be885f9b01f51c0
Wasia: 1

Oczywiście złożoność algorytmiczna pracy z taką strukturą w rozważanych przez nas scenariuszach będzie taka sama jak w przypadku opcji 4 - czyli O(1).
W sumie podsumujmy wszystkie nasze szacunki złożoności obliczeniowej w jednej tabeli:

Dodawanie znajomego
Sprawdzam u znajomego
Usuwanie przyjaciela

Opcja 1 (domyślna)
Na)
Na)
Na)

Opcja 2 (liczba)
O (1)
Na)
Na)

Opcja 3 (kolumna)
O (1)
O (1)
O (1)

Opcja 4 (wiersz)
O (1)
O (1)
O (1)

Opcja 5 (hasz)
O (1)
O (1)
O (1)

Jak widać, opcje 3-5 wydają się najkorzystniejsze i teoretycznie zapewniają wykonanie wszystkich niezbędnych scenariuszy manipulacji danymi w stałym czasie. W warunkach naszego zadania nie ma jednoznacznego wymogu uzyskania listy wszystkich znajomych użytkownika, jednak w rzeczywistych działaniach projektowych dobrze byłoby, gdybyśmy jako dobrzy analitycy „przewidzieli” wystąpienie takiego zadania i „rozłóż słomkę”. Dlatego współczuję opcji 3. Jest jednak całkiem prawdopodobne, że w prawdziwym projekcie to żądanie można było już rozwiązać w inny sposób, dlatego bez ogólnej wizji całego problemu lepiej nie robić tego wnioski końcowe.

Przygotowanie eksperymentu

Powyższe teoretyczne argumenty chciałbym sprawdzić w praktyce – taki był cel pomysłu, który zrodził się podczas długiego weekendu. Aby to zrobić, należy ocenić szybkość działania naszej „aplikacji warunkowej” we wszystkich opisanych scenariuszach korzystania z bazy danych, a także wydłużenie tego czasu wraz ze wzrostem rozmiaru sieci społecznościowej (n). Docelowym parametrem, który nas interesuje i który będziemy mierzyć podczas eksperymentu, jest czas, jaki „aplikacja warunkowa” poświęca na wykonanie jednej „operacji biznesowej”. Przez „transakcję biznesową” rozumiemy jedną z następujących czynności:

  • Dodawanie jednego nowego przyjaciela
  • Sprawdzanie, czy Użytkownik A jest przyjacielem Użytkownika B
  • Usuwanie jednego znajomego

Zatem biorąc pod uwagę wymagania określone w oświadczeniu wstępnym, scenariusz weryfikacji wyłania się następująco:

  • Rejestracja danych. Losowo wygeneruj początkową sieć o rozmiarze n. Aby zbliżyć się do „prawdziwego świata”, liczba znajomych każdego użytkownika jest również zmienną losową. Zmierz czas, w którym nasza „aplikacja warunkowa” zapisuje wszystkie wygenerowane dane do HBase. Następnie uzyskany czas dzielimy przez całkowitą liczbę dodanych znajomych – w ten sposób otrzymujemy średni czas na jedną „operację biznesową”
  • Czytanie danych. Dla każdego użytkownika utwórz listę „osobowości”, dla której musisz uzyskać odpowiedź, czy użytkownik je subskrybuje, czy nie. Długość listy = w przybliżeniu liczba znajomych użytkownika, przy czym dla połowy zaznaczonych znajomych odpowiedź powinna brzmieć „Tak”, a dla drugiej połowy – „Nie”. Sprawdzanie odbywa się w takiej kolejności, że odpowiedzi „Tak” i „Nie” są naprzemienne (to znaczy w co drugim przypadku będziemy musieli przejść przez wszystkie kolumny wiersza dla opcji 1 i 2). Całkowity czas badania przesiewowego dzieli się następnie przez liczbę przebadanych znajomych, aby uzyskać średni czas badania przesiewowego na osobę.
  • Usuwanie danych. Usuń wszystkich znajomych z użytkownika. Co więcej, kolejność usuwania jest losowa (tzn. „przetasowujemy” oryginalną listę, na której zapisano dane). Całkowity czas sprawdzania jest następnie dzielony przez liczbę usuniętych znajomych w celu uzyskania średniego czasu trwania czeku.

Scenariusze należy uruchomić dla każdej z 5 opcji modelu danych i dla różnych rozmiarów sieci społecznościowej, aby zobaczyć, jak zmienia się czas w miarę jego wzrostu. W ciągu jednego n połączenia w sieci i lista użytkowników do sprawdzenia muszą być oczywiście takie same dla wszystkich 5 opcji.
Dla lepszego zrozumienia poniżej znajduje się przykład wygenerowanych danych dla n= 5. Napisany „generator” generuje jako dane wyjściowe trzy słowniki ID:

  • pierwszy służy do wkładania
  • drugi służy do sprawdzania
  • po trzecie – do usunięcia

{0: [1], 1: [4, 5, 3, 2, 1], 2: [1, 2], 3: [2, 4, 1, 5, 3], 4: [2, 1]} # всего 15 друзей

{0: [1, 10800], 1: [5, 10800, 2, 10801, 4, 10802], 2: [1, 10800], 3: [3, 10800, 1, 10801, 5, 10802], 4: [2, 10800]} # всего 18 проверяемых субъектов

{0: [1], 1: [1, 3, 2, 5, 4], 2: [1, 2], 3: [4, 1, 2, 3, 5], 4: [1, 2]} # всего 15 друзей

Jak widać, wszystkie identyfikatory większe niż 10 000 w słowniku do sprawdzenia to właśnie te, które z pewnością dadzą odpowiedź Fałsz. Dodawanie, sprawdzanie i usuwanie „znajomych” odbywa się dokładnie w kolejności określonej w słowniku.

Eksperyment przeprowadzono na laptopie z systemem Windows 10, gdzie w jednym kontenerze Docker działał HBase, a w drugim Python z Jupyter Notebook. Dockerowi przydzielono 2 rdzenie procesora i 2 GB pamięci RAM. Cała logika, zarówno emulacja „aplikacji warunkowej”, jak i „potok” do generowania danych testowych i pomiaru czasu, została napisana w Pythonie. Biblioteka została wykorzystana do pracy z HBase szczęśliwa baza, aby obliczyć skróty (MD5) dla opcji 5 - hashlib

Biorąc pod uwagę moc obliczeniową konkretnego laptopa, eksperymentalnie wybrano uruchomienie dla n = 10, 30,…. 170 – gdy łączny czas pracy pełnego cyklu testowego (wszystkie scenariusze dla wszystkich opcji dla wszystkich n) był jeszcze mniej więcej rozsądny i zmieścił się podczas jednego podwieczorku (średnio 15 minut).

W tym miejscu należy zauważyć, że w tym eksperymencie nie oceniamy przede wszystkim bezwzględnych wartości wydajności. Nawet względne porównanie różnych dwóch opcji może nie być całkowicie poprawne. Interesuje nas teraz charakter zmiany czasu w zależności od n, gdyż biorąc pod uwagę powyższą konfigurację „stanowiska badawczego”, bardzo trudno jest uzyskać oszacowania czasu „oczyszczone” z wpływu czynników losowych i innych ( i takie zadanie nie zostało postawione).

Wynik eksperymentu

Pierwszym testem jest to, jak zmienia się czas spędzony na wypełnianiu listy znajomych. Wynik znajduje się na poniższym wykresie.
Cechy projektowania modelu danych dla NoSQL
Opcje 3-5, zgodnie z oczekiwaniami, pokazują prawie stały czas „transakcji biznesowej”, który nie zależy od wzrostu rozmiaru sieci i nieodróżnialnej różnicy w wydajności.
Opcja 2 również wykazuje stałą, choć nieco gorszą wydajność, prawie dokładnie 2 razy w porównaniu z opcjami 3-5. I nie można się z tego nie cieszyć, gdyż pokrywa się to z teorią – w tej wersji liczba operacji I/O do/z HBase jest dokładnie 2 razy większa. Może to służyć jako pośredni dowód na to, że nasze stanowisko testowe zasadniczo zapewnia dobrą dokładność.
Opcja 1 również, zgodnie z oczekiwaniami, okazuje się najwolniejsza i wykazuje liniowy wzrost czasu potrzebnego na wzajemne dodawanie się do rozmiaru sieci.
Przyjrzyjmy się teraz wynikom drugiego testu.
Cechy projektowania modelu danych dla NoSQL
Opcje 3-5 znów zachowują się zgodnie z oczekiwaniami - czas stały, niezależny od wielkości sieci. Opcje 1 i 2 wykazują liniowy wzrost w czasie wraz ze wzrostem rozmiaru sieci i podobną wydajnością. Co więcej, opcja 2 okazuje się nieco wolniejsza - najwyraźniej ze względu na konieczność sprawdzenia i przetworzenia dodatkowej kolumny „count”, która staje się bardziej zauważalna w miarę wzrostu n. Ale nadal będę się powstrzymywał od wyciągania jakichkolwiek wniosków, ponieważ trafność tego porównania jest stosunkowo niska. Dodatkowo te proporcje (która opcja 1 czy 2 jest szybsza) zmieniały się z biegu na bieg (przy zachowaniu charakteru zależności i „chodzenia łeb w łeb”).

Cóż, ostatni wykres jest wynikiem testów usuwania.

Cechy projektowania modelu danych dla NoSQL

Znów nie ma tu żadnych niespodzianek. Opcje 3-5 wykonują usuwanie w stałym czasie.
Co więcej, co ciekawe, opcje 4 i 5, w odróżnieniu od poprzednich scenariuszy, wykazują zauważalnie nieco gorszą wydajność niż opcja 3. Najwyraźniej operacja usuwania wierszy jest droższa niż operacja usuwania kolumn, co jest w sumie logiczne.

Warianty 1 i 2, zgodnie z oczekiwaniami, wykazują liniowy wzrost w czasie. Jednocześnie opcja 2 jest stale wolniejsza niż opcja 1 – ze względu na dodatkową operację we/wy w celu „utrzymania” kolumny zliczania.

Ogólne wnioski z eksperymentu:

  • Opcje 3-5 wykazują większą wydajność, ponieważ korzystają z HBase; Co więcej, ich wydajność różni się między sobą stałą i nie zależy od wielkości sieci.
  • Nie odnotowano różnicy pomiędzy opcjami 4 i 5. Nie oznacza to jednak, że nie należy stosować opcji 5. Jest prawdopodobne, że zastosowany scenariusz eksperymentalny, biorąc pod uwagę charakterystykę działania stanowiska badawczego, nie pozwolił na jego wykrycie.
  • Charakter wzrostu czasu potrzebnego na wykonanie „operacji gospodarczych” przy danych generalnie potwierdził uzyskane wcześniej obliczenia teoretyczne dla wszystkich wariantów.

Epilog

Przeprowadzonych zgrubnie eksperymentów nie należy traktować jako absolutnej prawdy. Istnieje wiele czynników, które nie zostały wzięte pod uwagę i zniekształciły wyniki (wahania te są szczególnie widoczne na wykresach przy małej wielkości sieci). Na przykład szybkość oszczędności, z której korzysta happybase, objętość i sposób implementacji logiki, którą napisałem w Pythonie (nie mogę twierdzić, że kod został napisany optymalnie i efektywnie wykorzystał możliwości wszystkich komponentów), być może funkcje buforowania HBase, aktywność systemu Windows 10 w tle na moim laptopie itp. Ogólnie rzecz biorąc, możemy założyć, że wszystkie obliczenia teoretyczne wykazały eksperymentalnie swoją ważność. No cóż, a przynajmniej nie można było ich odeprzeć takim „bezpośrednim atakiem”.

Podsumowując, zalecenia dla wszystkich, którzy dopiero zaczynają projektować modele danych w HBase: wyciągnij wnioski z wcześniejszych doświadczeń w pracy z relacyjnymi bazami danych i pamiętaj o „przykazaniach”:

  • Projektując wychodzimy od zadania i schematów manipulacji danymi, a nie od modelu dziedzinowego
  • Sprawny dostęp (bez pełnego skanowania tabeli) – tylko za pomocą klucza
  • Denormalizacja
  • Różne wiersze mogą zawierać różne kolumny
  • Dynamiczna kompozycja głośników

Źródło: www.habr.com

Dodaj komentarz