Korzystanie z API Elasticsearch

Nasze doświadczenie mówi nam że największym problemem z rozpoczęciu pracy z Elasticsearch i Kibany jest zrozumienie jak załadować dane do Elasticsearch z zewnętrznych źródeł. Produkty firmy Elastic zwane są często w skórcie "stosem ELK" - od słów Elasticsearch, Logstash, Kibana. Elasticsearch to baza danych, silnik. Kibana to interfejs do zarządzania bazą, ustawieniami i wizualizowania danych. Logstash natomiast to narzędzie do ładowania danych do Elasticsearch. Narzędzie to potrafi czytać dane z wielu różnych źródeł i formatów; plików, baz danych, strumieni, zasobów internetowych.... około 40 formatów.

Jak rozpocząć przygodę z tym fantastycznym zestawem narzędzi jakim jest ELK opisaliśmy na blogu, w artykule "Elasticsearch w 15 minut". Tamten artykuł posiada rozdział "Ładujemy własne dane do Elasticsearch" który instruuje jak wgrać dane z pliku do Elasticsearch używając programu Logstash.

Gdybyśmy jednak skorzystać z innej formy wgrania danych do Elasticsearch, np. zintegrować Elasticsearch z aplikacją biznesową, mamy do dyspozycji REST API. Elastic oferuje szereg API które realują różne funkcje. Pełny spis interfejsów znajdziecie tutaj. Ponieważ interesuje nas ładowanie danych, skupimy się na API (kiliku API) które nazywają się Document API.

Komunikacja z API

Testowo, na potrzeby przećwiczenia komunikacji z API Elasticseach którą tu opisujemy możesz użyć konsoli Kibany (opcja Dev Tools) - składnia zapytań w treści tego materiału będzie w formacie która możesz wkleić właśnie w okno Dev Tools. Ale możesz użyć także CURL, przykłady w dokumentacji mają link 'Copy as cURL' gdzie uzyskasz gotowy kod dla tego właśnie narzędzia. My preferujemy ETL, np Pentaho, w ktorym to w wygodnym GUI stworzysz łatwo skrypty do ładowania testowych i prawdziwych danych.

Z tego artykułu dowiesz się czegoś więcej niż tylko jak połączyć się z API - wyjaśnimy jak skonfigurować Elasticsearch by dane przechowywane były we właściwym formacie (istotne dla sprawnego wyszukiwania treści w dokumentach).

Tworzenie indeksu w Elastsicsearch

Dla osbób które zaczynają swoją przygodę z Elasticsearch, a tacy będą pewnie głownie zainteresowani tym materiałem, warto wyjaśnić czym jest 'indeks'. Indeksy, największa jednostka danych w Elasticsearch, są logicznymi partycjami dokumentów i można je porównać do bazy danych w świecie relacyjnych baz danych.
Możesz mieć np. jeden indeks zawierający wszystkie dane związane z produktami, a drugi zawierający wszystkie dane dotyczące klientów. Możesz mieć dowolną liczbę zdefiniowanych indeksów w Elasticsearch. Te z kolei będą zawierać dokumenty, które są unikalne dla każdego indeksu. Ważne; indeksy są identyfikowane małymi literami. Indeks tworzymy poleceniem:

PUT /test_index

Jak widzisz, użyta metoda to PUT a url to "/test_index". Gdybyś wykonywał to polecenie np. za pomocą CURL url wyglądałby: "http://adres_hosta:port/test_index. Tworzenie indeksu przed załadowaniem danych nie jest konieczne. Zapytanie które tworzy dokumenty, odpowiednik wierszy w relacyjnej bazie danych... czy po prostu wierszy z pliku, jest w stanie stworzyć nowy indeks jeśli taki jeszcze nie istnieje.

Tworzenie dokumentów

Dokument w Elasticsearch to odpowiednik wiersza/rekordu w relacyjnej bazie danych - czy nawet wyobraź sobie wiersz w pliku Excela czy CSV. Dokumenty w Elasticsarch to obiekty JSON, które są przechowywane w indeksie i są uważane za podstawową jednostkę przechowywania. Dane w dokumentach są definiowane za pomocą pól zawierających klucze i wartości. Klucz to nazwa pola, a wartość może być elementem wielu różnych typów, takim jak ciąg znaków, liczba, wyrażenie boolowskie, inny obiekt lub tablica wartości.

