Elasticsearch Basics

Elasticsearch ass eng Sichmotor mat json rest api, benotzt Lucene a geschriwwen op Java. Eng Beschreiwung vun all de Virdeeler vun dësem Motor ass verfügbar op offizieller Websäit. An deem folgend wäerte mir op Elasticsearch als ES bezeechnen.

Ähnlech Motore gi fir komplex Recherchen an enger Dokumentdatenbank benotzt. Zum Beispill, Sich ënner Beuechtung vun der Morphologie vun der Sprooch oder Sich no Geo Koordinaten.

An dësem Artikel wäert ech iwwer d'Basis vun ES schwätzen andeems Dir d'Beispill vun der Indexéierung vu Blogposte benotzt. Ech weisen Iech wéi Dir Dokumenter filtert, sortéiert a sicht.

Fir net vum Betribssystem ofhänken, wäert ech all Ufro un ES mat CURL maachen. Et gëtt och e Plugin fir Google Chrome genannt Sënn.

Den Text enthält Linken op Dokumentatioun an aner Quellen. Um Enn ginn et Linken fir séier Zougang zu der Dokumentatioun. Definitioune vun onbekannte Begrëffer kënnen an Glossarien.

Installatioun vun ES

Fir dëst ze maachen, brauche mir als éischt Java. Entwéckler empfeelen installéiere Java Versioune méi nei wéi Java 8 Update 20 oder Java 7 Update 55.

D'ES Verdeelung ass verfügbar op Entwéckler Websäit. Nodeems Dir den Archiv ausgepackt hutt, musst Dir lafen bin/elasticsearch. Och verfügbar Packagen fir apt a Yum... do ass offiziell Bild fir Docker. Méi iwwer Installatioun.

No der Installatioun a Start, loosst eis d'Funktionalitéit kontrolléieren:

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

curl -X GET $ES_URL

Mir kréien esou eppes:

{
  "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"
}

Indexéierung

Loosst eis e Post op ES addéieren:

# Добавим документ 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"
}'

Server Äntwert:

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

ES automatesch erstallt Index blog an Typ posten. Mir kënnen eng bedingt Analogie zéien: en Index ass eng Datebank, an en Typ ass eng Tabell an dëser Datebank. All Typ huet säin eegene Schema - Conclusiounen, grad wéi e relationalen Dësch. Mapping gëtt automatesch generéiert wann d'Dokument indexéiert ass:

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

An der ServerÄntwert hunn ech d'Wäerter vun de Felder vum indexéierten Dokument an de Kommentarer bäigefüügt:

{
  "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"
          }
        }
      }
    }
  }
}

Et ass derwäert ze notéieren datt ES net ënnerscheet tëscht engem eenzege Wäert an enger Rei vu Wäerter. Zum Beispill enthält den Titelfeld einfach en Titel, an d'Tagsfeld enthält eng Array vu Strings, obwuel se an der selwechter Aart a Mapping vertruede sinn.
Mir schwätze méi iwwer d'Mapping spéider.

Demanden

En Dokument duerch seng ID zréckzéien:

# извлечем документ с 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"
  }
}

Nei Schlësselen erschéngen an der Äntwert: _version и _source. Am Allgemengen, all Schlëssel ugefaange mat _ ginn als offiziell klasséiert.

Schlëssel _version weist d'Dokumentversioun. Et ass néideg fir den optimistesche Sperrmechanismus ze schaffen. Zum Beispill wëlle mir en Dokument änneren, deen d'Versioun 1 huet. Mir stellen dat geännert Dokument of a weisen datt dëst eng Ännerung vun engem Dokument mat der Versioun 1 ass. Wann een aneren och en Dokument mat der Versioun 1 geännert huet an Ännerunge virun eis presentéiert huet, dann ES wäert eis Ännerungen net akzeptéieren, well et späichert d'Dokument mat der Versioun 2.

Schlëssel _source enthält dat Dokument dat mir indexéiert hunn. ES benotzt dëse Wäert net fir Sich Operatiounen well Indexe gi benotzt fir ze sichen. Fir Plaz ze spueren, späichert ES e kompriméierte Quelldokument. Wa mir nëmmen d'ID brauchen, an net de ganze Quelldokument, da kënne mir d'Quellspäicherung auszeschalten.

Wa mir keng zousätzlech Informatioun brauchen, kënne mir nëmmen den Inhalt vun _source kréien:

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

Dir kënnt och nëmme bestëmmte Felder auswielen:

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

Loosst eis e puer méi Posts indexéieren a méi komplex Ufroen lafen.

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"
}'

Zortéieren

# найдем последний пост по дате публикации и извлечем поля 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 ]
    } ]
  }
}

Mir hunn de leschte Post gewielt. size limitéiert d'Zuel vun den Dokumenter déi ausgestallt ginn. total weist d'Gesamtzuel vun den Dokumenter, déi d'Ufro passen. sort am Ausgang enthält eng Rei vun ganz Zuelen, duerch déi d'Sortéierung duerchgefouert gëtt. Déi. den Datum gouf an eng ganz Zuel ëmgerechent. Méi Informatioun iwwer d'Sortéierung fannt Dir an Dokumentatioun.

