Artykuły | 26 listopad, 2019

Neo4j – zaproszenie do grafowych baz danych

Popularność grafowych baz danych ciągle rośnie. Jak jednak dokonać wyboru bazy danych i czy grafowe bazy sprawdzą się w każdym projekcie? W dzisiejszym artykule chciałbym przyjrzeć się zaletom grafowych baz danych i zachęcić możliwie jak najwięcej osób z branży programistycznej do korzystania z wszelkich dobrodziejstw, jakie mogą wnieść do codziennej pracy developera. Skupię się szczególnie na moim ulubionym reprezentancie tego gatunku, którym jest Neo4j.

Neo4j – zaproszenie do grafowych baz danych

Czym jest grafowa baza danych?

Neo4j (https://neo4j.com/) jest jedną z najpopularniejszych, o ile nie najpopularniejszą grafową bazą danych. Dla przypomnienia i uporządkowania wiedzy: graf jest kompozycją dwóch typów elementów, jakimi są węzły i relacje. Węzeł może reprezentować określony typ lub kilka typów i posiada swoje właściwości (ang. properties). Relacje zaś poza nazwą i własnymi właściwościami posiadają – co najistotniejsze – kierunek oddziaływania. Wspomniane właściwości są kolekcjami par klucz – wartość. Służą do przechowywania istotnych informacji. Dla przykładu: jeżeli węzłem będzie osoba, to jej właściwościami mogą być: imię, nazwisko, wiek albo lista ulubionych książek.

Relacje między węzłami w Neo4j – jak i w grafowych bazach danych w ogóle – są tak samo ważnymi danymi jak węzły. Traktujemy je jak obiekty, których istnienie jest determinowane przez obecność danych węzłów. Występowanie relacji samodzielne nie ma żadnego uzasadnienia.

Relacje znane z baz danych typu RDBMS (Relational Database Management System) zazwyczaj będą nam się kojarzyć z oznaczaniem danych w jakimś wierszu z jednej tabeli jako mających swoje odzwierciedlenie w konkretnym wierszu innej tabeli. Pozwoli nam to na operacje kaskadowe podczas edycji lub usuwania danych. Podczas normalizowania modelu danych w RDBMS możemy się zetknąć z koniecznością wprowadzenia specjalnych tabel pośredniczących między dwoma tabelami służącymi do wiązania ze sobą całych grup wierszy.

Czytaj także: Poznaj korzyści Test-Driven Development na co dzień

Wybór bazy danych

Wciąż jeszcze w wielu środowiskach panować może przeświadczenie, że RDBMS są najlepsze do wszelkiego rodzaju zadań. Osoby decyzyjne w zakresie doboru baz danych mogą nieprzychylnie spoglądać na alternatywne rozwiązania do przechowywania danych, a za taką zwykło się dotąd uważać właśnie Neo4j. Jak rozsądnie dokonać wyboru bazy lub baz danych do wykorzystania w projekcie? Przede wszystkim wybór powinien być zdeterminowany przez stojące przed aplikacją zadania.

Już na etapie projektowania aplikacji, jeszcze na etapie wyboru bazy danych, warto zastanowić się nad kilkoma kwestiami:

  • jakie operacje będą w przyszłości wykonywane na gromadzonych danych?
  • czy zadaniem aplikacji będzie zapisywanie i odczytywanie danych bez żadnych bardziej skomplikowanych operacji?
  • może najistotniejsze będą wzajemne relacje między danymi, a program powinien pomagać w ich analizie?

Wybór bazy uzależniony jest więc od tego, jaka jest podstawowa funkcja projektowanej aplikacji i charakter danych. Czy będą to dane osób zatrudnionych w konkretnej organizacji, współtworzących jej strukturę organizacyjną, a aplikacja, którą tworzysz, będzie służyć do zapewnienia sprawnego obiegu dokumentów między pracownikami? Może masz do opracowania wyszukiwarkę połączeń lotniczych? Albo aplikację, która zapewni wsparcie logistyczne dla firmy transportowej? A może dostałeś supertajne zlecenie dla rządowych służb specjalnych polegające na zaimplementowaniu systemu wspierającego zarządzanie siatką agentów i informatorów? W ostatnim przykładzie rzeczywiście fantazja nieco mnie poniosła, ale głównie dlatego, że możliwości, jakich dostarcza nam grafowa baza danych, są naprawdę ogromne.

Jednak nie zawsze przestrzega się zasady dopasowania bazy danych do celu aplikacji, a zwolennicy nowych i często bardziej odpowiednich rozwiązań muszą zmagać się ze sceptycyzmem managerów („Przecież nikt nie korzysta z tych rozwiązań”).

Zalety Neo4j

Jakie wielkie musi być zaskoczenie i zdumienie oponentów, gdy po wejściu na stronę główną projektu Neo4j ich oczom ukazują się liczne logotypy światowych marek czy instytucji rządowych albo badawczych. Od firm medycznych, przez instytuty naukowo-badawcze, po firmy finansowe, transportowe, telekomunikacyjne i wojskowe. Pełna obszerna lista oraz case studies dostępne są na stronie projektu. To smakowity kąsek dla wszystkich zajmujących się analizą i modelowaniem danych oraz architektów aplikacji.

Jak widać, popularność Neo4j i grafowych baz danych ciągle rośnie. W wielu rankingach, w tym w moim prywatnym, baza danych Neo4j jest jednym z liderów takich rozwiązań. Warto wspomnieć o tym, że twórcy Neo4j zadbali o przyjazne zorganizowanie Clusteringu i pracy w chmurze, a najczęściej stosowanym rozwiązaniem jest Neo4j pracujący na AWS – co również przemawia na jej korzyść.

Warto pamiętać, że jako narzędzie Neo4j jest mocno wspierane zarówno przez współczesne narzędzia do pisania kodu, jak i popularne frameworki, na przykład Spring Framework w projekcie „Spring Data Neo4j”. To, że grafowa baza danych jest tak intensywnie rozwijana, dobrze wróży na przyszłość.

Co to znaczy, że Neo4j jest bazą NoSQLową?

Tak jak bazy danych z grupy RDBMS korzystają z języka zapytań SQL, tak Neo4j korzysta z języka Cypher. W obu przypadkach są to języki deklaratywne. O ile składniowo Cypher jest w wielu aspektach podobny do SQL, o tyle jedną z najczęściej wskazywanych różnic jest użycie słowa kluczowego MATCH w miejsce SELECT.  Inną jest wykorzystanie – w znaczeniu dosłownym – strzałek relacji.

Cypher to język bardzo elastyczny pod względem możliwości budowania zapytań. Widać to dobrze choćby w takich przykładach jak ten, gdzie przeprowadzamy dopasowanie warunkowe. W SQL zawsze warunek umieścimy w klauzuli WHERE, zaś w Cypher dodatkowo może zostać zawarty już podczas deklaracji węzła. Możemy tworzyć interesujące i przydatne zapytania złożone z etapów przy wykorzystaniu klauzuli WITH.

Bardzo ważna, a często nawet kluczowa, jest czytelność zapytań, którą tu odnajdujemy. Godna uwagi jest również łatwość pisania zapytań dla osób, które mają zrozumienie pojęcia grafu i zetknęły się z pisownią zapytań w SQL.

Wyszukiwarka połączeń autobusowych – case study

Zaprezentowany przeze mnie niżej przykład aplikacji do rezerwacji biletów zaczerpnięty został z życia. Problem, z którym się mierzyłem, zaistniał kilka lat temu, głównie przez przywiązanie osób decyzyjnych do rozwiązań uznanych za sprawdzone i niechęci do rozeznania w gronie developerskim w poszukiwaniu nowych możliwości.

Projekt: aplikacja, która służy do rezerwacji biletów autobusowych.

Produkt końcowy: rezerwacja miejsca i zakup biletu. Z racji zbyt dużej złożoności tematu ograniczmy się jednak wyłącznie do wyszukiwarki połączeń.

Działanie aplikacji: zanim użytkownik zarezerwuje bilet, powinien najpierw wskazać przystanek, z którego zechce odjechać, oraz przystanek docelowy z listy dostępnych.

Modelowanie: w podejściu zgodnym z RDBMS do zamodelowania tego obszaru będziemy potrzebować przynajmniej trzech tabel. Pierwszą będzie rejestr przystanków, drugą – rejestr tras, na których zlokalizowane są przystanki. W trzeciej tabeli do konkretnych tras przypiszemy poszczególne przystanki.

Schemat tabel i relacji zgodny z RDBMS
Rysunek 1. Schemat tabel i relacji zgodny z RDBMS

Kłopotliwy RDBMS

Warto zwrócić uwagę na pewien typowy problem tabeli z odwzorowanymi relacjami pomiędzy poszczególnymi wierszami spajanych tabel. Występujące w niej wiersze zawierają komórki wypełnione nieczytelnymi liczbami należącymi zwykle do indeksowanych kluczy głównych tabel w relacji. Rozszyfrowywanie pochodzenia i znaczenia tych numerów może niekiedy wymagać sporego wysiłku. Tym większego, im bardziej złożona jest tabela. W naszym przykładzie taka spajająca tabela mogłaby wyglądać na przykład tak:

Tabela Neo4j
Tabela 1. Przykładowy fragment możliwej zawartości tabeli 'track_bus_stop’

Takie „twory” znajdziemy w relacjach typu „Many-to-Many”. Mogą być one szczególnie uciążliwe, gdy w projekcie jest sporo tabel, zdefiniowanych kluczy obcych i relacji. Są też problematyczne, gdy istnieje cała masa danych w skryptach potrzebnych do zasilania testowych instancji baz danych w celu integracyjnej weryfikacji poprawności implementacji. Sytuacja często kluczowa w developmencie. Nierzadko trzeba się solidnie napracować, żeby przygotować nowe rekordy danych z zachowaniem relacji do tabel pośredniczących. Dane w wierszach muszą być unikalne albo dopasowane do wzorca, zgodne z innymi danymi „nienullowymi” z co najmniej dziesięciu innych tabel. Może się zdarzyć tak, że w modelu danych zaczynają – np. przez nieuwagę – pojawiać się odwołania cykliczne. Wówczas bez wykonania operacji wyłączenia wszystkich ograniczeń na bazie, nie jest możliwe ani dalsze dodawanie nowych danych, ani nawet zaimportowanie do niej prawidłowych danych.

RDBMS pełne Joinów

Przejdźmy do pierwszego kroku przy użyciu wyszukiwarki połączeń. Będzie to wyszukiwanie przez pasażera wszystkich możliwych tras przypisanych do wybranego przystanku.

Neo4j schemat
Listing 1. Zapytanie w SQL do wyszukania nazw wszystkich tras, do których należy przykładowy przystanek „Pstrągowa”

Co interesującego właśnie się stało? Dane z trzech osobnych tabel na tym etapie scaliły się w zapytaniu w jedną i możemy wybrać z niej te wiersze, które zawierają szukany przystanek. Jeżeli jedną trasę określa nam N składających się na nią przystanków połączonych z nią nazwami tras, to odrzucamy wszystkie wiersze, w których przypisany przystanek jest inny niż wskazany przez nas.

Poniżej przykład tabeli wynikowej przed okrojeniem jej z wierszy o innej nazwie przystanku niż zadany:

Tabela przedstawiająca zbiór najważniejszych danych z tabel tras i przystanków zestawionych ze sobą za pośrednictwem tabeli 'track_bus_stop'
Tabela 2. Tabela przedstawiająca zbiór najważniejszych danych z tabel tras i przystanków zestawionych ze sobą za pośrednictwem tabeli 'track_bus_stop’

Po odrzuceniu wierszy, w których nazwa przystanku nie odpowiada wskazanej przez nas, otrzymujemy taki oto zbiór wierszy, który następnie należy odpowiednio przyciąć poprzez odrzucenie ewentualnych powtórzeń.

Neo4j grafika
Tabela 3. Zbiór unikalnych nazw tras, do których należy wskazany przystanek

RDBMS pełne podzapytań

Wyobraźmy sobie, że teraz nasz pasażer potrzebuje listy wszystkich przystanków, do których będzie mógł dojechać, wsiadając do autobusu na wskazanym przystanku. Poniżej przykładowe zapytanie będące odpowiedzią na oczekiwanie użytkownika aplikacji.

Neo4j listing
Listing 2. Przykładowe zapytanie zwracające listę unikalnych przystanków, do których można dojechać z przystanku X

W powyższym zapytaniu mamy do czynienia z koniecznością użycia dwóch podzapytań na rzecz jednej operacji projekcji. Występuje łącznie sześć operacji łączenia tabel przy użyciu polecenia JOIN. Złożoność tego zapytania jest jego niewątpliwą wadą. Inną wadą jest brak intuicyjności w konstrukcji powstałych w języku SQL w przypadku, gdy chcemy uzyskać wycinek zbioru danych badanej rzeczywistości. Wynikiem zapytania w naszym przykładzie będzie zbiór danych zawartych w tabeli poniżej.

Neo4j tabela
Tabela 4. Przykładowy zbiór wynikowy możliwych przystanków, do których użytkownik odjedzie z podanego przystanku początkowego

Zapytania z obu poprzednich listingów to dopiero wstęp do pozostałych operacji prowadzących do kupna biletu. Kolejne zapytania będą niekiedy jeszcze bardziej złożone. Szczególnie gdy utworzymy dodatkowe tabele do przechowywania informacji o cenach biletów uzależnionych od wybranego przystanku odjazdu oraz przystanku docelowego, rodzaju trasy, kursu, pory nocnej albo np. okresowych zniżek na danych odcinkach przejazdu.

Case study: użycie grafu Neo4j

Spójrzmy na analizowany model danych przez pryzmat obiektów grafu. Co to oznacza, że coś da się opisać przy pomocy grafu? Jak pisałem na początku: graf to zbiór węzłów i ich wzajemnych skierowanych relacji. W kontekście analizowanej wyszukiwarki połączeń autobusowych nasze przykładowe węzły i relacje możemy z wykorzystaniem Neo4j przedstawić w taki sposób:

Graf obrazujący przystanki jako węzły i ich wzajemne zdefiniowane relacje
Rysunek 2. Graf obrazujący przystanki jako węzły i ich wzajemne zdefiniowane relacje

Przystanki są węzłami, a droga, która prowadzi od jednego do drugiego przystanku, w sposób naturalny określa istniejącą między nimi relację. Droga pomiędzy przystankami spowoduje wystąpienie odpowiednich tras przejazdu, więc potraktujemy trasę jako właściwość relacji przez nią wyznaczoną.

Neo4j listing
Listing 3. Zapytanie w Cypher służące do wyszukania wszystkich tras, do których przypisany został wskazany przystanek

Przykładowy rezultat powyższego zapytania:

Neo4j tabela
Tabela 5. Przykładowy rezultat zapytania służącego do wyszukania wszystkich tras, do których przynależy wskazany przystanek

Zapytanie z listingu 3 już na pierwszy rzut oka jest o wiele bardziej intuicyjne, niż miało to miejsce w przypadku SQL. Na pewno jest też mniej skomplikowane. W powyższym zapytaniu staramy się wybrać wszystkie relacje typu LEADS TO  do innych przystanków, a wychodzące z takiego, którego nazwa odpowiada przystankowi wskazanemu w zapytaniu. Po czym zwracamy ich trasy przy użyciu słowa kluczowego RETURN.

Przejdźmy do kroku drugiego, który powinien wykonać użytkownik, by uzyskać listę możliwych przystanków, przez które będzie przejeżdżał autobus. W Neo4j uzyskamy ją, wykonując na przykład takie zapytanie jak na listingu 4.

Neo4j listing
Listing 4. Zapytanie zwracające nazwy wszystkich przystanków, do których możliwe będzie dotarcie z tego wskazanego przez użytkownika

Powyższe zapytanie możemy odczytać następująco: „Skoro między wskazanym przystankiem a kolejnymi istnieje relacja łączącej je drogi, to zwróć mi wszystkie kolejne aż do ostatniego”

Poniżej przykładowy rezultat w postaci grafu oraz w postaci tabeli nazw:

Graf prezentujący możliwe docelowe przystanki wraz z ich wzajemnymi relacjami
Rysunek 3. Graf prezentujący możliwe docelowe przystanki wraz z ich wzajemnymi relacjami
Zbiór nazw węzłów z powyższego zapytania o możliwe docelowe przystanki
Tabela 6. Zbiór nazw węzłów z powyższego zapytania o możliwe docelowe przystanki

Jak widzimy w powyższych przykładach, zapytania w Cypher są dużo krótsze niż ich odpowiedniki w SQL i zaprezentowane w odpowiednich listingach SQL. Jednocześnie pozwalają uzyskać identyczne efekty. Dodatkową zaletą jest to, że w Neo4j poza widokiem tabelarycznym dostępny jest również widok węzłów i ich relacji.

Podsumowanie

Jeżeli wycinek rzeczywistości, nad którym pracujemy, stanowi zbiór obiektów i ich wzajemnych relacji – bardzo prawdopodobne, że odnajdziemy tam strukturę grafową i użycie grafowej bazy danych będzie miało sens. W niniejszym tekście chciałem zaprezentować przede wszystkim intuicyjność i łatwość w konstruowaniu zapytań w Neo4j. Podałem przykład aplikacji, w której mierzyłem się z problemem niedopasowania narzędzia do potrzeb projektu. Gdybym wówczas posiadał obecne doświadczenie i wiedzę na temat grafowych baz danych, starałbym się przekonać decyzyjne osoby w projekcie do zastosowania Neo4j. Uchroniłoby to klienta przed wieloma niepotrzebnymi problemami i wydatkami.

Od ponad siedmiu lat java developer. Prywatnie miłośnik jazzu i piwa rzemieślniczego. Uważa, że w pracy developera najważniejsze są trzy rzeczy: dbałość o czysty kod, solidne testy jednostkowe i konsensus.

Zapisz się do newslettera, ekskluzywna zawartość czeka

Bądź na bieżąco z najnowszymi artykułami i wydarzeniami IT

Informacje dotyczące przetwarzania danych osobowych

Zapisz się do newslettera, ekskluzywna zawartość czeka

Bądź na bieżąco z najnowszymi artykułami i wydarzeniami IT

Informacje dotyczące przetwarzania danych osobowych

Zapisz się do newslettera, aby pobrać plik

Bądź na bieżąco z najnowszymi artykułami i wydarzeniami IT

Informacje dotyczące przetwarzania danych osobowych

Dziękujemy za zapis na newsletter — został ostatni krok do aktywacji

Potwierdź poprawność adresu e-mail klikając link wiadomości, która została do Ciebie wysłana w tej chwili.

 

Jeśli w czasie do 5 minut w Twojej skrzynce odbiorczej nie będzie wiadomości to sprawdź również folder *spam*.

Twój adres e-mail znajduje się już na liście odbiorców newslettera

Wystąpił nieoczekiwany błąd

Spróbuj ponownie za chwilę.

    Get notified about new articles

    Be a part of something more than just newsletter

    I hereby agree that Inetum Polska Sp. z o.o. shall process my personal data (hereinafter ‘personal data’), such as: my full name, e-mail address, telephone number and Skype ID/name for commercial purposes.

    I hereby agree that Inetum Polska Sp. z o.o. shall process my personal data (hereinafter ‘personal data’), such as: my full name, e-mail address and telephone number for marketing purposes.

    Read more

    Just one click away!

    We've sent you an email containing a confirmation link. Please open your inbox and finalize your subscription there to receive your e-book copy.

    Note: If you don't see that email in your inbox shortly, check your spam folder.