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

Obsługa języków w ElasticSearch

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

Obsługa języka polskiego w ElasticSearch

Niestety pomimo tego, że ElasticSearch wspiera wiele języków: arabic, armenian, basque, bengali, brazilian, bulgarian, catalan, cjk, czech, danish, dutch, english, finnish, french, galician, german, greek, hindi, hungarian, indonesian, irish, italian, latvian, lithuanian, norwegian, persian, portuguese, romanian, russian, sorani, spanish, swedish, turkish, thai. To na tej liście nie znajdziemy wsparcia dla języka polskiego. Jednak na stronie znajdziemy informację o pluginie, który doda nam obsługę naszego języka.

Plugin analysis-stempel

Plugin ten jest polecanym przez zespół Elastica i znajdziemy o tym informację w dokumentacji. Jednak pamiętajmy, że jest on tylko polecany, a sam zespół Elastic-a nie rozwija go i nie wspiera.

Instalacja pluginów w ElasticSearch jest banalnie łatwa. W tym celu przechodzimy do katalogu, gdzie znajduje się ElasticSearch np. w systemach linuks-owych może to być lokalizacja /usr/share/elasticsearch. Następnie wywołujemy poniższe polecenie:

sudo bin/elasticsearch-plugin install analysis-stempel

Po instalacji pozostaje jedynie zrestartować silnik i możemy się cieszyć wsparciem naszego języka. Plugin dostarcza nam analizer o nazwie polish oraz filtr tokenów polish_stem.

Analizer polish

Używanie analizera jest bardzo proste, i sprowadza się do jego ustawienia dla określonego pola w indeksie. Oczywiście należy o tym pamiętać przy tworzeniu indeksu, gdyż później taka zmiana będzie niemożliwa.

PUT my_index
{
  "mappings": {
    "_doc": {
      "properties": {
        "description": { 
          "type" : "text",
          "analyzer": "polish"
        }
      }
    }
  }
}

Jeśli jednak chcemy tylko potestować działanie analizera to nie ma potrzeby tworzyć indeksu i zasilać go w dane. Wystarczy odwołanie do _analyze, gdzie podajemy z jakiego analizera chcemy skorzystać oraz tekst jaki ma zostać przeanalizowany za jego pomocą.

GET _analyze
{
  "analyzer": "polish",
  "text": ["Anna miała czerwoną sukienkę."]
}

W rezultacie otrzymamy tokeny: anna, mieć, czerwony, sukienka. Tokeny te odpowiadają poszczególnym wyrazom w naszym zdaniu, co widać poniżej.

Dostarczenie analizera i łatwość z jaką możemy przeprowadzić na szybko testy jest super. Jednak żeby nie było tak różowo, nie mamy kontroli nad procesem tokenizacji oraz filtrowania co w niektórych przypadkach może się przydać. Dlatego warto zapoznać się z filtrem tokenów, który dokonuje jedynie przekształcenia tokenów na formy podstawowe.

Filtr tokenów - polish_stem

Chcąc skorzystać z filtra tokenów polish_stem konieczne będzie stworzenie własnego analizera. Wynika to z faktu, że filtrowanie tokenów to ostatni proces analizy

PUT my_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "lang_pl": { 
          "type": "custom",
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "polish_stem"
          ]
        }
      }
    }
  }
}

Teraz możemy przetestować działanie analizera, tak samo jak w przypadku dostarczonego przez plugin. Tym razem analizer nazywa się lang_pl i znajduje się w indeksie my_index

GET my_index/_analyze
{
  "analyzer": "lang_pl",
  "text": ["Anna miała czerwoną sukienkę."]
}

Wyniki dla tak przygotowanego analizera, będą niemal identyczne jak dla tego dostarczonego wraz z pluginem. Więc możesz zastanawiać się po co używać filtra tokenów, a nie analizera? Otóż chodzi o większy poziom kontroli filtracji i procesu tokenizacji.

Najlepiej zobrazuje to prosty przykład, zmienimy frazę na Wysłałem e-mail do Anny na adres: anna@gmail.com i użyjemy polskiego analizera.

