Elasticsearch pamati

Elasticsearch ir meklētājprogramma ar json rest api, izmantojot Lucene un rakstīta Java. Visu šī dzinēja priekšrocību apraksts ir pieejams vietnē oficiālā vietne. Turpmāk mēs atsauksimies uz Elasticsearch kā ES.

Līdzīgas programmas tiek izmantotas sarežģītai meklēšanai dokumentu datubāzē. Piemēram, meklējiet, ņemot vērā valodas morfoloģiju, vai meklējiet pēc ģeogrāfiskajām koordinātām.

Šajā rakstā es runāšu par ES pamatiem, izmantojot emuāra ierakstu indeksēšanas piemēru. Es jums parādīšu, kā filtrēt, kārtot un meklēt dokumentus.

Lai nebūtu atkarīgs no operētājsistēmas, visus pieprasījumus nosūtīšu ES, izmantojot CURL. Ir arī google chrome spraudnis, ko sauc izjūta.

Tekstā ir saites uz dokumentāciju un citiem avotiem. Beigās ir saites, lai ātri piekļūtu dokumentācijai. Nepazīstamu terminu definīcijas var atrast vārdnīcas.

ES instalēšana

Lai to izdarītu, mums vispirms ir nepieciešama Java. Izstrādātāji Ieteikt instalējiet Java versijas, kas jaunākas par Java 8 atjauninājumu 20 vai Java 7 atjauninājumu 55.

ES izplatīšana ir pieejama vietnē izstrādātāja vietne. Pēc arhīva izpakošanas jums jāsāk bin/elasticsearch. Pieejams arī iepakojumi apt un yum. Ir oficiālais dokera attēls. Vairāk par uzstādīšanu.

Pēc instalēšanas un palaišanas pārbaudīsim funkcionalitāti:

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

curl -X GET $ES_URL

Mēs saņemsim kaut ko līdzīgu:

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

Indeksēšana

Pievienosim ziņu 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"
}'

servera atbilde:

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

ES izveidota automātiski indekss emuārs un Tips pastu. Mēs varam izdarīt nosacītu analoģiju: indekss ir datu bāze, bet tips ir tabula šajā datu bāzē. Katram tipam ir sava shēma - kartografēšana, tāpat kā relāciju tabula. Kartēšana tiek ģenerēta automātiski, kad dokuments tiek indeksēts:

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

Servera atbildē komentāros pievienoju indeksētā dokumenta lauku vērtības:

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

Ir vērts atzīmēt, ka ES neatšķir vienu vērtību no vērtību masīva. Piemēram, virsraksta laukā ir vienkārši nosaukums, un tagu laukā ir virkņu masīvs, lai gan kartēšanā tās tiek attēlotas vienādi.
Vairāk par kartēšanu runāsim vēlāk.

pieprasījumi

Dokumenta izgūšana pēc tā 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"
  }
}

Atbildē parādījās jaunas atslēgas: _version и _source. Kopumā visi taustiņi sākas ar _ tiek klasificēti kā oficiāli.

Taustiņš _version parāda dokumenta versiju. Tas ir nepieciešams, lai darbotos optimistiskais bloķēšanas mehānisms. Piemēram, mēs vēlamies mainīt dokumentu, kuram ir versija 1. Mēs iesniedzam izmainīto dokumentu un norādām, ka tas ir dokumenta labojums ar versiju 1. Ja kāds cits arī rediģēja dokumentu ar versiju 1 un iesniedza izmaiņas pirms mums, tad ES nepieņems mūsu izmaiņas, jo tajā tiek saglabāts dokuments ar 2. versiju.

Taustiņš _source satur dokumentu, kuru indeksējām. ES neizmanto šo vērtību meklēšanas operācijām, jo Meklēšanai tiek izmantoti indeksi. Lai ietaupītu vietu, ES saglabā saspiestu avota dokumentu. Ja mums ir nepieciešams tikai ID, nevis viss avota dokuments, mēs varam atspējot avota krātuvi.

Ja mums nav nepieciešama papildu informācija, mēs varam iegūt tikai _source saturu:

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

Varat arī atlasīt tikai noteiktus laukus:

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

Indeksēsim vēl dažas ziņas un izpildīsim sarežģītākus vaicājumus.

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

šķirošana

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

Mēs izvēlējāmies pēdējo ierakstu. size ierobežo izsniedzamo dokumentu skaitu. total parāda kopējo pieprasījumam atbilstošu dokumentu skaitu. sort izvadē satur veselu skaitļu masīvu, pēc kura tiek veikta šķirošana. Tie. datums tika pārveidots par veselu skaitli. Vairāk informācijas par šķirošanu var atrast dokumentācija.

