Grunnleggende om Elasticsearch

Elasticsearch er en søkemotor med json rest api, som bruker Lucene og skrevet i Java. En beskrivelse av alle fordelene med denne motoren er tilgjengelig på offisiell nettside. I det følgende vil vi referere til Elasticsearch som ES.

Lignende motorer brukes for komplekse søk i en dokumentdatabase. For eksempel søk som tar hensyn til språkets morfologi eller søk etter geokoordinater.

I denne artikkelen vil jeg snakke om det grunnleggende om ES ved å bruke eksemplet med å indeksere blogginnlegg. Jeg skal vise deg hvordan du filtrerer, sorterer og søker i dokumenter.

For ikke å være avhengig av operativsystemet, vil jeg sende alle forespørsler til ES ved å bruke CURL. Det er også en plugin for google chrome som heter forstand.

Teksten inneholder lenker til dokumentasjon og andre kilder. På slutten er det lenker for rask tilgang til dokumentasjonen. Definisjoner av ukjente termer finner du i ordlister.

Installerer ES

For å gjøre dette trenger vi først Java. Utviklere anbefale installer Java-versjoner nyere enn Java 8-oppdatering 20 eller Java 7-oppdatering 55.

ES-distribusjonen er tilgjengelig på utviklernettsted. Etter å ha pakket ut arkivet må du løpe bin/elasticsearch. Også tilgjengelig pakker for apt og nam... det er offisielt bilde for docker. Mer om installasjon.

Etter installasjon og lansering, la oss sjekke funksjonaliteten:

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

curl -X GET $ES_URL

Vi vil motta noe slikt:

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

Indeksering

La oss legge til et innlegg til 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"
}'

server svar:

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

ES opprettet automatisk index blogg og typen post. Vi kan tegne en betinget analogi: en indeks er en database, og en type er en tabell i denne databasen. Hver type har sitt eget skjema − kartlegging, akkurat som en relasjonstabell. Kartlegging genereres automatisk når dokumentet er indeksert:

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

I serversvaret la jeg til verdiene til feltene i det indekserte dokumentet i kommentarene:

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

Det er verdt å merke seg at ES ikke skiller mellom en enkelt verdi og en rekke verdier. For eksempel inneholder tittelfeltet ganske enkelt en tittel, og tags-feltet inneholder en rekke strenger, selv om de er representert på samme måte i kartlegging.
Vi snakker mer om kartlegging senere.

forespørsler

Henter et dokument etter dets 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"
  }
}

Nye nøkler dukket opp i svaret: _version и _source. Generelt er alle nøkler som starter med _ er klassifisert som offisielle.

Ключ _version viser dokumentversjonen. Det er nødvendig for at den optimistiske låsemekanismen skal fungere. For eksempel ønsker vi å endre et dokument som har versjon 1. Vi sender inn det endrede dokumentet og indikerer at dette er en redigering av et dokument med versjon 1. Hvis noen andre også har redigert et dokument med versjon 1 og sendt inn endringer før oss, så ES vil ikke godta våre endringer, fordi den lagrer dokumentet med versjon 2.

Ключ _source inneholder dokumentet som vi indekserte. ES bruker ikke denne verdien for søkeoperasjoner fordi Indekser brukes til å søke. For å spare plass lagrer ES et komprimert kildedokument. Hvis vi bare trenger id, og ikke hele kildedokumentet, kan vi deaktivere kildelagring.

Hvis vi ikke trenger ytterligere informasjon, kan vi kun få innholdet i _source:

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

Du kan også velge bare visse felt:

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

La oss indeksere noen flere innlegg og kjøre mer komplekse søk.

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

Сортировка

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

Vi valgte det siste innlegget. size begrenser antall dokumenter som skal utstedes. total viser det totale antallet dokumenter som samsvarer med forespørselen. sort i utdataene inneholder en rekke heltall som sorteringen utføres med. De. datoen ble konvertert til et heltall. Mer informasjon om sortering finner du i dokumentasjon.

