Elasticsearch to rozproszony silnik do przeszukiwania i analizy danych, oparty na bibliotece Apache Lucene (biblioteka do indeksowania i wyszukiwania, licencja Apache License 2.0). Z tego powodu jest często porównywany do podobnego rozwiązania, jakim jest Apache Solr.
Elasticsearch jest napisany w Javie, a jego pierwsza wersja ukazała się w roku 2010. Jej autorem był Shay Banon. Aktualnie rozwojem zajmuje się firma Elastic NV, a najnowszą stabilną wersją na dzień dzisiejszy jest 8.9.1, która została wydana 14 sierpnia 2023 roku. Rozwiązanie przybliżę w niniejszym artykule.
Możliwości
Elasticsearch to narzędzie, które oferuje szereg funkcji umożliwiających przeszukiwanie i analizę danych w sposób efektywny i wszechstronny. Poniżej kluczowe możliwości, jakie oferuje Elasticsearch:
- Zbieranie i analiza logów: Logi generowane przez aplikacje często są rozproszone w różnych częściach infrastruktury i zawierają dane w wielu formatach. Elasticsearch daje możliwość łatwego scentralizowania tych danych, a następnie ich analizy oraz wizualizacji.
- Wyszukiwanie pełnotekstowe: Elasticsearch oferuje wiele różnych rodzajów zapytań, takie jak match, match phrase, range, fuzzy, autocompletery i wiele innych.
- Enterprise search: Umożliwia tworzenie aplikacji do wyszukiwania danych na podstawie indeksów Elasticsearcha, ale dodatkowo – i to stanowi o potencjale tego rozwiązania – również z innych źródeł. Udostępnione są narzędzia, takie jak web crawler do indeksowania zawartości z HTML, konektory do popularnych baz danych i aplikacji (MySQL, PostgreSQL, MongoDB, Jira) oraz wiele innych connectorów do źródeł danych.
- Analiza w czasie rzeczywistym: Elasticsearch umożliwia analizę w czasie rzeczywistym danych zebranych z różnych źródeł, takich jak logi systemowe, metryki, dane ze zdalnych sensorów czy urządzeń IoT. Przetworzone dane można następnie prezentować w Kibanie lub tworzyć alerty.
- Analiza danych geoprzestrzennych: Elasticsearch umożliwia analizę danych geoprzestrzennych, przechowując dane geograficzne jako punkt (geo_point) lub kształt (geo_shape). W tym drugim przypadku mamy możliwość użycia wielu różnych typów, np. Point, Line, Polygon, kolekcje powyższych i inne. Dostępne są również różne zapytania geoprzestrzenne (geo queries), które pozwalają na wykonywanie operacji od pomiaru odległości między punktami po bardziej zaawansowane, takie jak sprawdzenie czy dwa obiekty typu geo_shape mają część wspólną. Takie dane możemy umieszczać jako warstwy na mapie i używać w dashboardach stworzonych w Kibanie.
Elasticsearch jako zbiór aplikacji
Elasticsearch jest tak naprawdę zbiorem aplikacji pod wspólną nazwą „Elastic Stack” (wcześniej „ELK stack”, skrót od „Elasticsearch, Logstash, Kibana”).
W skład Elastic Stack, oprócz samego Elastricsearch, wchodzą:
- Logstash – silniki do gromadzenia danych i analizowania logów.
- Kibana – narzędzie umożliwiające podgląd i wizualizację danych zawartych w Elastisearchu oraz monitorowanie i zarządzanie całym stackiem.
- Beats – zestaw narzędzi ułatwiających dodawanie danych do Elasticsearch. Przykładowo mamy tutaj:
- Filebeat – dodawania logów,
- Metricbeat – dodawanie metryk,
- Packetbeat – dodawanie danych sieciowych,
- Winlogbeat – dodawanie logów w eventami systemu windows,
- i inne.
Więcej informacji o Elastic Stack możecie znaleźć na stronie rozwiązania.
Elasticsearch udostępnia REST-owe API, dzięki którym możemy zarządzać klastrem, indeksować dokumenty i przeprowadzać operacje wyszukiwania i wiele innych. Szczegółowa lista jest dostępna tutaj. API domyślnie wystawione jest na porcie 9200.
OpenSearch
W roku 2021 licencja Elasticsearch została zmieniona z otwartej licencji Apache License na Server Side Public License. Licencja SSPL nie jest uznawana za w pełni otwartą przez wiele stron, w tym Open Source Initiative (OSI) oraz twórców wielu wersji systemu Linux. Z tego też powodu Amazon postanowił stworzyć otwartą wersję Elasticsearch, która powstała w 2021r jako fork wersji 7.10.2 i jest rozwijana pod licencją Apache License 2 przez AWS. Najnowsza stabilna wersja, dostępna na dzień dzisiejszy, to 2.9.0 z 24 lipca 2023 roku.
Znakomita większość funkcji związanych z wyszukiwaniem, analizą i prezentacją danych jest w obu rozwiązaniach identyczna. Oczywiście jest też kilka znaczących różnic. Ze względu na to, że Opensearch jest oprogramowaniem open source, ma bardziej liberalną licencję, ale nie oferuje oficjalnego wsparcia dla wdrożenia oprogramowania.
Elasticsearch posiada większą gamę bibliotek umożliwiających integrację z różnymi językami oprogramowania, podczas gdy dla Opensearch jest ona bardziej uboga i implementacja może być bardziej problematyczna. Najnowsze wersje narzędzi, takich jak Logstash czy Beats, nie działają z Opensearchem. W odpowiedzi na ten problem powstało narzędzie Data Prepper, niestety na ten moment nie oferuje ono jeszcze takich możliwości jak konkurencja.
Oba systemy oferują możliwość uwierzytelniania za pomocą LDAP, OpenID czy SAML. W Opensearch dostajemy to w standardzie, a w przypadku Elasticsearch tylko w płatnych licencjach. To samo tyczy się takich funkcjonalności jak IP filtering czy autoryzacja na poziomie dokumentów lub nawet na poziomie poszczególnych pól w dokumentach. Jeśli chodzi o wydajność, ciężko znaleźć wiarygodne benchmarki. Na stronach elastic.co można znaleźć wyniki testów przeprowadzonych przez nich samych, a poniższa grafika podsumowuje te wyniki:
Na stronie Elastic.co można znaleźć metodologię testów i informację o walidacji wyników przez niezależnych ekspertów. Jednak, biorąc pod uwagę, że wyniki zostały opublikowane na stronie jednej z zainteresowanych firm, warto podejść do nich z pewnym dystansem. Strona projektu Opensearch dostępna jest tutaj.
Dane
W Elasticsearch dane są reprezentowane w postaci dokumentów. W przeciwieństwie do przechowywania informacji w wierszach tabel, jak ma to miejsce w relacyjnych bazach danych, Elasticsearch przechowuje dane jako złożone struktury w formie zserializowanych dokumentów JSON. Jeśli w klastrze znajduje się wiele węzłów Elasticsearch (a taka jest idea systemu rozproszonego!), dokumenty są rozproszone po całym klastrze, co umożliwia natychmiastowy dostęp do nich z dowolnego węzła.
Zapisany dokument zostaje zindeksowany (dodany) i jest dostępny do wyszukiwania niemal w czasie rzeczywistym. Podczas procesu indeksowania każdy dokument otrzymuje swój unikatowy identyfikator. Elasticsearch chwali się, że proces trwa około sekundy, co jest bardzo prawdopodobne.
Dokumenty są organizowane w indeksy, które stanowią logiczne kontenery dla dokumentów o podobnej strukturze. Możemy o nim myśleć jak o odpowiedniku tabeli w klasycznej bazie danych.
Skalowalność systemu i bezpieczeństwo danych
Elasticsearch został stworzony z myślą o skalowalności i niezawodności. Aby zwiększyć dostępne zasoby, wystarczy dodać kolejny węzeł (node) do klastra. Elasticsearch automatycznie rozdzieli obciążenie danymi i zapytaniami pomiędzy wszystkie dostępne węzły.
Indeks w Elasticsearch jest właściwie elementem logicznym. Każdy indeks jest podzielony na shardy, czyli fizyczne fragmenty danych. Shardy umożliwiają równoległe przetwarzanie i przechowywanie danych. Przy tworzeniu indeksu można określić liczbę shardów, które zostaną użyte, co wpływa na równomierność obciążenia i wydajność. Najczęściej liczbę shardów ustawia się na wartość równą ilości nodów. Dzięki temu dane zostaną równomiernie rozproszone pomiędzy wszystkie dostępne węzły.
Ale co nam to daje? Załóżmy, że nasz indeks zawiera 1 mln rekordów. Gdy umieścimy je w jednym shardzie (na jednym nodzie), to wyszukanie interesującego nas rekordu zajmuje 10 sekund. Teraz załóżmy, że tworzymy 10 węzłów i dzielimy indeks na 10 shardów. Wówczas możemy wyszukiwać na każdym shardzie równocześnie. Teoretycznie czas wyszukiwania skróci się do 1 sekundy. Oczywiście w praktyce trzeba dodać niewielki narzut związany z koordynacją zapytań i komunikacją między nodami, ale i tak przyrost prędkości jest ogromny.
Algorytm deterministyczny
Elasticsearch na podstawie identyfikatora dokumentu sam routuje go do odpowiedniego shardu i jest to oczywiście algorytm deterministyczny. Sam algorytm wygląda następująco:
shard_num = (hash(_routing) % num_routing_shards) / routing_factor
Gdzie:
routing_factor = num_routing_shards / num_primary_shards
num_routing_shards i num_primary_shards
to wartości ustawione w konfiguracji indeksu. A _routing
to właśnie identyfikator dokumentu w Elasticsearch. Podczas wysyłania dokumentu do Elasticsearch, istnieje możliwość nadania własnego klucza routingu w celu wymuszenia umieszczenia dokumentu w konkretnym shardzie. Jednak wtedy podczas każdej innej operacji na dokumencie (get, delete, update) trzeba w zapytaniu wskazać ten sam klucz routingu.
Czasem coś pójdzie nie tak jak powinno i któryś z nodów (a nawet kilka z nich) ulega awarii, prowadząc do utraty danych z shardu, jaki się nie nim znajdował. Oczywiście, reszta klastra nadal działa normalnie i dane na pozostałych węzłach pozostają dostępne. Jednak, co dzieje się z danymi na węźle, który uległ awarii?
- Elasticsearch natywnie obsługuję replikację shardów, co oznacza, że shardy są kopiowane.
- Każdy z głównych shardów (primary shard) ma utworzoną swoją replikę (replica shard) na innym nodzie.
- Domyślnie tworzona jest jedna replika, ale można to oczywiście konfigurować pod swoje własne potrzeby.
- W momencie, gdy primary shard jest niedostępny, jego rolę przejmuje replika.
Z mechanizmu replikowania bezpośrednio wynika jeszcze jedna zaleta. Zapytania do Elasticsearch mogą być wykonywane jednocześnie na wszystkich replikach, co dodatkowo zwiększa wydajność. Aby zapewnić spójność danych, wszelkie akcje, które modyfikują dane, są zawsze kierowane do głównego sharda. Tam następuje weryfikacja poprawności danych, a następnie zapis do głównego sharda. Następnie operacja zapisu jest propagowana do replik, które wysyłają potwierdzenie do głównego sharda.
Poniższy diagram prezentuje przykładową konfigurację dla klastra z 4 nodami, 4 głównymi shardami i 3 replikami per shard:
Snapshoty
Elasticsearch oferuje wbudowany system backupów w postaci snapshotów. Snapshoty są przechowywane poza lokalizacją klastra, a Elasticsearch pozwala wykorzystywać do tego celu popularne rozwiązania chmurowe, takie jak AWS S3, Google Clooud Storage, Azure i inne.
Oprócz roli backup/restore oraz możliwości przenoszenia danych między klastrami za pomocą snapshotów, Elasticsearch oferuje dodatkowo mechanizm „searchable snapshots”. Jak wynika z samej nazwy, ten mechanizm służy do przeszukiwania danych. Mechanizm ten ma zastosowanie tylko dla danych, które są rzadko używane i pozwala na zmniejszenie miejsca zajmowanego przez dane do 50%.
Należy zaznaczyć, że Elasticsearch udostępnia searchable snapshots w płatnych licencjach podczas gdy w Opensearch jest to w pełni darmowa opcja.
Interakcje
Jak już wspomniałem, Elasticsearch udostępnia nam REST-owe API. Spróbujmy zatem utworzyć pierwszy indeks o nazwie accounts. Używamy metody PUT i odpowiedniego URL. Wyglądałoby to tak:
curl -X PUT "localhost:9200/accounts?pretty"
W URL podajemy nazwę indeksu oraz dodatkowy parametr pretty, aby uzyskać odpowiedź w bardziej czytelnej formie. W odpowiedzi dostaniemy:
{
"acknowledged" : true,
"shards_acknowledged" : true,
"index" : "accounts"
}
W praktyce rzadko kiedy używa się polecenia curl. Dla komunikacji typu M2M mamy dostępnych wielu klientów Elasticsearch, takich jak Java, JS, PHP, Go, itp. Więcej informacji na ten temat możnecie znaleźć tutaj.
Dla developera najprostszą metodą komunikacji z API Elasticsearch jest użycie jakiegoś klienta, np. Postmana lub Kibany. Kibana, wśród wielu narzędzi, oferuje Dev Tools-y, a w nich konsolę do tworzenia requestów i odczytywania responsów. Konsola oferuje podpowiedzi i kolorowanie składni, formatowanie kodu i inne udogodnienia. Przykładowe zapytanie tworzące indeks może wyglądać tak:
Dodajmy teraz pierwszy dokument do indeksu.
Operacja się powiodła, dokument otrzymał identyfikator. Uwagę może zwrócić jedna rzecz. W naszym requeście użyliśmy danych o różnych typach, mamy zmienne tekstowe, liczbowe czy datę. Nawet pojawił się prosty obiekt. Elasticsearch działa w trybie bez schematu (ang. Schema-less), co oznacza, że może indeksować dokumenty bez wyraźnego wskazania typu dla poszczególnych pól.
Dla nas jest to bardzo wygodne, bo umożliwia szybkie dodawanie danych bez dodatkowego narzutu pracy. Oczywiście mamy możliwość utworzenia schematu danych (mapping) przed dodaniem dokumentu, co może być konieczne w kilku przypadkach. Na przykład, gdy chcemy wymusić konkretny tryb wyszukiwania dla danego pola (full-text albo exact value), bądź gdy potrzebujemy użyć partial matching, wykonać analizę tekstu w sposób specyficzny dla danego języka, itp.
Do już istniejącego mapowania możemy dodawać kolejne pola, zarówno automatycznie, jak i ręcznie. Nie możemy jednak zmienić typu dla już zmapowanego pola. W takim wypadku należy usunąć indeks, stworzyć nowe mapowanie i następnie raz jeszcze zaindeksować wszystkie dane.
Sprawdźmy, jakie mapowanie stworzył Elasticsearch dla naszych danych:
Jak widać, Elasticsearch poradził sobie bardzo dobrze. Rozpoznał typy i odpowiednio ustawił mapowania dla pól tekstowych, liczb całkowitych, zmiennoprzecinkowych i logicznych.
Full-text search
Elasticsearch potrafi szybko przeprowadzić wyszukiwania full-text. Aby zrozumieć, dlaczego tak się dzieje, warto przyjrzeć się procesowi indeksowania dokumentu.
Tekst wprowadzany do Elasticsearcha jest poddawany analizie, która jest przeprowadzana przez tzw. analyzer. Istnieje wiele wbudowanych analyzerów, można oczywiście definiować swoje, bardziej dopasowane pod indywidualne potrzeby. Analyzer to tak naprawdę zestaw reguł, które zostaną użyte w procesie analizy tekstu. Zazwyczaj składa się on z trzech bloków:
- Character filter – może wykonywać proste operacje, takie jak usuwanie fragmentów tekstu (np. znaczników html), zamiany jednych znaków na drugie, bądź zamiany ciągów znaków pasujących do określonego wyrażenia regularnego. Analyzer może zawierać zero lub wiele character filterów, które zostaną zastosowane w deklarowanej kolejności. Więcej informacji tutaj.
- Tokenizer – dzieli podany tekst na tokeny, zazwyczaj pojedyncze słowa. Elasticsearch posiada wiele wbudowanych tokenizerów, przy czym najczęściej używanym jest Standard Tokenizer który dzieli tekst po znakach whitespace oraz usuwa znaki przestankowe. Analyzer musi mieć dokładnie jeden tokenizer. Więcej informacji o tokenizerach tutaj.
- Token filter – wykonuje modyfikacje na otrzymanych tokenach, może je usuwać, dodawać bądź modyfikować.
Tak naprawdę, to tutaj dzieje się duża część „magii” Elasticsearcha. Oto kilka przykładowych token filtrów:
- Lowercase token filter – prosta zmiana wielkich liter na małe.
- Stop token filter – usuwa tokeny (słowa), które są bez znaczenia dla procesu wyszukiwania. Elasticsearch posiada wbudowane listy stop words dla kilku języków. Oczywiście, użytkownik może samodzielnie zdefiniować listę stop words. W przypadku języka angielskiego, lista wbudowanych stop words wygląda następująco: a, an, and, are, as, at, be, but, by, for, if, in, into, is, it, no, not, of, on, or, such, that, the, their, then, there, these, they, this, to, was, will, with.
- Synonym token filter – umożliwia dodanie synonimów dla tokenów i adresuje w ten sposób kilka różnych problemów, takich jak:
- skróty: km = kilometr;
- różnice w zapisie: i-pod = ipod = i pod;
- synonimy: zmęczony = senny;
- british vs american: lift = elevator.
- Stemmer token filter – doprowadza słowa do podstawowej formy, np. lisy, lisica, lisek sprowadzi do słowa lis.
Analyzer może nie posiadać żadnego token filter, bądź posiadać ich wiele. Wtedy zostaną użyte w zadeklarowanej kolejności.
Więcej informacji o token filters można znaleźć tutaj.
Po dodatkowe informacje na temat analizy tekstu i samych analyzerów odsyłam tutaj.
Inverted index
Gdy mamy już wszystkie tokeny w docelowej postaci, zostają one zapisane jako inverted index. Inverted index przechowuje tokeny oraz miejsce (identyfikator dokumentu), w którym tokeny wystąpiły. Najprościej wytłumaczyć to na przykładzie, więc weźmy pod uwagę dwa dokumenty:
doc1
„Ala ma kota”
doc2
„A sierotka ma rysia i kota”
Załóżmy że po analizie tokeny wyglądają następująco:
doc1
„ala, ma, kot”
doc2
„sierotka, ma, ryś, kot”
Odwrócony index dla powyższych dokumentów mógłby wyglądać następująco:
token | dokument |
ala | doc1 |
ma | doc1, doc2 |
kot | doc1, doc2 |
sierotka | doc2 |
ryś | doc2 |
Widać teraz, dlaczego jesteśmy w stanie tak szybko znaleźć dokument zawierający interesujące nas słowo. Jest to oczywiście mocno uproszczony przykład, ale dobrze oddaje ideę.
Podsumowanie
Elasticsearch to bardzo potężne narzędzie, które nie ogranicza się tylko szybkiego do wyszukiwania danych. Potrafi poddawać dane analizie (szczególnie pamiętajmy o danych tekstowych) i prezentować rezultaty dzięki wbudowanym narzędziom. Jest łatwy w rozbudowie, zarówno jeżeli chodzi o zasoby systemowe, jak i integracje z innymi systemami.
Warto pamiętać, że Elasticsearch wymaga wiedzy i umiejętności w zakresie konfiguracji oraz zarządzania, zwłaszcza w środowiskach produkcyjnych. Dlatego ważne jest, aby poznać najlepsze praktyki dotyczące bezpieczeństwa, optymalizacji i skalowania, żeby wykorzystać pełny potencjał tej platformy.
Zostaw komentarz