Základy Elasticsearch

Elasticsearch je vyhľadávací nástroj s json rest api, ktorý používa Lucene a je napísaný v jazyku Java. Popis všetkých výhod tohto motora je dostupný na oficiálne internetové stránky. V nasledujúcom texte budeme označovať Elasticsearch ako ES.

Podobné nástroje sa používajú na komplexné vyhľadávanie v databáze dokumentov. Napríklad vyhľadávanie s prihliadnutím na morfológiu jazyka alebo vyhľadávanie podľa geo súradníc.

V tomto článku budem hovoriť o základoch ES na príklade indexovania blogových príspevkov. Ukážem vám, ako dokumenty filtrovať, triediť a vyhľadávať.

Aby som nebol závislý od operačného systému, urobím všetky požiadavky na ES pomocou CURL. Existuje aj plugin pre google chrome tzv význam.

Text obsahuje odkazy na dokumentáciu a iné zdroje. Na konci sú odkazy na rýchly prístup k dokumentácii. Definície neznámych pojmov nájdete v glosáre.

Inštalácia ES

Na to potrebujeme najprv Javu. Vývojári odporučiť nainštalujte verzie Java novšie ako Java 8 update 20 alebo Java 7 update 55.

Distribúcia ES je dostupná na web pre vývojárov. Po rozbalení archívu je potrebné spustiť bin/elasticsearch. Tiež dostupný balíčky pre apt a mňam, Tam je oficiálny obrázok pre docker. Viac o inštalácii.

Po inštalácii a spustení skontrolujte funkčnosť:

# для удобства запомним адрес в переменную
#export ES_URL=$(docker-machine ip dev):9200
export ES_URL=localhost:9200

curl -X GET $ES_URL

Dostaneme niečo takéto:

{
  "name" : "Heimdall",
  "cluster_name" : "elasticsearch",
  "version" : {
    "number" : "2.2.1",
    "build_hash" : "d045fc29d1932bce18b2e65ab8b297fbf6cd41a1",
    "build_timestamp" : "2016-03-09T09:38:54Z",
    "build_snapshot" : false,
    "lucene_version" : "5.4.1"
  },
  "tagline" : "You Know, for Search"
}

indexovanie

Pridajme príspevok do ES:

# Добавим документ c id 1 типа post в индекс blog.
# ?pretty указывает, что вывод должен быть человеко-читаемым.

curl -XPUT "$ES_URL/blog/post/1?pretty" -d'
{
  "title": "Веселые котята",
  "content": "<p>Смешная история про котят<p>",
  "tags": [
    "котята",
    "смешная история"
  ],
  "published_at": "2014-09-12T20:44:42+00:00"
}'

odpoveď servera:

{
  "_index" : "blog",
  "_type" : "post",
  "_id" : "1",
  "_version" : 1,
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "created" : false
}

ES automaticky vytvorené index blog a тип príspevok. Môžeme nakresliť podmienenú analógiu: index je databáza a typ je tabuľka v tejto databáze. Každý typ má svoju vlastnú schému − mapovanie, rovnako ako relačná tabuľka. Mapovanie sa generuje automaticky pri indexovaní dokumentu:

# Получим mapping всех типов индекса blog
curl -XGET "$ES_URL/blog/_mapping?pretty"

V odpovedi servera som do komentárov pridal hodnoty polí indexovaného dokumentu:

{
  "blog" : {
    "mappings" : {
      "post" : {
        "properties" : {
          /* "content": "<p>Смешная история про котят<p>", */ 
          "content" : {
            "type" : "string"
          },
          /* "published_at": "2014-09-12T20:44:42+00:00" */
          "published_at" : {
            "type" : "date",
            "format" : "strict_date_optional_time||epoch_millis"
          },
          /* "tags": ["котята", "смешная история"] */
          "tags" : {
            "type" : "string"
          },
          /*  "title": "Веселые котята" */
          "title" : {
            "type" : "string"
          }
        }
      }
    }
  }
}