GET _analyze
{
  "analyzer": "polish", 
  "text": ["Wysłałem e-mail do Anny na adres: anna@gmail.com"]
}

Mam nadzieję, że problem widać od razu. Niepotrzebne robicie słowa e-mail oraz samego adresu anna@gmail.com. Jedyny plus to fakt, że wyraz do został usunięty, ale o tym dlaczego to wyjaśnię przy stopwords-ach.

Rozwiązaniem powyższej sytuacji będzie użycie filtra tokenów oraz zmiana domyślnego tokenizera. W tym celu tworzymy nowy analizer, który nazwiemy lang_pl_whitespace.

Jako że chcemy dodać nowy analizer do istniejącego indeksu musimy najpierw go na chwilę zamknąć. Można to traktować jak zatrzymanie bazy danych, ale tylko dla tego indeksu.

POST my_index/_close

Kiedy indeks jest zamknięty to dodajemy nowy analizer.

PUT my_index/_settings
{
  "settings": {
    "analysis": {
      "analyzer": {
        "lang_pl_whitespace": { 
          "type": "custom",
          "tokenizer": "whitespace",
          "filter": [
            "lowercase",
            "polish_stem"
          ]
        }
      }
    }
  }
}

I teraz możemy z powrotem otworzyć indeks.

POST my_index/_open

Skoro nowy analizer jest gotowy to zobaczmy jak zmiana tokenizera wpłynie na wyniki analizy.

GET my_index/_analyze
{
  "analyzer": "lang_pl_whitespace", 
  "text": ["Wysłałem e-mail do Anny na adres: anna@gmail.com"]
}

Jest dużo lepiej, wyraz e-mail nie został rozbity choć uległ transformacji, co nie jest sytuacją idealną. Podobnie adres e-mail anna@gmail.com nie został rozbity oraz nie uległ transformacji. Efektem ubocznym zastosowania filtra tokenów jest dodatkowy token do, który jest zbędny i możemy go usunąć poprzez stoprowds-y.

Plugin allegro/elasticsearch-analysis-morfologik

Alternatywnym pluginem dla Analizer polish jest plugin dostarczany przez Allegro allegro/elasticsearch-analysis-morfologik i w niektórych przypadkach działa on zdecydowanie lepiej. Plugin ten bazuje na monterail/elasticsearch-analysis-morfologik, który nie jest już rozwijany.

Instalacja jest tak samo prosta jak w przypadku polecanego plugina. Wystarczy uruchomić polecenie w katalogu ElasticSearch:

sudo bin/elasticsearch-plugin install pl.allegro.tech.elasticsearch.plugin:elasticsearch-analysis-morfologik

Możemy także podać wersję ES dla której ma zostać zainstalowany plugin. W tym celu na końcu polecenia dodajemy dwukropek : i numer wersji np. :6.2.4

bin/elasticsearch-plugin install pl.allegro.tech.elasticsearch.plugin:elasticsearch-analysis-morfologik:6.2.4

Po instalacji wymagany jest restart ElasticSearch-a. Po którym do dyspozycji otrzymujemy analizer morfologik oraz filtr tokenów morfologik_stem.

Skoro już mamy nowy plugin to zobaczmy czy wyniki zmienią się w jakikolwiek sposób dla frazy testowej ?

GET _analyze
{
  "analyzer": "morfologik",
  "text": ["Anna miała czerwoną sukienkę."]
}

W rezultacie otrzymamy tokeny: Anna, mieć, czerwona, czerwony, sukienka. Tokeny te odpowiadają poszczególnym wyrazom w naszym zdaniu, co widać poniżej.

Widzimy, że w stosunku do pluginu analysis-stempel jest drobna różnica. Mianowicie dla wyrazu czerwoną otrzymaliśmy dwa tokeny: czerwona, czerwony co ma uzasadnienie w języku polskim. I według mnie jest lepszym wynikiem. Zobaczmy teraz frazę, która jest nieco bardziej skomplikowana Wysłałem e-mail do Anny na adres: anna@gmail.com. To dla niej zaczęliśmy używać filtra tokenów w poprzednim przypadku.

