Architekturę (cz. 2) i instalację (cz. 1) mamy już za sobą. Wiesz, jak to jest ze sobą spięte, więc czas na podstawową konfigurację i pierwsze zastosowanie praktyczne stosu ELK.

Konfiguracja Elasticsearcha siedzi w config/elasticsearch.yaml. Dokładny opis wszystkich opcji znajdziesz w oficjalnej dokumentacji na https://www.elastic.co/guide/index.html, ale wiadomo, nikomu się nie chce tam zaglądać, dopóki wszystko działa.

Na potrzeby twojej instalacji, zmień tylko nazwę node’a i po zapisaniu pliku YAML uruchom Elasticsearcha. Skrypt uruchamiający to bin/elasticsearch albo bin\elasticsearch.bat.


W cz. 1 sprawdzaliśmy, czy po instalacji wszystko działa, więc tylko w ramach przypomnienia:
wpisz w przeglądarce: http://localhost:9200
żeby dostać odpowiedź w stylu
{
"name" : "test-node"
"clustername" : "elasticsearch"
...
}
Jeśli nic nie dostajesz, to pewnie Elasticsearch nie został uruchomiony albo coś poszło nie tak w czasie instalacji lub kolejnego uruchomienia.

Elasticsearch gada w REST po HTTP. Możesz używać dowolnego narzędzia; czy to będzie curl, czy jakiś plugin do przeglądarki, nie ma to większego znaczenia. Jako omnibus, używasz wszystkiego i na wszystkim się znasz, prawda? 😉

Ja proponuję korzystać z konsoli Kibany, czyli K w stosie ELK. Kibana i tak ci się przyda do wizualizacji danych, więc nie ma na co czekać, tylko leć pod adres https://www.elastic.co/downloads/kibana i ściągnij pakiet instalacyjny dla swojego systemu operacyjnego.

Po zainstalowaniu Kibany zgodnie z instrukcjami, uruchom skrypt bin/kibana (lub bin\kibana.bat). Kibana ma piękny webowy interfejs, dostępny pod http://localhost:5601. Konsola jest w sekcji Dev Tools. Z konsoli możesz zadawać zapytania w formacie REST i tak też zaraz zrobimy.

Żeby Elasticsearch miał na czym pracować, a ty o co go odpytać, musisz najpierw stworzyć nowy indeks, w którym będzie przechowywana informacja o produktach. W kibanowej konsoli wpisz:

PUT /products?pretty

Na takie dictum Elasticsearch powinien odpowiedzieć:
{
"acknowledged": true,
"shards_acknowledged": true
}

Można to zinterpretować tak, że indeks został pomyślnie utworzony wraz z całym dobytkiem stłukli (shardów) i replik. Łatwo to sprawdzić poleceniem:

GET /_cat/indices?v
Serwer w odpowiedzi powinien pokazać coś w tym stylu:

Taki rezultat potwierdza, że indeks o nazwie „products” powstał i ma się dobrze. Ale sam indeks to za mało, więc jako omnibus masz inne komponenty w pogotowiu i nie zawahasz się użyć pierwszego dokumentu, który zostanie dodany do indeksu.

W tym celu instruujesz serwer:
PUT /products/test/1?pretty
{
"name": "Our first product",
"price": 20
}

gdzie dokumentem jest fragment JSON-a, a mówi on tyle, że produkt o niewątpliwie oryginalnej nazwie „Our first product” i numerze identyfikacyjnym równym 1 jest aktualnie wyceniony na 20 jednostek monetarnych. Walutę możesz sobie na razie wyobrazić. Gdyby ID (1) nie zostało podane na końcu polecenia, Elasticsearch sam przydzieli kolejny wolnym numerek.

Dokument został dodany do indeksu, ale czasami trzeba go stamtąd pobrać.

GET /products/test/1?pretty
na co serwer niezwłocznie odpowiada tak:
{
"_index": "products",
"_type": "test",
"_id": "1",
"_version": 1,
"found": true,
"_source": {
"name": "Our first product",
"price" : 20
}
}

Jak się słusznie domyślasz, pola o nazwach z podkreśleniem to „ornamenty” dodane podczas indeksowania przez Elasticsearcha. Typy pól nie były zdefiniowane w dokumencie, jak w przypadku definicji rekordów relacyjnej bazy danych, bo Elasticsearch nieźle sobie radzi z odgadywaniem tego na podstawie wartości. Mechanizm mapowania w tej zgadywance zostanie omówiony innym razem.