Warto wspomnieć też że przed wersją 7 w Elasticsearch mieliśmy pojęcie "typów". Typy określały klasy dokumentów mających wspólne właściwości pól. Np. klasa "klient" mogła określać ze dokumenty w tej klasie z nazwą pola "nazwa" miały typ pola "keyword" a pole "numer klienta" to typ danych "integer". W obecnych wersjach Elasticsearch typ dokumentów został wyeliminowany - dla kompatybilności wstecz w każdym indeksie istnieje tylko jeden typ (nazywa sie '_doc').

Zatem Elasticsearch domyślnie nie będzie wymagać od Ciebie stworzenia mapowania pól dokumentu do określonych typów danych. Nie musisz definiować ze pole "nazwa klienta" będzie np. typem "text". Elasticsearch zrobi takie mapowanie za Ciebie. I oczywiście nie zawsze zrobi to dobrze (a niby dlaczego miałby?). Istnieje jednak sposób by wymusić takie mapowanie dla nowo tworzonych indeksów i pól w dokumentach tych indeksów. By wskazać Elasticearch jak mają być przechowywane dane - musisz zrobić mapowanie ręcznie.

Mapowanie ("mapping") to proces definiowania sposobu przechowywania i indeksowania dokumentu i zawartych w nim pól. Na przykład użyj mapowań, aby zdefiniować:

  • które pola tekstowe powinny być traktowane jako pola pełnotekstowe.
  • które pola zawierają liczby, daty lub geolokalizacje.
  • format wartości dat.
  • reguły niestandardowe do kontrolowania mapowania dynamicznie dodawanych pól.

Jak napisane wyżej. Możesz pominąć mapowanie na etapie tych ćwiczeń - Elasticsearch i tak przyjmie od Ciebie dane. Jeśli jednak zależy Ci na właściwej strukturze bazy danych, kontunuuj. Kontunuujesz? OK - wejdź do właściwego menu Kibany (pamiętasz, ustawień ES dokonujemy za pomocą Kibany):

Spaces => manage spaces => index management

 

Mapowanie jest częścią szablonów ("templates"). Masz z pewnością wyobrażenie do to jest "szablon" i tak samo działa to w ES. Mapowania danego szablonu mogą być aplikowane na dane ładowane do jednego lub wielu szablonów. Mówi o tym "index pattern(s)" danego szablonu. Jeśli np. "index pattern" szablonu wskazuje na "test*", oznacza to że ustwienia w nim zawarte będą odnosić się do indeksów "test_index", "test_index2" czy "test2" i podobnie. Czyli kiedy będziesz ładować dane do któregoś z wymienionych indeksów; dane będą formatowane wg wskazań w tym szablonie.

Mapowanie dla danego indeksu, zawarte w szablonie, będzie działać jeśli utworzysz je przed załozeniem indeksu i wgraniem danych. Szablon i mapowanie nie zadziała na utworzone już indeksy i wgrane dane (nazywane "dokumentami" w ES).

OK, w menu jak wyżej znajdziesz zakładkę "Index templates" i zakładkę "component templates". Same "index templates" dzielą się na dwie kategorie. "Legacy index templates" to stara forma templates, ciągle wspierana przez ES zla wstecznej kompatybilności. Nie tworz tych szablonów w ES wersji 7.9 i wyżej.

"Index template" może składać się z wielu "component templates". "Component template" to blok "index template". To oznacza że "szablon indeksu" to kontener z który zawiera "szablony komonentów". Same "component templates" nie mogą funkcjonować samodzielnie (nie mogą formatować danych przy ich ładowania do indeksów) - taką funkcje wykonuje szablon indeksu którego częściami mogą być "component templates" (szablon indeksu może zawierać jeden lub wiele szablonów komponentów). Może jednak nie mieć ich wcale. Component template to coś jak rola w zarządzaniu uprawnieniami - jeden użytkownik może mieć jedna lub wiele ról. A wiec w szablonie komponentu możesz np. ustawić mapowanie pol X, Y, Z do odpowiedniego typu danych i jeśli będziesz chcieć zastosować takie same mapowanie pól w innym indeksie, używasz tych gotowych ustawień zawartych w szablonie komponentu - ten sam component template może być użyty w wielu szablonach indeksu.

Zarówno szablon indeksu jak i szablon komponentu można utworzyć w wygodnym wizzardzie. Oczywiście istnieje też możliwosć wykonania tej operacji za pomocą konsoli lub wydania zdalnie polecenia, np z użyciem CURL. Bardziej zaawansowane ustawienia wymagają parametrów w formacie JSON (część kroków wizzarda). Wizzard daje ci np. możliwość definicji "index pattern(s)", czyli określenia na które indeksy bedą propagowane mapowania, i określenie do jakich typów pól bedą mapowane pola ladowanego dokumentu.

 

Tworzenie dokumentu