Stojí za zmienku, že ES nerozlišuje medzi jednou hodnotou a súborom hodnôt. Napríklad pole title jednoducho obsahuje názov a pole tagy obsahuje pole reťazcov, hoci sú v mapovaní reprezentované rovnakým spôsobom.
O mapovaní si povieme viac neskôr.

žiadosti

Načítanie dokumentu podľa jeho ID:

# извлечем документ с id 1 типа post из индекса blog
curl -XGET "$ES_URL/blog/post/1?pretty"
{
  "_index" : "blog",
  "_type" : "post",
  "_id" : "1",
  "_version" : 1,
  "found" : true,
  "_source" : {
    "title" : "Веселые котята",
    "content" : "<p>Смешная история про котят<p>",
    "tags" : [ "котята", "смешная история" ],
    "published_at" : "2014-09-12T20:44:42+00:00"
  }
}

V odpovedi sa objavili nové kľúče: _version и _source. Vo všeobecnosti všetky kľúče začínajúce na _ sú klasifikované ako oficiálne.

kľúč _version zobrazuje verziu dokumentu. Je to potrebné pre fungovanie optimistického uzamykacieho mechanizmu. Napríklad chceme zmeniť dokument, ktorý má verziu 1. Odošleme zmenený dokument a uvedieme, že ide o úpravu dokumentu s verziou 1. Ak niekto iný upravoval aj dokument s verziou 1 a odoslal zmeny pred nami, potom ES neprijme naše zmeny, pretože ukladá dokument s verziou 2.

kľúč _source obsahuje dokument, ktorý sme indexovali. ES nepoužíva túto hodnotu na operácie vyhľadávania, pretože Na vyhľadávanie sa používajú indexy. Aby sa ušetrilo miesto, ES ukladá komprimovaný zdrojový dokument. Ak potrebujeme iba ID a nie celý zdrojový dokument, môžeme zakázať ukladanie zdroja.

Ak nepotrebujeme ďalšie informácie, môžeme získať iba obsah _source:

curl -XGET "$ES_URL/blog/post/1/_source?pretty"
{
  "title" : "Веселые котята",
  "content" : "<p>Смешная история про котят<p>",
  "tags" : [ "котята", "смешная история" ],
  "published_at" : "2014-09-12T20:44:42+00:00"
}

Môžete tiež vybrať iba určité polia:

# извлечем только поле title
curl -XGET "$ES_URL/blog/post/1?_source=title&pretty"
{
  "_index" : "blog",
  "_type" : "post",
  "_id" : "1",
  "_version" : 1,
  "found" : true,
  "_source" : {
    "title" : "Веселые котята"
  }
}

Poďme indexovať niekoľko ďalších príspevkov a spustiť zložitejšie dotazy.

curl -XPUT "$ES_URL/blog/post/2" -d'
{
  "title": "Веселые щенки",
  "content": "<p>Смешная история про щенков<p>",
  "tags": [
    "щенки",
    "смешная история"
  ],
  "published_at": "2014-08-12T20:44:42+00:00"
}'
curl -XPUT "$ES_URL/blog/post/3" -d'
{
  "title": "Как у меня появился котенок",
  "content": "<p>Душераздирающая история про бедного котенка с улицы<p>",
  "tags": [
    "котята"
  ],
  "published_at": "2014-07-21T20:44:42+00:00"
}'

triedenie

# найдем последний пост по дате публикации и извлечем поля title и published_at
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
  "size": 1,
  "_source": ["title", "published_at"],
  "sort": [{"published_at": "desc"}]
}'
{
  "took" : 8,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 3,
    "max_score" : null,
    "hits" : [ {
      "_index" : "blog",
      "_type" : "post",
      "_id" : "1",
      "_score" : null,
      "_source" : {
        "title" : "Веселые котята",
        "published_at" : "2014-09-12T20:44:42+00:00"
      },
      "sort" : [ 1410554682000 ]
    } ]
  }
}