Filteren an Ufroen

ES zënter Versioun 2 ënnerscheet net tëscht Filteren an Ufroen, amplaz d'Konzept vu Kontexter gëtt agefouert.
En Ufrokontext ënnerscheet sech vun engem Filterkontext an datt d'Ufro en _Score generéiert an net am Cache gëtt. Ech weisen Iech méi spéit wat _score ass.

Filter no Datum

Mir benotzen d'Ufro Rei am Kader vum Filter:

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

Filter no Tags

Mir benotzen Begrëff Ufro Sich no Dokument-IDen mat engem bestëmmte Wuert:

# найдем все документы, в поле 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" : [ "котята" ]
      }
    } ]
  }
}

Voll Text Sich

Dräi vun eisen Dokumenter enthalen déi folgend am Inhaltsfeld:

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

Mir benotzen Match Ufro Sich no Dokument-IDen mat engem bestëmmte Wuert:

# 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
    } ]
  }
}

Wéi och ëmmer, wa mir no "Geschichten" am Inhaltsfeld sichen, fanne mir näischt, well Den Index enthält nëmmen déi originell Wierder, net hir Stämm. Fir eng qualitativ héichwäerteg Sich ze maachen, musst Dir den Analyser konfiguréieren.

Beräich _score weist Relevanz. Wann d'Ufro an engem Filterkontext ausgefouert gëtt, da wäert de _score Wäert ëmmer gläich 1 sinn, dat heescht e komplette Match zum Filter.

Analyséierer

Analyséierer sinn néideg fir de Quelltext an eng Rei vun Tokens ze konvertéieren.
Analysatoren besteet aus engem Tokenizer an e puer fakultativ TokenFilters. Tokenizer ka vun e puer virausgesat ginn CharFilters. Tokenizer briechen d'Quellstring an Tokens, wéi Plazen a Punktuatiounszeechen. TokenFilter kann Tokens änneren, läschen oder nei addéieren, zum Beispill nëmmen de Stamm vum Wuert verloossen, Präpositioune läschen, Synonyme derbäisetzen. CharFilter - ännert de ganze Quellstring, zum Beispill, schneid HTML-Tags aus.

ES huet e puer Standard Analysatoren. Zum Beispill en Analyser russesch.

Loosst eis profitéieren API a loosst eis kucken wéi d'Standard a russesch Analysatoren d'String "Komesch Geschichten iwwer Kitten" transforméieren:

# используем анализатор 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
  } ]
}

De Standard Analyser huet d'String duerch Plazen opgedeelt an alles an kleng Buschtawen ëmgewandelt, de russesche Analyser huet onwichteg Wierder ewechgeholl, an kleng Buschtawen ëmgewandelt an de Stamm vun de Wierder verlooss.

Loosst eis kucken wéi eng Tokenizer, TokenFilters, CharFilters de russesche Analyser benotzt:

{
  "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 отсутствуют */
    }
  }
}

Loosst eis eisen Analyser op Russesch beschreiwen, deen HTML-Tags ausschneiden. Loosst eis et Standard nennen, well en Analyser mat dësem Numm gëtt Par défaut benotzt.

{
  "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"
      ]
    }
  }
}

Als éischt ginn all HTML-Tags aus der Quellstring geläscht, da wäert den Tokenizer-Standard et an Tokens opdeelen, déi resultéierend Tokens ginn op kleng Buschtawen réckelen, onbedeitend Wierder ginn ewechgeholl, an déi verbleiwen Tokens bleiwen de Stamm vum Wuert.

En Index erstellen

Uewen hu mir de Standardanalysator beschriwwen. Et gëlt fir all Stringfelder. Eise Post enthält eng ganz Rëtsch vun Tags, sou datt d'Tags och vum Analysator veraarbecht ginn. Well Mir sichen no Posts no genaue Match zu engem Tag, da musse mir d'Analyse fir d'Tagsfeld auszeschalten.

Loosst eis en Index Blog2 mat engem Analysator a Mapping erstellen, an deem d'Analyse vum Tagsfeld behënnert ass:

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"
        }
      }
    }
  }
}'

Loosst eis déi selwecht 3 Posts an dësen Index addéieren (blog2). Ech wäert dëse Prozess ausgoen well ... et ass ähnlech wéi Dokumenter an de Blogindex bäizefügen.

Voll Text Sich mat Ausdrock Ënnerstëtzung

Loosst eis eng aner Aart vun Ufro kucken:

# найдем документы, в которых встречается слово 'истории'
# 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"
      ]
    }
  }
}'

Well Mir benotzen en Analyser mat russescher Stemmung, da wäert dës Ufro all Dokumenter zréckginn, obwuel se nëmmen d'Wuert "Geschicht" enthalen.

D'Ufro kann speziell Zeechen enthalen, zum Beispill:

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

Ufro Syntax:

+ 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 поста про котиков

Referenze

PS

Wann Dir un ähnlechen Artikel-Lektioune interesséiert sidd, Iddie fir nei Artikelen hutt oder Propositioune fir Zesummenaarbecht hutt, da wäert ech frou e Message an enger perséinlecher Noriicht oder per E-Mail ze kréien [Email geschützt].

Source: will.com