Elasticsearch Basics

Elasticsearch je pretraživač sa json rest api, koji koristi Lucene i napisan na Javi. Opis svih prednosti ovog motora dostupan je na službena web stranica. U nastavku ćemo Elasticsearch nazivati ​​ES.

Slični motori se koriste za složena pretraživanja u bazi podataka. Na primjer, pretraživanje uzimajući u obzir morfologiju jezika ili pretraživanje po geografskim koordinatama.

U ovom članku ću govoriti o osnovama ES-a na primjeru indeksiranja blog postova. Pokazat ću vam kako filtrirati, sortirati i pretraživati ​​dokumente.

Da ne bih zavisio od operativnog sistema, sve zahteve ću uputiti ES-u koristeći CURL. Postoji i dodatak za Google Chrome koji se zove osjećaj.

Tekst sadrži linkove na dokumentaciju i druge izvore. Na kraju se nalaze linkovi za brzi pristup dokumentaciji. Definicije nepoznatih pojmova mogu se naći u glossarii.

Instaliranje ES

Da bismo to uradili, prvo nam je potrebna Java. Developers preporučeno instalirajte Java verzije novije od Java 8 ažuriranja 20 ili Java 7 ažuriranja 55.

ES distribucija je dostupna na adresi developer site. Nakon raspakivanja arhive morate pokrenuti bin/elasticsearch. Također dostupno paketi za apt i yum. Postoji službena slika za docker. Više o instalaciji.

Nakon instalacije i pokretanja, provjerimo funkcionalnost:

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

curl -X GET $ES_URL

Dobićemo nešto ovako:

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

Indeksiranje

Dodajmo objavu na 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"
}'

odgovor servera:

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

ES automatski kreiran indeks blog i Tip pošta. Možemo povući uslovnu analogiju: indeks je baza podataka, a tip je tabela u ovoj bazi podataka. Svaki tip ima svoju shemu − kartografija, baš poput relacijske tablice. Mapiranje se generiše automatski kada se dokument indeksira:

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

U odgovoru servera dodao sam vrijednosti polja indeksiranog dokumenta u komentarima:

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

Vrijedi napomenuti da ES ne pravi razliku između jedne vrijednosti i niza vrijednosti. Na primjer, polje naslova jednostavno sadrži naslov, a polje oznaka sadrži niz stringova, iako su oni predstavljeni na isti način u mapiranju.
Kasnije ćemo razgovarati više o mapiranju.

Zahtjevi

Preuzimanje dokumenta po njegovom ID-u:

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

U odgovoru su se pojavili novi ključevi: _version и _source. Općenito, svi ključevi počinju sa _ klasifikovani su kao službeni.

Ključ _version prikazuje verziju dokumenta. To je potrebno da bi optimistični mehanizam za zaključavanje radio. Na primjer, želimo promijeniti dokument koji ima verziju 1. Predajemo izmijenjeni dokument i označavamo da je ovo izmjena dokumenta sa verzijom 1. Ako je neko drugi također uređivao dokument sa verzijom 1 i podnio izmjene prije nas, onda ES neće prihvatiti naše promjene, jer pohranjuje dokument s verzijom 2.

Ključ _source sadrži dokument koji smo indeksirali. ES ne koristi ovu vrijednost za operacije pretraživanja jer Za pretraživanje se koriste indeksi. Da uštedi prostor, ES pohranjuje komprimirani izvorni dokument. Ako nam je potreban samo id, a ne cijeli izvorni dokument, onda možemo onemogućiti izvornu memoriju.

Ako nam nisu potrebne dodatne informacije, možemo dobiti samo sadržaj _source:

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

Također možete odabrati samo određena polja:

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

Hajde da indeksiramo još nekoliko postova i pokrenemo složenije upite.

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

Sortiranje

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

Izabrali smo zadnji post. size ograničava broj dokumenata koji se izdaju. total prikazuje ukupan broj dokumenata koji odgovaraju zahtjevu. sort u izlazu sadrži niz cijelih brojeva po kojima se vrši sortiranje. One. datum je pretvoren u cijeli broj. Više informacija o sortiranju možete pronaći u dokumentaciju.

