Czego nie zrobi NoSQL, czyli pierwsze doświadczenia z Google Cloud Datastore
Jakub Kluczewski
Spis treści
Baza NoSQL dla aplikacji – porady dotyczące wykorzystania Google Cloud Datastore
Kilka lat temu w itCrafcie stanęliśmy przed wyzwaniem ewolucji naszych schematów budowania architektury backendowej w taki sposób, aby potrafiły obsłużyć nie tylko klasyczne, “konserwatywne” systemy klasy enterprise, ale udźwignęły również aplikacje mobilne o potencjale wielu tysięcy użytkowników.
Wybór typu bazy danych, która będzie działała za kulisami systemu jest często kluczowy i zła decyzja może zaważyć o sukcesie całego projektu. Coraz częstsze decyzje o wykorzystaniu nierelacyjnych rozwiązań poskutkowały wykształceniem się podziału naszych wewnętrznych specjalizacji i powstaniem gałęzi NoSQLowej w technologiach Cloudowych.
Wejście w tę architekturę bywa problematyczne dla programisty przyzwyczajonego do klasycznych, relacyjnych systemów bazodanowych, dlatego sięgniemy pamięcią do początków i przedstawimy kilka praktycznych porad ułatwiających pierwsze kroki na przykładzie bazy Google Cloud Datastore. Cloud Datastore od Google to wysoce skalowalna baza danych NoSQL dla twoich aplikacji.
Not Only SQL
SQL-owy interfejs zapytań do bazy Cloud Datastore jest przydatny i naturalny, bo nie trzeba znać jej architektury, żeby pisać proste zapytania i widzieć wyniki których się spodziewamy.
Bywa to jednak mylące, bo pierwsze czytanie dokumentacji i natrafienie na zdanie, że zapytania piszemy językiem bardzo podobnym do klasycznego SQLa może sprawić, że programista założy, że jego kod niczym się nie będzie różnił od dotychczasowych rozwiązań, a on jedynie skorzysta z dobrodziejstwa znajdującego się “pod spodem”.
Taki pomysł niestety sprowadza developera na drogę bardzo wyboistą, a momentami wręcz brutalną.
Zapomnij o JOINach
“Aplikacja mobilna wyświetla listę artykułów, każdy jest opatrzony imieniem autora i jego zdjęciem profilowym.”
W przypadku rozwiązania relacyjnego programista natychmiast oczami wyobraźni widzi zapytanie do tabeli artykuły używając złączenia join tabeli autorzy. Programista wywołujący swój pierwszy kod w Cloud Datastore być może nawet uruchomi na niej dokładnie takie, klasyczne zapytanie. Kod nie wykona się.
Cloud Datastore firmy Google jest bazą dokumentową w której encje zapisane są w indeksach. Pojedyncze zapytanie sprowadza się do przejrzenia odpowiedniego zakresu zbudowanego wcześniej indeksu encji. Nie ma zatem możliwości aby ten jeden proces jednocześnie odwołał się do danych różnego typu.
Skalowalność przychodzi z ograniczeniami
Ceną za masywną skalowalność operacji odczytu z bazy jest ograniczenie się jedynie do zapytań, które skalują się względem wielkości zwracanego zbioru. Nie wykonamy na niej wspomnianych złączeń, nie zadziała kod w którym filtrujemy nierównością na więcej niż jednym z pól, niemożliwe jest wreszcie filtrowanie z użyciem podzapytań.
Co w takim razie otrzymamy zgadzając się, jak się wydaje na pierwszy rzut oka, na tak potężne ograniczenia?
Masywna skalowalność zapytań oznacza, że na performance wpływ ma wielkość odpowiedzi a nie całkowita ilość danych zapisanych w bazie. Jeśli na jednej stronie wyświetlimy kilkadziesiąt artykułów z naszego przykładu, to czas wykonywania zapytania będzie taki sam niezależnie od tego czy w tabeli zapisanych jest kilka tysięcy, czy kilka milionów artykułów.
Paginacja – im dalej w las…
Typową realizacją stronicowania zapytań jest wykorzystanie z limitu i offsetu. O ile obydwa polecenia są wspierane przez Cloud Datastore od Google, o tyle użycie offsetu w kontekście paginacji jest złym pomysłem.
Tak zbudowane przeglądanie kolejnych stron wyników będzie zwracało poprawne wyniki w rozsądnym czasie, ale szybko okaże się, że wykonywanie kodu zwalnia z każdą kolejną stroną. Architektoniczny szczegół który powoduje takie zachowanie bazy wynika z fizycznej struktury ułożenia w niej danych. Żądanie wyników zaczynających się od setnej pozycji przy użyciu polecenia offset wymusza przejrzenie indeksu od jego początku, a nie jak można by sobie życzyć z pominięciem pierwszych stu elementów.
W przypadku zbiorów wielkości tysięcy czy milionów encji konieczność każdorazowego odczytywania danych od początku tabeli wyklucza możliwość stworzenia takiej paginacji.
Z pomocą przychodzi technologia kursorów, czyli wskaźników na miejsce w indeksie w którym zakończone zostało poprzednie zapytanie. Utrudnia to budowę klasycznej stronicowanej tabeli, pełnej numerów stron, liczników i możliwości wyświetlenia od razu dowolnej strony, ale w zupełności wystarcza do większości typowych w procesie tworzenia aplikacji mobilnych list dociąganych obiektów.
– “Możemy użyć JPA?
– Świetny pomysł!”
To nie był świetny pomysł…
Jedną z pierwszych pokus programisty wkraczającego na nieznane tereny jest zwykle wprowadzenie nieznanych elementów w znajome, utarte mechanizmy.
Przykładem jak bardzo taki pomysł może pochłonąć czas i utrudnić życie jest próba wtłoczenia NoSQLowej bazy danych do frameworków typowo relacyjnych. To, że jest to możliwe i nawet znajdziemy enigmatyczny opis tego procesu w samej dokumentacji produktu nie oznacza niestety, że poświęcone środki zaowocują jakąś szczególną korzyścią.
Konfiguracja obwarowana jest najczęściej stosem ograniczeń i zapisów mówiących co nie będzie działało, lub zadziała inaczej niż się spodziewamy. Otrzymany w ten sposób “standard” jest małym potworem, który pomimo tego że na pierwszy rzut oka wygląda znajomo, przy bliższym poznaniu pokazuje zęby.
Kilka przykładów które opisałem nie wyczerpuje tematu różnic pomiędzy Datastorem w chmurze od Googla a bardziej klasycznym, relacyjnym podejściem. Tu, podobnie jak w przypadku każdej nieznajomej technologii w którą wchodzi programista słowo pisane nie zastąpi napisanego (własnoręcznie) kodu, ale znajomość podstawowych zasad obniża próg wejścia i przyspiesza naukę.
Doświadczenie ubiegłych lat pokazało nam w itCrafcie, że początki poznawania tej gałęzi backendowych technologii bywają żmudne. Warto zatem uprzedzić zdziwienie programisty i zatrzymać go przed wyważaniem otwartych drzwi w kontekście opisanych porad.