Vybrali sme posledný príspevok. size obmedzuje počet dokumentov, ktoré sa majú vydať. total zobrazuje celkový počet dokumentov zodpovedajúcich požiadavke. sort vo výstupe obsahuje pole celých čísel, podľa ktorých sa vykonáva triedenie. Tie. dátum bol prevedený na celé číslo. Viac informácií o triedení nájdete v dokumentáciu.

Filtre a dotazy

ES od verzie 2 nerozlišuje medzi filtrami a dopytmi zavádza sa pojem kontextov.
Kontext dotazu sa líši od kontextu filtra v tom, že dotaz generuje _score a neukladá sa do vyrovnávacej pamäte. Čo je _skóre, vám ukážem neskôr.

Filtrujte podľa dátumu

Používame žiadosť rozsah v kontexte filtra:

# получим посты, опубликованные 1ого сентября или позже
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
  "filter": {
    "range": {
      "published_at": { "gte": "2014-09-01" }
    }
  }
}'

Filtrujte podľa značiek

Používame termínový dotaz ak chcete vyhľadať ID dokumentov obsahujúce dané slovo:

# найдем все документы, в поле tags которых есть элемент 'котята'
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
  "_source": [
    "title",
    "tags"
  ],
  "filter": {
    "term": {
      "tags": "котята"
    }
  }
}'
{
  "took" : 9,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 2,
    "max_score" : 1.0,
    "hits" : [ {
      "_index" : "blog",
      "_type" : "post",
      "_id" : "1",
      "_score" : 1.0,
      "_source" : {
        "title" : "Веселые котята",
        "tags" : [ "котята", "смешная история" ]
      }
    }, {
      "_index" : "blog",
      "_type" : "post",
      "_id" : "3",
      "_score" : 1.0,
      "_source" : {
        "title" : "Как у меня появился котенок",
        "tags" : [ "котята" ]
      }
    } ]
  }
}

Fulltextové vyhľadávanie

Tri z našich dokumentov obsahujú v poli obsahu nasledovné:

  • <p>Смешная история про котят<p>
  • <p>Смешная история про щенков<p>
  • <p>Душераздирающая история про бедного котенка с улицы<p>

Používame zhoda dotazu ak chcete vyhľadať ID dokumentov obsahujúce dané slovo:

# source: false означает, что не нужно извлекать _source найденных документов
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
  "_source": false,
  "query": {
    "match": {
      "content": "история"
    }
  }
}'
{
  "took" : 13,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 3,
    "max_score" : 0.11506981,
    "hits" : [ {
      "_index" : "blog",
      "_type" : "post",
      "_id" : "2",
      "_score" : 0.11506981
    }, {
      "_index" : "blog",
      "_type" : "post",
      "_id" : "1",
      "_score" : 0.11506981
    }, {
      "_index" : "blog",
      "_type" : "post",
      "_id" : "3",
      "_score" : 0.095891505
    } ]
  }
}

Ak však hľadáme „príbehy“ v poli obsahu, nenájdeme nič, pretože Index obsahuje iba pôvodné slová, nie ich kmene. Ak chcete vykonať vysokokvalitné vyhľadávanie, musíte nakonfigurovať analyzátor.

Pole _score relácie relevantnosť. Ak sa požiadavka vykoná v kontexte filtra, potom sa hodnota _score bude vždy rovnať 1, čo znamená úplnú zhodu s filtrom.

analyzátory

analyzátory sú potrebné na konverziu zdrojového textu na sadu tokenov.
Analyzátory pozostávajú z jedného Tokenizer a niekoľko voliteľných TokenFilters. Tokenizeru môže predchádzať niekoľko CharFilters. Tokenizéry rozdeľujú zdrojový reťazec na tokeny, ako sú medzery a interpunkčné znamienka. TokenFilter dokáže meniť tokeny, mazať alebo pridávať nové, napríklad ponechať len kmeň slova, odstraňovať predložky, pridávať synonymá. CharFilter - zmení celý zdrojový reťazec, napríklad vystrihne html značky.