Ogólny wzór poleceń
Polecenia Elasticsearcha pasują do ogólnego wzorca:
<REST Verb> /<Index>/<Type>/<ID>
{
JSON document/query content
}

Wiem, wiem, prawie nikt nie lubi tych ogólnych, metajęzykowych definicji. Jest też parę wyjątków, które warto zapamiętać:

Usuwanie indeksu
DELETE /<Indeks>?pretty

Aktualizacja dokumentu
POST /<Indeks>/<Typ>/<id>/_update?pretty
{”doc” : {
body dokumentu
}
}

Usuwanie dokumentu
DELETE /<Indeks>/<Typ>/<id>?pretty

Elasticsearach oferuje również tzw. Bulk API, który pozwala na operacje na wielu dokumentach jednocześnie. Konstrukcja zapytania wygląda następująco:

POST /<Indeks>/<Typ>/_bulk?pretty
{body dokumentów JSON}

na przykład:
{"update":{"_id":"1"}}
{"doc": {"name" : "Our first product modified", "price" : 35}}
{"index":{"_id":"2"}}
{"name": "Our second product", "price" : 27}

Na koniec zapytania dotyczące wyszukiwania, czyli GET. Najprostszy wariant jest następujący:
GET /products/_search
{
"query": { "match_all": {} }
}

Serwer powinien odpowiedzieć, jak na obrazku:

Zmienna hits.total określa liczbę znalezionych dokumentów, które pasują do kryteriów zapytania GET.
Budowanie kryteriów nie jest szczególnie trudne. Np. jeśli chcemy znaleźć jogurty w naszym indeksie, to zapytanie może wyglądać następująco:

GET /products/_search
{
"query": { "match": { "name": "yoghurt" } }
}

a wynik w konsoli Kibany:

Wyrażenia logiczne
Bardziej złożone wyrażenia logiczne buduje się wg poniższego przykładu:
GET /products/_search
{
"query": {
"bool": {
"should": [
{ "match": { "name": "yoghurt" } },
{ "match": { "name": "water" } }
]
}
}
}

Powyższa kwerenda powinna zwrócić produkty zawierające frazę youghurt lub water. Jednak składnia logiki Elasticsearch wcale nie jest taka oczywista. Wg „elastycznej” terminologii boolowski AND to MUST, a NOR to MUST_NOT. Kwestia przyzwyczajenia, a omnibusom żadna składnia nie przeszkadza, choć warto się pilnować, bo AND czy NOR to popularne błędy.

Inny przykład, lista produktów, które cenę mają nie mniejszą niż 30.

GET /products/_search
{
"query": {
"bool": {
"must": { "match_all": {} },
"filter": {
"range": {
"price": {
"gte": 30
}
}
}
}
}
}

Kryterium „gte” to >=, podczas gdy zwykła relacja większości to „gt” (greater than). Generalnie, składnia nie jest jakoś specjalnie trudna i dobrze opisana w oficjalnej dokumentacji pod https://www.elastic.co/guide/index.html.
Agregacja danych
Elasticsearch posiada mechanizm agregacji danych zrealizowany przez inverted index. Chwilowo bez wdawania się w szczegóły jego działania, trzeba zaznaczyć, że agregacja danych jest szybka jak Maserati Quattroporte GTS (choć pozbawiona jakże miłego uszom fana motoryzacji dźwięku silnika) i moc jej mechanizmu objawia się szczególnie przy wielkich zbiorach danych.

Dane muszą być odpowiednio przygotowane do agregacji, ale nie jest to ani trudnie, ani kłopotliwe. Wszystko, co jest potrzebne, to ustawienie atrybutu na polu, które zostanie użyte do agregacji.
PUT products/_mapping/test
{
"properties": {
"category": {
"type": "text",
"fielddata": true
}
}
}

Powyższe polecenie mapuje pole „category” jako „keyword”. O mechanizmie mapowania i inverted index jeszcze sobie pogadamy, ale teraz nie jest to potrzebne.

Sama agregacja danych odbywa się następująco:
GET /products/_search
{
"size": 0,
"aggs": {
"agg1": {
"terms": {
"field": "category"
}
}
}
}

To daje następujący rezultat:


a wyniki są automatycznie posortowane wg liczby wystąpień zagregowanych dokumentów.

Nawet najbardziej wypasione procesory omnibusów też mają swoje granice. I tak właśnie dobrnęliśmy do jednej z nich. Na dziś wystarczy, bo już wiesz, jak stworzyć indeks, zdefiniować kryteria i przeszukać oraz zagregować dokumenty. Następne sztuczki w kolejnych odsłonach omnibusowych przygód z ELK.