Elasticsearch Əsasları

Elasticsearch, Lucene istifadə edərək və Java-da yazılmış json rest api ilə axtarış motorudur. Bu mühərrikin bütün üstünlüklərinin təsviri burada mövcuddur rəsmi sayt. Bundan sonra biz Elasticsearch-a ES kimi istinad edəcəyik.

Oxşar mühərriklər sənəd verilənlər bazasında mürəkkəb axtarışlar üçün istifadə olunur. Məsələn, dilin morfologiyasını nəzərə alaraq axtarış və ya coğrafi koordinatlar üzrə axtarış.

Bu yazıda blog yazılarının indeksləşdirilməsi nümunəsindən istifadə edərək ES-nin əsasları haqqında danışacağam. Sənədləri necə süzgəcdən keçirməyi, çeşidləməyi və axtarmağı sizə göstərəcəyəm.

Əməliyyat sistemindən asılı olmamaq üçün bütün sorğuları CURL-dan istifadə edərək ES-ə verəcəm. Google Chrome üçün plagin də var mənada.

Mətndə sənədlərə və digər mənbələrə keçidlər var. Sonda sənədlərə sürətli daxil olmaq üçün bağlantılar var. Tanış olmayan terminlərin təriflərinə burada rast gəlmək olar lüğətlər.

ES quraşdırılması

Bunun üçün bizə ilk olaraq Java lazımdır. Tərtibatçılar məsləhətdir Java 8 yeniləmə 20 və ya Java 7 yeniləmə 55-dən daha yeni Java versiyalarını quraşdırın.

ES paylanması burada mövcuddur developer saytı. Arxivi açdıqdan sonra işə başlamalısınız bin/elasticsearch. Həmçinin mövcuddur apt və yum üçün paketlər. Var docker üçün rəsmi şəkil. Quraşdırma haqqında daha çox.

Quraşdırıldıqdan və işə salındıqdan sonra funksionallığı yoxlayaq:

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

curl -X GET $ES_URL

Biz belə bir şey alacağıq:

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

İndeksləmə

ES-ə yazı əlavə edək:

# Добавим документ 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 cavabı:

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

ES avtomatik yaradılmışdır indeks blog və Tipi post. Şərti bənzətmə çəkə bilərik: indeks verilənlər bazasıdır, tip isə bu verilənlər bazasında cədvəldir. Hər növün öz sxemi var - Xəritəçəkmə, eynilə əlaqə cədvəli kimi. Sənəd indeksləşdirildikdə xəritə avtomatik olaraq yaradılır:

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

Server cavabında indekslənmiş sənədin sahələrinin dəyərlərini şərhlərdə əlavə etdim:

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

Qeyd etmək lazımdır ki, ES tək dəyər və dəyərlər massivi arasında fərq qoymur. Məsələn, başlıq sahəsi sadəcə bir başlıqdan ibarətdir və teqlər sahəsi xəritələşdirmədə eyni şəkildə təmsil olunsa da, sətirlər massivindən ibarətdir.
Xəritəçəkmə haqqında daha sonra danışacağıq.

İstək

Sənədin identifikatoru ilə alınması:

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

Cavabda yeni açarlar göründü: _version и _source. Ümumiyyətlə, bütün açarlar ilə başlayır _ rəsmilər kimi təsnif edilir.

Açar _version sənəd versiyasını göstərir. Optimist kilidləmə mexanizminin işləməsi üçün lazımdır. Məsələn, biz 1-ci versiyası olan sənədi dəyişmək istəyirik. Dəyişdirilmiş sənədi təqdim edirik və bunun 1-ci versiyaya malik sənədin redaktəsi olduğunu bildiririk. Əgər başqası da 1-ci versiyaya malik sənədi redaktə edibsə və bizdən əvvəl dəyişikliklər təqdim edibsə, onda ES dəyişikliklərimizi qəbul etməyəcək, çünki o, sənədi 2-ci versiya ilə saxlayır.

Açar _source indeksləşdirdiyimiz sənədi ehtiva edir. ES bu dəyəri axtarış əməliyyatları üçün istifadə etmir, çünki İndekslər axtarış üçün istifadə olunur. Yerə qənaət etmək üçün ES sıxılmış mənbə sənədini saxlayır. Əgər bizə bütün mənbə sənədi yox, yalnız id lazımdırsa, mənbə yaddaşını söndürə bilərik.

Əgər əlavə məlumata ehtiyacımız yoxdursa, biz yalnız _source məzmununu əldə edə bilərik:

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

Siz həmçinin yalnız müəyyən sahələri seçə bilərsiniz:

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

Gəlin daha bir neçə postu indeksləşdirək və daha mürəkkəb sorğular aparaq.

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

Sortlaşdırma

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

Biz sonuncu postu seçdik. size veriləcək sənədlərin sayını məhdudlaşdırır. total sorğuya uyğun gələn sənədlərin ümumi sayını göstərir. sort çıxışda çeşidləmənin həyata keçirildiyi tam ədədlər massivi var. Bunlar. tarix tam ədədə çevrildi. Çeşidləmə haqqında ətraflı məlumatı burada tapa bilərsiniz sənədləşdirmə.