ES má niekoľko štandardné analyzátory. Napríklad analyzátor ruský.

Využime to api a pozrime sa, ako štandardné a ruské analyzátory transformujú reťazec „Vtipné príbehy o mačiatkach“:

# используем анализатор standard       
# обязательно нужно перекодировать не ASCII символы
curl -XGET "$ES_URL/_analyze?pretty&analyzer=standard&text=%D0%92%D0%B5%D1%81%D0%B5%D0%BB%D1%8B%D0%B5%20%D0%B8%D1%81%D1%82%D0%BE%D1%80%D0%B8%D0%B8%20%D0%BF%D1%80%D0%BE%20%D0%BA%D0%BE%D1%82%D1%8F%D1%82"
{
  "tokens" : [ {
    "token" : "веселые",
    "start_offset" : 0,
    "end_offset" : 7,
    "type" : "<ALPHANUM>",
    "position" : 0
  }, {
    "token" : "истории",
    "start_offset" : 8,
    "end_offset" : 15,
    "type" : "<ALPHANUM>",
    "position" : 1
  }, {
    "token" : "про",
    "start_offset" : 16,
    "end_offset" : 19,
    "type" : "<ALPHANUM>",
    "position" : 2
  }, {
    "token" : "котят",
    "start_offset" : 20,
    "end_offset" : 25,
    "type" : "<ALPHANUM>",
    "position" : 3
  } ]
}
# используем анализатор russian
curl -XGET "$ES_URL/_analyze?pretty&analyzer=russian&text=%D0%92%D0%B5%D1%81%D0%B5%D0%BB%D1%8B%D0%B5%20%D0%B8%D1%81%D1%82%D0%BE%D1%80%D0%B8%D0%B8%20%D0%BF%D1%80%D0%BE%20%D0%BA%D0%BE%D1%82%D1%8F%D1%82"
{
  "tokens" : [ {
    "token" : "весел",
    "start_offset" : 0,
    "end_offset" : 7,
    "type" : "<ALPHANUM>",
    "position" : 0
  }, {
    "token" : "истор",
    "start_offset" : 8,
    "end_offset" : 15,
    "type" : "<ALPHANUM>",
    "position" : 1
  }, {
    "token" : "кот",
    "start_offset" : 20,
    "end_offset" : 25,
    "type" : "<ALPHANUM>",
    "position" : 3
  } ]
}

Štandardný analyzátor rozdelil reťazec podľa medzier a všetko skonvertoval na malé písmená, ruský analyzátor odstránil nedôležité slová, skonvertoval ho na malé písmená a nechal kmeň slov.

Pozrime sa, ktorý Tokenizer, TokenFilters, CharFilters používa ruský analyzátor:

{
  "filter": {
    "russian_stop": {
      "type":       "stop",
      "stopwords":  "_russian_"
    },
    "russian_keywords": {
      "type":       "keyword_marker",
      "keywords":   []
    },
    "russian_stemmer": {
      "type":       "stemmer",
      "language":   "russian"
    }
  },
  "analyzer": {
    "russian": {
      "tokenizer":  "standard",
      /* TokenFilters */
      "filter": [
        "lowercase",
        "russian_stop",
        "russian_keywords",
        "russian_stemmer"
      ]
      /* CharFilters отсутствуют */
    }
  }
}

Poďme si popísať náš analyzátor založený na ruštine, ktorý vystrihne html tagy. Nazvime to predvolené, pretože predvolene sa použije analyzátor s týmto názvom.