Filtre og spørringer

ES siden versjon 2 ikke skiller mellom filtre og spørringer, i stedet begrepet kontekster introduseres.
En spørringskontekst skiller seg fra en filterkontekst ved at spørringen genererer en _score og ikke bufres. Jeg skal vise deg hva _score er senere.

Filtrer etter dato

Vi bruker forespørselen område i sammenheng med filter:

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

Filtrer etter tagger

Vi bruker term spørring for å søke etter dokument-ID-er som inneholder et gitt ord:

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

Fulltekstsøk

Tre av våre dokumenter inneholder følgende i innholdsfeltet:

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

Vi bruker samsvarssøk for å søke etter dokument-ID-er som inneholder et gitt ord:

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

Men hvis vi søker etter "historier" i innholdsfeltet, vil vi ikke finne noe, fordi Indeksen inneholder bare de originale ordene, ikke stammene deres. For å gjøre et søk av høy kvalitet, må du konfigurere analysatoren.

Feltet _score viser relevans. Hvis forespørselen utføres i en filterkontekst, vil _score-verdien alltid være lik 1, noe som betyr et fullstendig samsvar med filteret.

Analysatorer

Analysatorer er nødvendig for å konvertere kildeteksten til et sett med tokens.
Analysatorer består av en Tokenizer og flere valgfrie TokenFilters. Tokenizer kan innledes med flere CharFilters. Tokenizere deler opp kildestrengen i tokens, for eksempel mellomrom og tegnsettingstegn. TokenFilter kan endre tokens, slette eller legge til nye, for eksempel la bare stammen til ordet stå, fjerne preposisjoner, legge til synonymer. CharFilter - endrer hele kildestrengen, for eksempel, kutter ut html-tagger.

ES har flere standard analysatorer. For eksempel en analysator russisk.

La oss dra nytte api og la oss se hvordan de standard og russiske analysatorene forvandler strengen "Morsomme historier om kattunger":

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

Standardanalysatoren delte opp strengen i mellomrom og konverterte alt til små bokstaver, den russiske analysatoren fjernet uviktige ord, konverterte den til små bokstaver og forlot stammen til ordene.

La oss se hvilke Tokenizer, TokenFilters, CharFilters den russiske analysatoren bruker:

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

La oss beskrive analysatoren vår basert på russisk, som vil kutte ut html-koder. La oss kalle det standard, fordi en analysator med dette navnet vil bli brukt som standard.

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

Først vil alle HTML-tagger fjernes fra kildestrengen, deretter vil tokenizer-standarden dele den opp i tokens, de resulterende tokenene vil flytte til små bokstaver, ubetydelige ord vil bli fjernet, og de gjenværende tokens vil forbli stammen til ordet.

Opprette en indeks

Ovenfor beskrev vi standardanalysatoren. Det vil gjelde for alle strengfelt. Innlegget vårt inneholder en rekke tagger, så taggene vil også bli behandlet av analysatoren. Fordi Vi ser etter innlegg etter nøyaktig samsvar med en tag, så må vi deaktivere analyse for tags-feltet.

La oss lage en indeksblogg2 med en analysator og kartlegging, der analysen av taggerfeltet er deaktivert:

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

La oss legge til de samme 3 innleggene til denne indeksen (blogg2). Jeg vil utelate denne prosessen fordi... det ligner på å legge til dokumenter i bloggindeksen.

Fulltekstsøk med uttrykksstøtte

La oss ta en titt på en annen type forespørsel:

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

Fordi Vi bruker en analysator med russisk stemming, så vil denne forespørselen returnere alle dokumenter, selv om de bare inneholder ordet "historie".

Forespørselen kan inneholde spesialtegn, for eksempel:

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

Forespørselssyntaks:

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

referanser

PS

Hvis du er interessert i lignende artikler-leksjoner, har ideer til nye artikler, eller har forslag til samarbeid, vil jeg gjerne motta en melding i en personlig melding eller på e-post [e-postbeskyttet].

Kilde: www.habr.com