Filtrlər və sorğular

Versiya 2-dən bəri ES filtrlər və sorğular arasında fərq qoymur kontekstlər anlayışı təqdim edilir.
Sorğu konteksti filtr kontekstindən ona görə fərqlənir ki, sorğu _score yaradır və yaddaşda saxlanmır. Mən sizə _xalın nə olduğunu sonra göstərəcəyəm.

Tarixə görə süzün

sorğudan istifadə edirik silsilə filtr kontekstində:

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

Teqlərə görə filtrləmə

Biz istifadə edirik müddətli sorğu verilmiş sözü ehtiva edən sənəd identifikatorlarını axtarmaq üçün:

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

Tam mətn axtarışı

Sənədlərimizdən üçü məzmun sahəsində aşağıdakıları ehtiva edir:

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

Biz istifadə edirik uyğun sorğu verilmiş sözü ehtiva edən sənəd identifikatorlarını axtarmaq üçün:

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

Lakin məzmun sahəsində “hekayələri” axtarsaq, heç nə tapa bilmərik, çünki İndeks yalnız orijinal sözləri ehtiva edir, onların köklərini deyil. Yüksək keyfiyyətli axtarış etmək üçün analizatoru konfiqurasiya etməlisiniz.

Sahə _score göstərir aktuallıq. Əgər sorğu filtr kontekstində yerinə yetirilirsə, _score dəyəri həmişə 1-ə bərabər olacaq, bu da filtrə tam uyğunluq deməkdir.

Analizatorlar

Analizatorlar mənbə mətni tokenlər dəstinə çevirmək üçün lazımdır.
Analizatorlar birdən ibarətdir Tokenizator və bir neçə isteğe bağlıdır Token Filtrləri. Tokenizerdən əvvəl bir neçə ola bilər CharFilters. Tokenizatorlar mənbə sətirini boşluqlar və durğu işarələri kimi işarələrə ayırır. TokenFilter tokenləri dəyişdirə, yenilərini silə və ya əlavə edə bilər, məsələn, yalnız sözün kökünü tərk edə, ön sözləri silə, sinonimlər əlavə edə bilər. CharFilter - bütün mənbə sətirini dəyişir, məsələn, html teqlərini kəsir.

ES-də bir neçə var standart analizatorlar. Məsələn, analizator rus.

Gəlin yararlanaq Api və standart və rus analizatorlarının "Kediciklər haqqında məzəli hekayələr" sətirini necə çevirdiyini görək:

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

Standart analizator sətri boşluqlara böldü və hər şeyi kiçik hərflərə çevirdi, rus analizatoru əhəmiyyətsiz sözləri çıxardı, kiçik hərflərə çevirdi və sözlərin kökünü buraxdı.

Rus analizatorunun hansı Tokenizer, TokenFilters, CharFilters istifadə etdiyinə baxaq:

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

html teqlərini kəsəcək rus dili əsasında analizatorumuzu təsvir edək. Onu defolt adlandıraq, çünki bu adda analizator standart olaraq istifadə olunacaq.

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

Əvvəlcə bütün HTML teqləri mənbə sətirindən silinəcək, sonra tokenizer standart onu tokenlərə böləcək, nəticədə yaranan tokenlər kiçik hərflərə keçəcək, əhəmiyyətsiz sözlər silinəcək və qalan tokenlər sözün kökü olaraq qalacaq.

İndeksin yaradılması

Yuxarıda standart analizatoru təsvir etdik. Bu, bütün sətir sahələrinə tətbiq olunacaq. Yazımızda bir sıra etiketlər var, ona görə də teqlər analizator tərəfindən işlənəcək. Çünki Biz etiketə tam uyğun gələn yazılar axtarırıq, sonra teqlər sahəsi üçün təhlili deaktiv etməliyik.

Təqlər sahəsinin təhlili deaktiv edilmiş analizator və xəritəçəkmə ilə indeks blog2 yaradaq:

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

Gəlin bu indeksə (blog3) eyni 2 yazı əlavə edək. Mən bu prosesi buraxacağam, çünki... bu, blog indeksinə sənədlər əlavə etməyə bənzəyir.

İfadə dəstəyi ilə tam mətn axtarışı

Başqa bir sorğu növünə nəzər salaq:

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

Çünki Biz rus mənşəli analizatordan istifadə edirik, onda bu sorğu bütün sənədləri qaytaracaq, baxmayaraq ki, onlarda yalnız "tarix" sözü var.

Sorğuda xüsusi simvollar ola bilər, məsələn:

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

Sorğu sintaksisi:

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

References

PS

Oxşar məqalə-dərslərlə maraqlanırsınızsa, yeni məqalələr üçün ideyalarınız varsa və ya əməkdaşlıq təklifləriniz varsa, şəxsi mesaj və ya e-poçt vasitəsilə mesaj almaqdan şad olaram. [e-poçt qorunur].

Mənbə: www.habr.com