GET _analyze
{
  "analyzer": "morfologik",
  "text": ["Wysłałem e-mail do Anny na adres: anna@gmail.com"]
}

Zmiana pluginu w tym przypadku nie wpłynęła znacząco na poprawę wyników. Choć plusem jest brak transformacji wyrazu mail oraz zmiana imienia na podstawową formę. Znów konieczne jest użycie filtra tokenów.

Filtr tokenów - morfologik_stem

W przypadku tego plugina filtr tokenów nazywa się morfologik_stem. I teraz także zaczynamy od zamknięcia indeksu.

POST my_index/_close

Kiedy indeks jest zamknięty to dodajemy nowy analizer.

PUT my_index/_settings
{
  "settings": {
    "analysis": {
      "analyzer": {
        "lang_pl_morfologik": { 
          "type": "custom",
          "tokenizer": "whitespace",
          "filter": [
            "lowercase",
            "morfologik_stem"
          ]
        }
      }
    }
  }
}

I teraz możemy z powrotem otworzyć indeks.

POST my_index/_open

Czas na testy nowego analizera.

GET my_index/_analyze
{
  "analyzer": "lang_pl_morfologik", 
  "text": ["Wysłałem e-mail do Anny na adres: anna@gmail.com"]
}

Teraz wygląda to już bardzo fajnie, nie mamy dziwnych transformacji w wyrazie e-mail, oraz sam adres e-mail anna@gmail.com jest pojedynczym tokenem.

Stopwords czyli eliminacja “a”, “i”, “z”…

Podczas indeksowania tekstu spotykamy się ze spójnikami, które niewiele wnoszą do indeksu. W związku z czym dobrze by było, nie indeksować takich tokenów. I właśnie do tego celu został stworzony mechanizm stopwords. Przy językach wspieranych przez ElasticSearch nie ma potrzeby ręcznego definiowania listy słów, które mają być wykluczone. Jednak nasz język nie jest wspierany, w związku z czym musimy sobie taką listę stworzyć sami.

Skorzystanie z tego mechanizmu wiąże się ze zdefiniowaniem własnego analizera oraz filtra. Jako, że będziemy pracowali cały czas na tym samym indeksie to konieczne jest jego zamknięcie.

POST my_index/_close

Teraz możemy dodać nowy analizer zawierający nasz filtr z tokenami do wykluczenia.

PUT my_index/_settings
{
  "settings": {
    "analysis": {
      "filter": {
        "pl_stop": {
          "type": "stop",
          "stopwords": [ "na", "do" ]  
        }
      },
      "analyzer": {
        "lang_pl_stopwords": { 
          "type": "custom",
          "tokenizer": "whitespace",
          "filter": [
            "lowercase",
            "morfologik_stem",
            "pl_stop"
          ]
        }
      }
    }
  }
}

I otwieramy indeks ponownie.

POST my_index/_open

Teraz możemy zweryfikować czy wszystko działa według naszych oczekiwań. Za frazę testową posłuży nam Wysłałem e-mail do Anny na adres: anna@gmail.com, gdzie tokenami do wykluczenia będzie token do oraz na.

Nowy analizer zwrócił wszystkie tokeny poza tymi wykluczonymi zdefiniowanymi w filtrze pl_stop. Oczywiście tutaj zdefiniowałem tylko dwa przykładowe tokeny, a pełna lista jest dużo dłuższa. Wersję bardziej rozbudowaną znajdziecie tutaj

Synonimy

Ostatnim elementem dotyczącym języka o jakim chciałbym Ci opowiedzieć są synonimy. Dla osób nie kojarzących czym są synonimy krótka definicja z Wikipedii.

Synonim – wyraz lub dłuższe określenie równoważne znaczeniowo innemu, lub na tyle zbliżone, że można nim zastąpić to drugie w odpowiednim kontekście (auto – samochód).