Filtri un vaicājumi

Tā vietā ES kopš 2. versijas neatšķir filtrus un vaicājumus tiek ieviests kontekstu jēdziens.
Vaicājuma konteksts atšķiras no filtra konteksta ar to, ka vaicājums ģenerē _score un netiek saglabāts kešatmiņā. Es jums parādīšu, kas ir _score vēlāk.

Filtrēt pēc datuma

Mēs izmantojam pieprasījumu diapazons filtra kontekstā:

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

Filtrēt pēc tagiem

Mēs izmantojam termina vaicājums lai meklētu dokumentu ID, kas satur noteiktu vārdu:

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

Pilna teksta meklēšana

Trīs no mūsu dokumentiem satura laukā ir:

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

Mēs izmantojam atbilstības vaicājums lai meklētu dokumentu ID, kas satur noteiktu vārdu:

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

Taču, ja satura laukā meklēsim “stāstus”, neko neatradīsim, jo Rādītājs satur tikai oriģinālos vārdus, nevis to celmus. Lai veiktu kvalitatīvu meklēšanu, ir jākonfigurē analizators.

Lauks _score šovi atbilstība. Ja pieprasījums tiek izpildīts filtra kontekstā, _score vērtība vienmēr būs vienāda ar 1, kas nozīmē pilnīgu atbilstību filtram.

Analizatori

Analizatori ir nepieciešami, lai avota tekstu pārvērstu marķieru komplektā.
Analizatori sastāv no viena Tokenizators un vairākas izvēles TokenFilters. Pirms marķiera var būt vairāki CharFilters. Tokenizatori sadala avota virkni marķieros, piemēram, atstarpes un pieturzīmes. TokenFilter var mainīt marķierus, dzēst vai pievienot jaunus, piemēram, atstāt tikai vārda celmu, noņemt prievārdus, pievienot sinonīmus. CharFilter - maina visu avota virkni, piemēram, izgriež html tagus.

ES ir vairākas standarta analizatori. Piemēram, analizators krievu.

Izmantosim priekšrocības api un redzēsim, kā standarta un krievu analizatori pārveido virkni “Smieklīgi stāsti par kaķēniem”:

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

Standarta analizators sadalīja virkni pa atstarpēm un pārveidoja visu uz mazajiem burtiem, krievu analizators noņēma nesvarīgos vārdus, pārveidoja tos uz mazajiem burtiem un atstāja vārdu celmu.

Apskatīsim, kuru Tokenizer, TokenFilters, CharFilters izmanto krievu analizators:

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

Aprakstīsim mūsu analizatoru, kura pamatā ir krievu valoda, kas izgriezīs html tagus. Sauksim to par noklusējuma, jo pēc noklusējuma tiks izmantots analizators ar šādu nosaukumu.

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

Pirmkārt, visi HTML tagi tiks noņemti no avota virknes, pēc tam marķiera standarts to sadalīs marķieros, iegūtie marķieri tiks pārvietoti uz mazajiem burtiem, nenozīmīgie vārdi tiks noņemti, un atlikušie marķieri paliks vārda celms.

Indeksa izveide

Iepriekš mēs aprakstījām noklusējuma analizatoru. Tas attieksies uz visiem virknes laukiem. Mūsu ierakstā ir ietverts atzīmju masīvs, tāpēc analizators apstrādās arī tagus. Jo Mēs meklējam ziņas pēc precīzas atbilstības tagam, pēc tam mums ir jāatspējo tagu lauka analīze.

Izveidosim indeksa emuāru2 ar analizatoru un kartēšanu, kurā ir atspējota tagu lauka analīze:

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

Pievienosim tos pašus 3 ierakstus šim rādītājam (emuārs2). Es izlaidīšu šo procesu, jo... tas ir līdzīgi dokumentu pievienošanai emuāra rādītājam.

Pilna teksta meklēšana ar izteiksmes atbalstu

Apskatīsim cita veida pieprasījumu:

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

Jo Mēs izmantojam analizatoru ar krievu izcelsmi, tad šis pieprasījums atgriezīs visus dokumentus, lai gan tajos ir tikai vārds "vēsture".

Pieprasījumā var būt speciālās rakstzīmes, piemēram:

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

Pieprasīt sintakse:

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

atsauces

PS

Ja ir interese par līdzīgiem rakstiem-mācībām, ir idejas jauniem rakstiem vai ir priekšlikumi sadarbībai, tad ar prieku saņemšu ziņu personīgā ziņā vai e-pastā [e-pasts aizsargāts].

Avots: www.habr.com