Elasticsearch asoslari

Elasticsearch - bu Lucene-dan foydalanadigan va Java-da yozilgan json rest api-ga ega qidiruv tizimi. Ushbu dvigatelning barcha afzalliklari tavsifi quyidagi manzilda mavjud rasmiy veb-sayti. Quyida biz Elasticsearch-ga ES deb murojaat qilamiz.

Hujjat ma'lumotlar bazasida murakkab qidiruvlar uchun shunga o'xshash mexanizmlar qo'llaniladi. Masalan, tilning morfologiyasini hisobga olgan holda qidirish yoki geokoordinatalar bo'yicha qidirish.

Ushbu maqolada men blog xabarlarini indekslash misolidan foydalanib, ES asoslari haqida gapiraman. Men sizga hujjatlarni qanday filtrlash, saralash va qidirishni ko'rsataman.

Operatsion tizimga bog'liq bo'lmaslik uchun men barcha so'rovlarni ES ga CURL yordamida amalga oshiraman. Google Chrome uchun plagin ham mavjud ma'no.

Matnda hujjatlar va boshqa manbalarga havolalar mavjud. Oxirida hujjatlarga tezkor kirish uchun havolalar mavjud. Noma'lum atamalarning ta'riflarini topish mumkin lug'atlar.

ES o'rnatilmoqda

Buning uchun bizga birinchi navbatda Java kerak. Dasturchilar tavsiya qilamiz Java 8 yangilanishi 20 yoki Java 7 yangilanishi 55 dan yangiroq Java versiyalarini o'rnating.

ES tarqatish quyidagi manzilda mavjud ishlab chiquvchi sayti. Arxivni ochgandan so'ng siz ishga tushirishingiz kerak bin/elasticsearch. Shuningdek, mavjud apt va yum uchun paketlar. Bor docker uchun rasmiy rasm. O'rnatish haqida batafsil.

O'rnatish va ishga tushirgandan so'ng, funksionallikni tekshiramiz:

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

curl -X GET $ES_URL

Biz shunga o'xshash narsani olamiz:

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

Indekslash

Keling, ES ga post qo'shamiz:

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

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

ES avtomatik ravishda yaratilgan indeks blog va Turi post. Biz shartli o'xshashlikni chizishimiz mumkin: indeks - bu ma'lumotlar bazasi va tip - bu ma'lumotlar bazasidagi jadval. Har bir tur o'z sxemasiga ega - xaritalash, xuddi nisbiy jadval kabi. Hujjat indekslanganda xaritalash avtomatik ravishda yaratiladi:

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

Server javobida men indekslangan hujjat maydonlarining qiymatlarini sharhlarga qo'shdim:

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

Shuni ta'kidlash kerakki, ES bitta qiymat va qiymatlar massivi o'rtasida farq qilmaydi. Masalan, sarlavha maydonida shunchaki sarlavha mavjud, teglar maydonida esa qatorlar qatori mavjud, garchi ular xaritalashda bir xil tarzda ifodalangan.
Xaritalash haqida keyinroq gaplashamiz.

so'rovlar

Hujjatni identifikatori bo'yicha olish:

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

Javobda yangi kalitlar paydo bo'ldi: _version и _source. Umuman olganda, barcha kalitlar bilan boshlanadi _ rasmiy deb tasniflanadi.

Kalit _version hujjat versiyasini ko'rsatadi. Bu optimistik qulflash mexanizmi ishlashi uchun kerak. Masalan, biz 1-versiyaga ega hujjatni oʻzgartirmoqchimiz. Biz oʻzgartirilgan hujjatni taqdim etamiz va bu 1-versiyaga ega hujjat tahriri ekanligini bildiramiz. Agar kimdir 1-versiyaga ega hujjatni tahrir qilgan boʻlsa va bizdan oldin oʻzgarishlar kiritgan boʻlsa, u holda ES bizning o'zgarishlarimizni qabul qilmaydi, chunki u hujjatni 2-versiya bilan saqlaydi.

Kalit _source biz indekslagan hujjatni o'z ichiga oladi. ES qidiruv operatsiyalari uchun ushbu qiymatdan foydalanmaydi, chunki Indekslar qidiruv uchun ishlatiladi. Joyni tejash uchun ES siqilgan manba hujjatni saqlaydi. Agar bizga to'liq manba hujjat emas, balki faqat identifikator kerak bo'lsa, biz manba xotirasini o'chirib qo'yishimiz mumkin.

Agar bizga qo'shimcha ma'lumot kerak bo'lmasa, biz faqat _source mazmunini olishimiz mumkin:

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

Bundan tashqari, faqat ma'lum maydonlarni tanlashingiz mumkin:

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

Keling, yana bir nechta postlarni indekslaymiz va murakkabroq so'rovlarni bajaramiz.

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

saralash

# найдем последний пост по дате публикации и извлечем поля 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 oxirgi postni tanladik. size beriladigan hujjatlar sonini cheklaydi. total so'rovga mos keladigan hujjatlarning umumiy sonini ko'rsatadi. sort chiqishda tartiblash amalga oshiriladigan butun sonlar massivi mavjud. Bular. sana butun songa aylantirildi. Saralash haqida ko'proq ma'lumotni quyidagi sahifada topishingiz mumkin hujjatlar.