Synonimy w ElasticSearch obsługujemy poprzez definiowanie filtrów z synonimami. Jest to proces bardzo podobny do obsługi stopwords-ów. Zacznijmy od zdefiniowania prostego filtra z synonimami.

"pl_synonym" : {
    "type" : "synonym",
    "synonyms" : [
        "różny, inny",
        "istotny, ważny",
        "zadanie, cel"
    ]
}

Zaczynamy od podania nazwy filtra, w tym przypadku wybrałem nazwę pl_synonym. W obiekcie pod kluczem type podajemy typ filtra, tym razem będzie to filtr typu synonym. Na samym końcu podajemy tablicę z synonimami, każdy synonim znajduje się w osobnym elemencie tablicy. Tak przygotowany filtr możemy dodać do ustawień indeksu, a następnie do analizera. W tym celu zamykamy indeks.

POST my_index/_close

Wprowadzamy zmiany w ustawieniach indeksu dodając nowy filtr i analizer.

PUT my_index/_settings
{
  "settings": {
    "analysis": {
      "filter": {
        "pl_synonym" : {
            "type" : "synonym",
            "synonyms" : [
                "różny, inny",
                "istotny, ważny",
                "zadanie, cel"
            ]
        }
      },
      "analyzer": {
        "lang_pl_synonym": { 
          "type": "custom",
          "tokenizer": "whitespace",
          "filter": [
            "lowercase",
            "pl_synonym"
          ]
        }
      }
    }
  }
}

Teraz możemy otworzyć ponownie indeks.

POST my_index/_open

Jak widzicie napisany analizer jest bardzo prosty i korzysta tylko z utworzonego filtra. Jednak na ten moment jest to wystarczające do zobrazowania zasad działania filtra synonimów. Przeprowadźmy zatem jakiś test na nowym analizerze.

GET my_index/_analyze
{
  "analyzer": "lang_pl_synonym", 
  "text": ["zadanie na dziś"]
}

W rezultacie dostaniemy tokeny: zadanie, cel, na, dziś. I takich tokenów oczekiwaliśmy, jednak istnieje możliwość zmiany zachowania filtra. Zmiana ta polega na zastępowaniu danych wyrazów synonimami, nie zaś dodawaniu synonimów do listy tokenów. Aby zmienić zachowanie filtra należy ustawić parametr expand na wartość false.

"pl_synonym_no_exp" : {
    "type" : "synonym",
    "expand" : false,
    "synonyms" : [
        "różny => inny",
        "istotny => ważny",
        "zadanie => cel"
    ]
}

Dodatkowo należy także zmienić zapis z przecinków na strzałki wskazujące jak ma następować zamiana. Pozostawienie bowiem poprzedniej konstrukcji nie przyniesie pożądanych rezultatów.

Ostatnią rzeczą o jakiej chciałbym wspomnieć to możliwość pobierania listy synonimów z pliku. Zapis powyższy jest fajny jednak przy dużych zbiorach może być nieefektywny. Dlatego w parametrze synonyms_path filtra, możemy wskazać ścieżkę do pliku tekstowego z synonimami.

Podsumowanie

Brak wbudowanej obsługi języka polskiego w ElasticSearch nie jest jakąś dużą przeszkodą, wystarczy nieco chęci ;) Pluginy pozwalają na dość dobrą obsługę transformacji wyrazów, a zbędne spójniki możemy wyeliminować własną listą stopwords-ów. Dodatkowo mamy filtr do synonimów, który pozwala nam na rozszerzenie możliwości językowych pluginów. Na początek jest to dużo więcej niż pozwoli przeciętna wyszukiwarka w bazie danych ;)

Pamiętajcie jadnak że tematy tu poruszone to dopiero podstawy, które wprowadzają w temat obsługi języków. Przykłady tu przedstawione są dalekie od ideału i zaledwie pokazują proces transformacji wyrazów, zasady działania filtrów. Więc jeśli byście chcieli je rozszerzyć, a mieli byście z tym problemy to chętnie pomogę w komentarzach.