Principi di Elasticsearch

Elasticsearch hè un mutore di ricerca cù json rest api, cù Lucene è scrittu in Java. Una descrizzione di tutti i vantaghji di stu mutore hè dispunibule à situ ufficiali. In ciò chì seguita faremu riferite à Elasticsearch cum'è ES.

I mutori simili sò usati per ricerche cumplessi in una basa di dati di documentu. Per esempiu, cercate tenendu in contu a morfologia di a lingua o cercate per coordenate geo.

In questu articulu parraraghju di i principii di ES cù l'esempiu di l'indexazione di i blog posts. Vi mustraraghju cumu filtrà, sorte è cercà i ducumenti.

Per ùn dipende micca di u sistema operatore, aghju da fà tutte e dumande à ES usendu CURL. Ci hè ancu un plugin per google chrome chjamatu sensu.

U testu cuntene ligami à documentazione è altre fonti. À a fine ci sò ligami per un accessu rapidu à a documentazione. Definizioni di termini micca familiari ponu esse truvati in glossarii.

Installazione di ES

Per fà questu, avemu prima bisognu di Java. Sviluppatori ricumandemu installate versioni Java più recenti di Java 8 update 20 o Java 7 update 55.

A distribuzione ES hè dispunibule à situ di sviluppatore. Dopu à unpacking l 'archiviu vi tuccherà à curriri bin/elasticsearch. Disponibile ancu pacchetti per apt è yum... ci hè immagine ufficiale per docker. Più nantu à a stallazione.

Dopu a stallazione è u lanciu, verificate a funziunalità:

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

curl -X GET $ES_URL

Riceveremu qualcosa cum'è questu:

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

Indexazione

Aghjunghjite un postu à 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"
}'

risposta di u servitore:

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

ES creatu automaticamente indice blog è Type postu. Pudemu disegnà una analogia cundizionale: un indexu hè una basa di dati, è un tipu hè una tavola in questa basa di dati. Ogni tipu hà u so propiu schema - aerial, cum'è una tavola relazionale. A mappatura hè generata automaticamente quandu u documentu hè indexatu:

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

In a risposta di u servitore, aghju aghjustatu i valori di i campi di u documentu indexatu in i cumenti:

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

Hè da nutà chì ES ùn distingue micca trà un valore unicu è un array di valori. Per esempiu, u campu di tìtulu cuntene solu un titulu, è u campu di tags cuntene un array of strings, ancu s'elli sò rapprisintati in u listessu modu in mapping.
Parleremu di più nantu à a mappa più tardi.

E dumande

Ritruvà un documentu da u so 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"
  }
}

Novi chjavi apparsu in a risposta: _version и _source. In generale, tutte e chjave cumincianu cù _ sò classificate cum'è ufficiali.

Chjave _version mostra a versione di u documentu. Hè necessariu per u mecanismu di serratura ottimistu per travaglià. Per esempiu, vulemu cambià un documentu chì hà a versione 1. Inviamu u documentu cambiatu è indichemu chì questu hè un editu di un documentu cù a versione 1. Se qualchissia altru hà ancu editatu un documentu cù a versione 1 è hà sottumessu cambiamenti davanti à noi, allora ES ùn accetta micca i nostri cambiamenti, perchè guarda u documentu cù a versione 2.

Chjave _source cuntene u documentu chì avemu indexatu. ES ùn usa micca stu valore per l'operazioni di ricerca perchè L'indici sò usati per a ricerca. Per risparmià spaziu, ES guarda un documentu fonte cumpressu. Se avemu solu bisognu di l'id, è micca u documentu surghjente tutale, allora pudemu disattivà l'almacenamiento di fonte.

Se ùn avemu micca bisognu di infurmazioni supplementari, pudemu avè solu u cuntenutu di _source:

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

Pudete ancu selezziunate solu certi campi:

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

Indiciemu uni pochi di più posti è eseguite dumande più cumplesse.

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

Ordine

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

Avemu sceltu l'ultimu postu. size limita u numeru di ducumenti chì deve esse emessu. total mostra u numeru tutale di documenti chì currispondenu à a dumanda. sort in l'output cuntene un array di integer per quale l'ordinamentu hè realizatu. Quelli. a data hè stata cunvertita in un integer. Più infurmazione nantu à a classificazione pò esse truvata in ducumentazione.