Filtrlar va so'rovlar

ES 2-versiyadan beri filtrlar va so'rovlar o'rtasida farq qilmaydi kontekstlar tushunchasi kiritiladi.
So'rov konteksti filtr kontekstidan farq qiladi, chunki so'rov _score hosil qiladi va keshda saqlanmaydi. _score nima ekanligini keyinroq ko'rsataman.

Sana bo'yicha filtrlash

Biz so'rovdan foydalanamiz qator filtr kontekstida:

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

Teglar bo'yicha filtrlash

Biz foydalanamiz muddatli so'rov berilgan so'zni o'z ichiga olgan hujjat identifikatorlarini qidirish uchun:

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

To'liq matn qidirish

Bizning uchta hujjatimiz mazmun sohasida quyidagilarni o'z ichiga oladi:

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

Biz foydalanamiz mos so'rov berilgan so'zni o'z ichiga olgan hujjat identifikatorlarini qidirish uchun:

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

Ammo, agar biz kontent sohasida "hikoyalar" ni qidirsak, biz hech narsa topa olmaymiz, chunki Indeks faqat asl so'zlarni o'z ichiga oladi, ularning ildizlari emas. Yuqori sifatli qidiruvni amalga oshirish uchun siz analizatorni sozlashingiz kerak.

dala _score ko'rsatiladi dolzarbligi. Agar so'rov filtr kontekstida bajarilsa, _score qiymati har doim 1 ga teng bo'ladi, bu filtrga to'liq mos kelishini anglatadi.

Analizatorlar

Analizatorlar manba matnni tokenlar to'plamiga aylantirish uchun kerak.
Analizatorlar bittadan iborat Tokenizer va bir nechta ixtiyoriy Token filtrlari. Tokenizerdan oldin bir nechta bo'lishi mumkin CharFilters. Tokenizatorlar manba qatorini bo'shliqlar va tinish belgilari kabi belgilarga ajratadi. TokenFilter tokenlarni oʻzgartirishi, yangilarini oʻchirishi yoki qoʻshishi mumkin, masalan, faqat soʻzning oʻzagini qoldirishi, predloglarni olib tashlashi, sinonimlarni qoʻshishi mumkin. CharFilter - butun manba qatorini o'zgartiradi, masalan, html teglarini kesib tashlaydi.

ESda bir nechta standart analizatorlar. Masalan, analizator rus.

Keling, foyda keltiraylik api Keling, standart va rus analizatorlari "Mushukchalar haqida kulgili hikoyalar" qatorini qanday o'zgartirishini ko'rib chiqaylik:

# используем анализатор 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 satrni bo'shliqlarga ajratdi va hamma narsani kichik harfga aylantirdi, ruscha analizator ahamiyatsiz so'zlarni olib tashladi, uni kichik harfga aylantirdi va so'zlarning ildizini qoldirdi.

Keling, rus analizatori qaysi Tokenizer, TokenFilters, CharFilters ishlatishini ko'rib chiqaylik:

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

Keling, html teglarini kesib tashlaydigan rus tiliga asoslangan analizatorimizni tavsiflaymiz. Keling, uni standart deb ataymiz, chunki sukut bo'yicha bunday nomli analizator ishlatiladi.

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

Birinchidan, barcha HTML teglar manba qatoridan o‘chiriladi, so‘ngra tokenizator standarti uni tokenlarga ajratadi, natijada olingan tokenlar kichik harflarga o‘tadi, ahamiyatsiz so‘zlar o‘chiriladi, qolgan tokenlar esa so‘zning o‘zagi bo‘lib qoladi.

Indeks yaratish

Yuqorida biz standart analizatorni tasvirlab berdik. U barcha satr maydonlariga qo'llaniladi. Bizning postimizda bir qator teglar mavjud, shuning uchun teglar ham analizator tomonidan qayta ishlanadi. Chunki Biz tegga aniq mos keladigan xabarlarni qidirmoqdamiz, keyin teglar maydoni uchun tahlilni o'chirib qo'yishimiz kerak.

Analizator va xaritalash bilan blog2 indeksini yarataylik, unda teglar maydonini tahlil qilish o'chirilgan:

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

Keling, bir xil 3 ta postni ushbu indeksga (blog2) qo'shamiz. Men bu jarayonni o'tkazib yuboraman, chunki ... bu blog indeksiga hujjatlar qo'shishga o'xshaydi.

Ifodani qo'llab-quvvatlash bilan to'liq matn qidirish

Keling, so'rovning boshqa turini ko'rib chiqaylik:

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

Chunki Biz rus tilidagi analizatordan foydalanmoqdamiz, keyin bu so'rov barcha hujjatlarni qaytaradi, garchi ular faqat "tarix" so'zini o'z ichiga oladi.

So'rovda maxsus belgilar bo'lishi mumkin, masalan:

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

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

Manbalar

PS

Agar siz shunga o'xshash maqola-darslarga qiziqsangiz, yangi maqolalar uchun g'oyalaringiz yoki hamkorlik bo'yicha takliflaringiz bo'lsa, men shaxsiy xabar yoki elektron pochta orqali xabar olishdan xursand bo'laman. [elektron pochta bilan himoyalangan].

Manba: www.habr.com