{
  "filter": {
    "ru_stop": {
      "type":       "stop",
      "stopwords":  "_russian_"
    },
    "ru_stemmer": {
      "type":       "stemmer",
      "language":   "russian"
    }
  },
  "analyzer": {
    "default": {
      /* добавляем удаление html тегов */
      "char_filter": ["html_strip"],
      "tokenizer":  "standard",
      "filter": [
        "lowercase",
        "ru_stop",
        "ru_stemmer"
      ]
    }
  }
}

Najprv sa zo zdrojového reťazca odstránia všetky značky HTML, potom ho štandard tokenizéra rozdelí na tokeny, výsledné tokeny sa presunú na malé písmená, nepodstatné slová sa odstránia a zvyšné tokeny zostanú kmeňom slova.

Vytvorenie indexu

Vyššie sme opísali predvolený analyzátor. Bude sa vzťahovať na všetky polia reťazcov. Náš príspevok obsahuje pole značiek, takže značky spracuje aj analyzátor. Pretože Hľadáme príspevky podľa presnej zhody so značkou, potom musíme zakázať analýzu poľa značiek.

Vytvorme index blog2 s analyzátorom a mapovaním, v ktorom je zakázaná analýza poľa značiek:

curl -XPOST "$ES_URL/blog2" -d'
{
  "settings": {
    "analysis": {
      "filter": {
        "ru_stop": {
          "type": "stop",
          "stopwords": "_russian_"
        },
        "ru_stemmer": {
          "type": "stemmer",
          "language": "russian"
        }
      },
      "analyzer": {
        "default": {
          "char_filter": [
            "html_strip"
          ],
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "ru_stop",
            "ru_stemmer"
          ]
        }
      }
    }
  },
  "mappings": {
    "post": {
      "properties": {
        "content": {
          "type": "string"
        },
        "published_at": {
          "type": "date"
        },
        "tags": {
          "type": "string",
          "index": "not_analyzed"
        },
        "title": {
          "type": "string"
        }
      }
    }
  }
}'

Do tohto indexu (blog3) pridáme rovnaké 2 príspevky. Tento proces vynechám, pretože... je to podobné ako pridávanie dokumentov do indexu blogu.

Fulltextové vyhľadávanie s podporou výrazov

Pozrime sa na iný typ žiadosti:

# найдем документы, в которых встречается слово 'истории'
# query -> simple_query_string -> query содержит поисковый запрос
# поле title имеет приоритет 3
# поле tags имеет приоритет 2
# поле content имеет приоритет 1
# приоритет используется при ранжировании результатов
curl -XPOST "$ES_URL/blog2/post/_search?pretty" -d'
{
  "query": {
    "simple_query_string": {
      "query": "истории",
      "fields": [
        "title^3",
        "tags^2",
        "content"
      ]
    }
  }
}'

Pretože Používame analyzátor s ruským pôvodom, potom táto žiadosť vráti všetky dokumenty, hoci obsahujú iba slovo „história“.

Žiadosť môže obsahovať špeciálne znaky, napr.

""fried eggs" +(eggplant | potato) -frittata"

Syntax požiadavky:

+ signifies AND operation
| signifies OR operation
- negates a single token
" wraps a number of tokens to signify a phrase for searching
* at the end of a term signifies a prefix query
( and ) signify precedence
~N after a word signifies edit distance (fuzziness)
~N after a phrase signifies slop amount
# найдем документы без слова 'щенки'
curl -XPOST "$ES_URL/blog2/post/_search?pretty" -d'
{
  "query": {
    "simple_query_string": {
      "query": "-щенки",
      "fields": [
        "title^3",
        "tags^2",
        "content"
      ]
    }
  }
}'

# получим 2 поста про котиков

referencie

PS

Ak máte záujem o podobné články-lekcie, nápady na nové články alebo návrhy na spoluprácu, budem rád, ak mi pošlete správu v osobnej správe alebo emailom [chránené e-mailom].

Zdroj: hab.com