Filteri i upiti

ES od verzije 2 ne pravi razliku između filtera i upita uvodi se koncept konteksta.
Kontekst upita razlikuje se od konteksta filtera po tome što upit generiše _score i nije keširan. Kasnije ću vam pokazati šta je _score.

Filtrirajte po datumu

Koristimo zahtjev domet u kontekstu filtera:

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

Filtriranje po oznakama

Koristimo upit o terminu za traženje ID-ova dokumenata koji sadrže datu riječ:

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

Pretraživanje cijelog teksta

Tri naša dokumenta sadrže sljedeće u polju sadržaja:

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

Koristimo upit podudaranja za traženje ID-ova dokumenata koji sadrže datu riječ:

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

Međutim, ako tražimo "priče" u polju sadržaja, nećemo ništa pronaći, jer Indeks sadrži samo originalne riječi, ne i njihove osnove. Da biste izvršili kvalitetno pretraživanje, potrebno je konfigurirati analizator.

polje _score pokazuje relevantnost. Ako se zahtjev izvršava u kontekstu filtera, tada će vrijednost _score uvijek biti jednaka 1, što znači potpuno podudaranje s filterom.

Analizatori

Analizatori potrebni su za pretvaranje izvornog teksta u skup tokena.
Analizatori se sastoje od jednog Tokenizer i nekoliko opcionih TokenFilters. Tokenizer može prethoditi nekoliko CharFilters. Tokenizatori razbijaju izvorni niz u tokene, kao što su razmaci i znakovi interpunkcije. TokenFilter može promijeniti tokene, izbrisati ili dodati nove, na primjer, ostaviti samo korijen riječi, ukloniti prijedloge, dodati sinonime. CharFilter - mijenja cijeli izvorni niz, na primjer, izrezuje html oznake.

ES ima nekoliko standardni analizatori. Na primjer, analizator ruski.

Hajde da iskoristimo prednost api i da vidimo kako standardni i ruski analizatori transformiraju niz "Smiješne priče o mačićima":

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

Standardni analizator je podijelio string po razmacima i konvertirao sve u mala slova, ruski analizator je uklonio nevažne riječi, pretvorio ga u mala slova i ostavio korijen riječi.

Hajde da vidimo koje Tokenizer, TokenFilters, CharFilters ruski analizator koristi:

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

Hajde da opišemo naš analizator baziran na ruskom jeziku, koji će izrezati html oznake. Nazovimo to zadanim, jer analizator sa ovim imenom će se koristiti po defaultu.

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

Prvo će se sve HTML oznake ukloniti iz izvornog niza, zatim će ga standard za tokenizer podijeliti na tokene, rezultirajući tokeni će se premjestiti na mala slova, beznačajne riječi će biti uklonjene, a preostali tokeni će ostati osnova riječi.

Kreiranje indeksa

Iznad smo opisali zadani analizator. Primjenjivat će se na sva polja stringova. Naš post sadrži niz oznaka, tako da će i tagovi biti obrađeni od strane analizatora. Jer Tražimo postove po tačnom podudaranju sa oznakom, zatim moramo onemogućiti analizu za polje oznaka.

Kreirajmo indeksni blog2 sa analizatorom i mapiranjem, u kojem je onemogućena analiza polja oznaka:

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

Dodajmo ista 3 posta u ovaj indeks (blog2). Izostaviću ovaj proces jer... slično je dodavanju dokumenata u indeks bloga.

Pretraživanje cijelog teksta s podrškom za izraze

Pogledajmo drugu vrstu zahtjeva:

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

Jer Koristimo analizator sa ruskim poreklom, onda će ovaj zahtev vratiti sve dokumente, iako sadrže samo reč 'istorija'.

Zahtjev može sadržavati posebne znakove, na primjer:

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

Sintaksa zahtjeva:

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

reference

PS

Ako ste zainteresovani za slične članke-lekcije, imate ideje za nove članke, ili imate predloge za saradnju, biće mi drago da dobijete poruku u ličnoj poruci ili na email [email zaštićen].

izvor: www.habr.com