Elasticsearch

9 artykułów

Relacje w ElasticSearch ( parent – child )

W relacyjnych bazach danych tworzenie relacji jest naturalnym sposobem odwzorowywania rzeczywistości. Jednak w przypadku, gdy mamy odczynienia z wyszukiwarką opartą o nierelacyjną bazę danych sprawy nieco się komplikują.

10 lut 2020

Migracja serwera ElasticSearch na nowszą wersję z wykorzystaniem _reindex

ElasticSearch rozwija się bardzo dynamicznie w związku z czym możemy zaobserwować dość częste wydawanie nowej wersje silnika. I pojawia się pytanie, czy aktualizować ? Osobiście chętnie aktualizuję, czy to ElasticSearch-a, czy też frameworki na których pracuję. Wyznaję przy tym kilka zasad, jedna z nich to stabilność działania. Dlatego w tym wpisie pokażę, jak w prosty sposób przenieść nasze indeksy do nowszej wersji ElasticSearch-a wykorzystując _reindex.

9 maj 2019

Obsługa języków w ElasticSearch

Tworząc wyszukiwarkę o wysokim poziomie trafności, musimy wziąć pod uwagę obsługę języka. A jak wiemy nasz język do najłatwiejszych nie należy. Sam silnik wyszukiwania także nie wspiera naszego języka, ale pokażę Ci jak pomimo tych przeszkód poradzić sobie z obsługą języka polskiego.

9 sty 2019

Sposoby wyszukiwania w ElasticSearch

W pierwszym wpisie tej serii opisałem jak konstruować proste zapytania wyszukujące. Był to zaledwie przedsmak tego co można zrobić w ElasticSearch. Tym razem poszerzymy wiedzę o najczęściej wykorzystywane sposoby wyszukiwania oraz zobaczymy jakie problemy poszczególne sposoby rozwiązują.

4 wrz 2018

Wprowadzenie do agregacji danych w ElasticSearch

Oprócz zaawansowanego wyszukiwanie pełnotekstowego w ElasticSearch mamy także możliwość grupowania i zliczania dokumentów. Co ważne operacje zliczania mogą być wykonywane równolegle z operacjami przeszukiwania indeksu. Dzięki czemu możemy zmniejszyć ilość zapytań do wyszukiwarki.

3 kwi 2018

Mapping dokumentów w ElasticSearch

Jeśli mieliście kontakt z relacyjnymi bazami danych (MySQL, MSSQL, PostgreSQL) to przyzwyczaiły was one do definiowania schematów bazy danych. W takim schemacie bazę dzielimy na tabele, tabele na kolumny, którym z kolei przypisujemy określony typy danych. Odpowiednikiem tego podejścia jest mapping w ElasticSearch.

4 lut 2018

Jak stworzyć własny analizer w ElasticSearch

W przypadku, gdy żaden z wbudowanych analizer-ów nie spełnia naszych wymagań. ElasticSearch daje nam możliwość zbudowania własnych. Jednak jeśli mamy już stworzony indeks to dodanie nowego analizer-a wymaga odrobiny gimnastyki.

23 sty 2018

Jak ElasticSearch analizuje dane przed dodaniem ich do indeksu

Podczas operacji wprowadzania danych czyli indeksacji, dane są analizowane przez mechanizm, który nazywa się analizerem. Elasticsearch dostarcza nam zestaw wbudowanych analizerów, które w większości przypadków będą wystarczające.

19 paź 2017

Wprowadzenie do Elasticsearch

Niezależnie od języka w jakim piszemy aplikacje prędzej czy później możemy się spotkać z koniecznością przeszukiwania dużych ilości danych. Z reguły przeszukiwanie to ma być “inteligentne” czyli pozwalać na drobne błędy w pisowni oraz uwzględniać odmiany słów. Jak już zapewne część z was się domyśliła chodzi mi o wyszukiwanie pełnotekstowe.

19 sie 2017

Jak stworzyć własny analizer w ElasticSearch

W przypadku, gdy żaden z wbudowanych analizer-ów nie spełnia naszych wymagań. ElasticSearch daje nam możliwość zbudowania własnych.

Jednak jeśli mamy już stworzony indeks to dodanie nowego analizer-a wymaga odrobiny gimnastyki. Mianowicie konieczne jest zamknięcie indeksu:

POST /nazwa_indeksu/_close

Po czym dodajemy nowy analizer lub modyfikujemy istniejący, gdy zakończymy prace to otwieramy indeks:

POST /nazwa_indeksu/_open

W poniższych przykładach pominę ten proces i skupię się na pokazaniu procesu tworzenia analizera. Więc nie dziwcie się, że każdy przykład będzie miał nową nazwę indeksu. Dzięki temu będziecie mogli skopiować przykłady bez obaw o błąd resource_already_exists_exception:

{
  "error": {
    "root_cause": [
      {
        "type": "resource_already_exists_exception",
        "reason": "index [my_index/jgjij-zPS86T_lINGGvAUg] already exists",
        "index_uuid": "jgjij-zPS86T_lINGGvAUg",
        "index": "my_index"
      }
    ],
    "type": "resource_already_exists_exception",
    "reason": "index [my_index/jgjij-zPS86T_lINGGvAUg] already exists",
    "index_uuid": "jgjij-zPS86T_lINGGvAUg",
    "index": "my_index"
  },
  "status": 400
}

Tworzymy pierwszy analizer

Definiując nowy analizer, będziemy podawać trzy parametry wynikające ze sposobu analizy danych przez ElasticSearch-a. Bowiem proces analizy sprowadza się do trzech kroków:

  1. character filters, nałożenie na tekst filtrów, które dokonają jego modyfikacji np. eliminując tagi html,
  2. tokenizacja, rozbija na tokeny tekst,
  3. token filters, nałożenie na stworzone tokeny filtrów np. zamiana na małe litery

Dlatego tworząc analizer podajemy następujące parametry:

  1. char_filter - lista filtrów jakie mają zostać użyte przed tokenizacją np. html_strip czyli eliminacja tagów html (ten parametr nie jest obowiązkowy i można go pominąć),
  2. tokenizer - tokenizer jakiego chcemy użyć do rozbicia tekstu na tokeny (parametr obowiązkowy),
  3. filter - lista filtrów jakie mają zostać użyte na tokenach (parametr opcjonalny, można pominąć)

Zdefiniujmy nasz pierwszy analizer, który będzie usuwał znaczniki html, wykorzysta standardowy tokenizer. I na sam koniec zmieni tokeny na pisane małymi literami. Więc nasz zapis będzie wyglądał następująco:

"custom_analyzer_1": {
     "type": "custom",
     "char_filter": ["html_strip"]
     "tokenizer": "standard",
     "filter": ["lowercase"]
}

Mamy tutaj dwa elementy o których nie wspominałem wcześniej, jednak łatwo wywnioskować czym są ;) custom_analyzer_1 to nazwa naszego analizera, zaś type określa typ i w naszym przypadku jest to typ custom. Jednak analizer musimy gdzieś przypisać i najlepszym miejscem będzie indeks. W indeksie znajduje się klucz settings gdzie mamy ustawienia indeksu, zaś w nim znajdziemy klucz analysis. I pod tym kluczem znajdziemy kolejny klucz o znajomej nazwie analyzer, gdzie dodajemy nasze analizery. Taka struktura może wyglądać następująco:

{
  "settings": {
    "analysis": {
      "analyzer": {
        "custom_analyzer_1": {
          "type": "custom",
          "char_filter": ["html_strip"]
          "tokenizer": "standard",
          "filter": ["lowercase"]
        }
      }
    }
  }
}

Teraz pozostaje jedynie utworzenie nowego indeksu z naszym analizerem:

curl -XPUT 'localhost:9200/my_index_1?pretty' -H 'Content-Type: application/json' -d'{
  "settings": {
    "analysis": {
      "analyzer": {
        "custom_analyzer_1": {
          "type": "custom",
          "char_filter": ["html_strip"],
          "tokenizer": "standard",
          "filter": ["lowercase"]
        }
      }
    }
  }
}'

Skoro mamy własny analizer czas go przetestować, może nie robi on zbyt wiele jednak zobaczmy jak zadziała:

curl -XPOST 'localhost:9200/my_index_1/_analyze?pretty' -H 'Content-Type: application/json' -d '{
  "analyzer": "custom_analyzer_1",
  "text": "Pierwszy <b>analizer</b>, który usuwa znaczniki HTML !!!"
}'

Rezultatem działania będzie wyświetlenie poniższej listy tokenów:

[pierwszy, analizer, który, usuwa, znaczniki, html]

W porównaniu do wzorcowego tekstu widzimy, że zostały usunięte znaczniki html-a oraz przecinek i wykrzykniki. Analizer zadziałał zgodnie z naszymi oczekiwaniami.

Zaawansowane analizery

Nasz prosty analizer z poprzedniego przykładu jest idealnym wyjście do czegoś bardziej zaawansowanego. Zróbmy więc analizer, który będzie wyciągał adresy e-mail. Zapewne zastanawiasz się cóż w tym takiego zaawansowanego, otóż jeśli wykorzystamy standardowy mechanizm:

curl -XPOST 'localhost:9200/my_index_2/_analyze?pretty' -H 'Content-Type: application/json' -d '{
  "analyzer": "standard",
  "text": "Mój adres email: marcin.lewandowski@czterytygodnie.pl"
}'

Rezultatem działania będą tokeny:

[mój, adres, email, marcin.lewandowski, czterytygodnie.pl]

Jak widzisz, nie znajdziemy tutaj pełnego adresu e-mail. Został on rozbity na dwa tokeny, które mogą być przydatne jednak sam adres email także byłby mile widziany. W tym przypadku problemem jest tokenizer, który dzieli adres email na dwie części. Rozwiązanie było by użycie tokenizera uax_url_email.