Filtri è dumande

ES da a versione 2 ùn distingue micca i filtri è e dumande, invece u cuncettu di cuntesti hè introduttu.
Un cuntestu di dumanda difiere da un cuntestu di filtru in chì a dumanda genera un _score è ùn hè micca cache. Vi mustraraghju ciò chì _score hè più tardi.

Filtrà per data

Avemu aduprà a dumanda catalogu in u cuntestu di filtru:

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

Filtrazione per tag

Avemu aduprà dumanda di termini per circà l'ID di documentu chì cuntene una parola data:

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

Ricerca di testu cumpletu

Trè di i nostri documenti cuntenenu i seguenti in u campu di cuntenutu:

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

Avemu aduprà dumanda di cuncurrenza per circà l'ID di documentu chì cuntene una parola data:

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

In ogni casu, s'ellu circhemu "storie" in u campu di cuntenutu, ùn truvamu nunda, perchè L'indici cuntene solu e parolle originali, micca i so steli. Per fà una ricerca d'alta qualità, avete bisognu di cunfigurà l'analizzatore.

chjosu _score mostra pertinenza. Se a dumanda hè eseguita in un cuntestu di filtru, allora u valore _score serà sempre uguale à 1, chì significa una partita cumpleta cù u filtru.

Analyzers

Analyzers sò necessarii per cunvertisce u testu fonte in un set di tokens.
L'analizzatori sò custituiti da unu Tokenizer è parechji opzionali TokenFilters. Tokenizer pò esse precedutu da parechji CharFilters. Tokenizers rompe a stringa fonte in tokens, cum'è spazii è caratteri di puntuazione. TokenFilter pò cambià i tokens, sguassate o aghjunghje novi, per esempiu, lasciate solu u troncu di a parolla, sguassate preposizioni, aghjunghje sinonimi. CharFilter - cambia tutta a stringa fonte, per esempiu, taglia i tag html.

ES hà parechji analizzatori standard. Per esempiu, un analizzatore russo.

Aprovechemu Api è vedemu cumu l'analizzatori standard è russi trasformanu a stringa "Storie divertenti nantu à i gattini":

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

L'analizzatore standard divisu a stringa per spazii è cunvertisce tuttu in minuscule, l'analizzatore russu sguassate e parolle senza impurtanza, cunvertisce in minuscule è lasciò u troncu di e parolle.

Videmu quale Tokenizer, TokenFilters, CharFilters usa l'analizzatore russu:

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

Descrivimu u nostru analizzatore basatu annantu à u russu, chì hà da taglià e tag html. Chjamemu predeterminatu, perchè un analizzatore cù stu nome serà utilizatu per difettu.

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

Prima, tutte e tag HTML seranu sguassate da a stringa di fonte, dopu u standard di tokenizer u sparterà in tokens, i tokens resultanti si moveranu in minuscule, e parolle insignificanti seranu eliminate, è i tokens rimanenti fermanu u gambu di a parolla.

Crià un indice

Sopra avemu descrittu l'analizzatore predeterminatu. S'applicà à tutti i campi di stringa. U nostru postu cuntene una serie di tags, cusì i tags seranu ancu trattati da l'analizzatore. Perchè Cerchemu i posti per corrispondenza esatta à una tag, allora avemu bisognu di disattivà l'analisi per u campu di tag.

Creemu un index blog2 cù un analizatore è mapping, in quale l'analisi di u campu di tags hè disattivata:

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

Aghjunghjemu i stessi 3 posti à questu indice (blog2). Omette stu prucessu perchè ... hè simile à aghjunghje documenti à l'indici di u blog.

Ricerca di testu cumpletu cù supportu di spressione

Fighjemu un altru tipu di dumanda:

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

Perchè Avemu aduprà un analizzatore cù stemming russa, allora sta dumanda restituverà tutti i documenti, ancu s'ellu cuntene solu a parolla "storia".

A dumanda pò cuntene caratteri speciali, per esempiu:

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

Sintassi di dumanda:

+ 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

Sè site interessatu in articuli simili-lezioni, avete idee per novi articuli, o avete pruposte per a cooperazione, allora saraghju felice di riceve un missaghju in un missaghju persunale o per email. [email prutettu].

Source: www.habr.com