Pamiętasz, "dokumenty" w ES to odpowiednik wierszy w relacyjnej badzie danych czy pliku. Dokument sklada się z pól. REST API Eelasticserach akceptuje nowe dokumenty w formacie JSON. Aby załadować dokument do ES używając API wykonaj polecenie:

POST test_index/_doc/
{
  "ip_address": "234.10.23.1",
  "nazwisko": "Grzegorz Brzęczyszczykiewicz",
  "numer_klienta": 2381
}

Jeśli "test_indeks" nie istnieje, zostanie utworzony. ID dokumentu zostanie utworzone automatycznie przez ES. Jeśli chcesz sam(a) decydować o ID dokumentów, uwzględnij go w URI zapytania:

POST test_index/_doc/
{
  "ip_address": "234.10.23.1",
  "nazwisko": "Grzegorz Brzęczyszczykiewicz",
  "numer_klienta": 2381
}

Ten sam dokument został utworzony pod ID = 22. ES po pomyślnym utworzeniu dokuentu zwraca:

{
  "_index" : "test_index",
  "_type" : "_doc",
  "_id" : "HZMjWnUBcGLv9iq-LEZI",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 1,
  "_primary_term" : 1
}

Tworzenie dokumentów masowo

Bulk API wykonuje wiele operacji indeksowania lub usuwania w jednym wywołaniu interfejsu API. Zmniejsza to obciążenie i może znacznie zwiększyć prędkość indeksowania. By wysłac wiele dokumentów w jednym zapytaniu (do jednego lub wielu różnych indeksów) przygotowujesz plik JSON w formacie:

{ "index" : { "_index" : "test", "_id" : "1" } }
{ "field1" : "value1" }
{ "delete" : { "_index" : "test", "_id" : "2" } }
{ "create" : { "_index" : "test", "_id" : "3" } }
{ "field1" : "value3" }
{ "update" : {"_id" : "1", "_index" : "test"} }
{ "doc" : {"field2" : "value2"} }

Zauważ ze powyższy plik zawiera inforację o operacji do wykonania oraz wskazuje na nazwę indeksu i ID dokumentu. Cześciej jednak bedziesz chcieć wykonać ładowanie, aktualizacje lub kasowanie dokumentów w obrębie jednego indeksu. Wtedy nazwa operacj nie jest potrzebna - nie musisz też wskazywać nazwy indeksu i ID (jeśli pozostawiasz zarządzanie ID dla ES). W najprostszym przypadku takie zapytanie wyglądałoby następująco:

POST test_index/_bulk/
 { "index" : { } }
 { "ip_address": "234.10.23.1", "nazwisko": "Grzegorz Brzęczyszczykiewicz", "numer_klienta": 2381 }
{
"index" : { } } { "ip_address": "235.10.12.15", "nazwisko": "Marian Paździoch", "numer_klienta": 9383 }
..

Każa linia JSON musi zaczynać się nową linią (\n po każdej linii). Inaczej ES zgłosi błąd formatowania JSON.

Pobieranie dokumentów

Po prostu wykonaj poniższe zapytanie podając ID dokumentu. ES zwroci pola dokumentu w formacie JSON.

GET test_index/_doc/22

Aktualizacja i kasowanie

Operacje aktualizowania i kasowania dobrze opisano w dokumentacji REST API [aktualizacja, kasowanie].

Reindex

ES posiada komendę do transferu dokumentów miedzy indeksami - "reindex". Poniższa komenda spowoduje skopiowanie dokumentów z "test_index" do "test_index3". Jeśli "test_index3" nie istnieje, zostanie utworzony. Komenda ta nie kasuje źródłowego indeksu ani nie usuwa żródłowych dokumentów po skopiowaniu.

POST _reindex
{
  "source": {
    "index": "test_index"
  },
  "dest": {
    "index": "test_index3"
  }
}

Rezultat wykonania:

{
  "took" : 388,
  "timed_out" : false,
  "total" : 2,
  "updated" : 0,
  "created" : 2,
  "deleted" : 0,
  "batches" : 1,
  "version_conflicts" : 0,
  "noops" : 0,
  "retries" : {
    "bulk" : 0,
    "search" : 0
  },
  "throttled_millis" : 0,
  "requests_per_second" : -1.0,
  "throttled_until_millis" : 0,
  "failures" : [ ]
}

REST API Elasticsearch daje niegoraniczoną swobodę w interakcji z tym środowiskiem. API to bywa szczególnie przydatne jeśli checsz ładować do ES dane w wygodny sposób (np. używając ETL) lub wykorzystujesz ES jako komponent większego systemu.