curl -XPOST 'localhost:9200/my_index_2/_analyze?pretty' -H 'Content-Type: application/json' -d '{
  "tokenizer": "uax_url_email",
  "text": "Mój adres email: marcin.lewandowski@czterytygodnie.pl"
}'

Rezultatem działania będą tokeny:

[mój, adres, email, marcin.lewandowski@czterytygodnie.pl]

Więc jeśli chcielibyśmy mieć analizer uwzględniający adresy email to powinien wyglądać on następująco:

curl -XPUT 'localhost:9200/my_index_3?pretty' -H 'Content-Type: application/json' -d'{
  "settings": {
    "analysis": {
      "analyzer": {
        "email_analyzer": {
          "type": "custom",
          "char_filter": ["html_strip"],
          "tokenizer": "uax_url_email",
          "filter": ["lowercase"]
        }
      }
    }
  }
}'

Jednak dodatkowo chcielibyśmy, aby nasz analizer rozbijał nam adres email na dwie części jak to było robione wcześniej. I tu pojawia się problem bowiem nie ma tokenizera czy filtra, który potrafił by taką operację przeprowadzić. Jednak możemy zdefiniować własne filtry i to będzie rozwiązaniem naszego problemu.

Tworzenie filtrów

Właściwie to nie do końca tworzymy filtry, raczej je konfigurujemy tak samo jak to ma miejsce w przypadku analizerów. Z tą jednak różnicą, że filtry nie mają stałej struktury. Jest ona uzależniona od opcji konfiguracji jakie są udostępnione przez dany filtr. I tak filtr uppercase nie posiada żadnych opcji tym samym nie ma sensu tworzyć na jego bazie własnego filtra. Za to filtr pattern_capture już posiada kilka opcji, które pozwalają na odpowiednie skonfigurowanie tego filtra do naszych wymagań.

Struktura dla nowego filtra pattern_capture będzie wyglądała następująco:

"my_filter": {
    "type": "pattern_capture",
    "preserve_original": true,
    "patterns": [
        "([^@]+)@",
        "@(.+)"
    ]
}

Mamy tutaj nazwę naszego filtra my_filter. Następnie pod kluczem type znajdziemy nazwę filtra, na bazie którego tworzymy nasz filtr. Pozostałe opcje to indywidualne ustawienia dla filtra pattern_capture.

Gdy mamy już odpowiednią strukturę to możemy ją dodać do listy filtrów. Lista ta znajduje się podobnie jak analizery w ustawieniach indeksu settings. Pod kluczem analysis znajdziemy klucz filter, gdzie zapisujemy nasze filtry.

Zapis filtra może wyglądać następująco:

curl -XPUT 'localhost:9200/my_index_4?pretty' -H 'Content-Type: application/json' -d'{
  "settings": {
    "analysis": {
      "filter": {
        "my_filter": {
          "type": "pattern_capture",
          "preserve_original": true,
          "patterns": [
            "([^@]+)@",
            "@(.+)"
          ]
        }
      }
    }
  }
}'

Teraz czas na połączenie go z analizerem i weryfikację czy wszystko działa według naszych założeń.

curl -XPUT 'localhost:9200/my_index_5?pretty' -H 'Content-Type: application/json' -d'{
  "settings": {
    "analysis": {
      "filter": {
        "email": {
          "type": "pattern_capture",
          "preserve_original": true,
          "patterns": [
            "([^@]+)@",
            "@(.+)"
          ]
        }
      },
      "analyzer": {
        "email_analyzer": {
          "type": "custom",
          "char_filter": ["html_strip"],
          "tokenizer": "uax_url_email",
          "filter": ["lowercase", "email"]
        }
      }
    }
  }
}'

Mając tak przygotowany analizer zobaczmy jak zostanie przetworzony tekst:

curl -XPOST 'localhost:9200/my_index_5/_analyze?pretty' -H 'Content-Type: application/json' -d '{
  "analyzer": "email_analyzer",
  "text": "Mój adres email: marcin.lewandowski@czterytygodnie.pl"
}'

W rezultacie dostajemy listę tagów:

[mój, adres, email, marcin.lewandowski@czterytygodnie.pl, marcin.lewandowski, czterytygodnie.pl]

I to jest dokładnie to czego oczekiwaliśmy :) Może nie jest to najbardziej zaawansowany przykład na świecie, jednak pokazuje ideę jaka przyświeca tworzeniu analizer-ów oraz filtrów.

Podsumowanie

Możliwość tworzenia własnych analizer-ów oraz filtrów daje nam ogromne możliwości. Kiedy dodatkowo połączymy je z mapper-ami co pozwoli na analizę określonych pól poprzez nasze analizery i filtry. Zyskamy pełną kontrolę nad tym jak powstają tokeny, dlatego w kolejnym wpisie skupię się na mapper-ach.