इलास्टिक्स खोज मूल बातें

इलास्टिकसर्च एक ऐसा सर्च इंजन है जिसमें json रेस्ट एपीआई है, जो ल्यूसीन का उपयोग करता है और जावा में लिखा गया है। इस इंजन के सभी फायदों का विवरण यहाँ उपलब्ध है आधिकारिक वेबसाइटअब से हम Elasticsearch को ES के नाम से संदर्भित करेंगे।

ऐसे इंजनों का उपयोग दस्तावेज़ डेटाबेस में जटिल खोजों के लिए किया जाता है। उदाहरण के लिए, भाषा की आकृति विज्ञान को ध्यान में रखते हुए खोज या भौगोलिक निर्देशांक द्वारा खोज।

इस लेख में मैं आपको ब्लॉग पोस्ट को इंडेक्स करने के उदाहरण का उपयोग करके ES की मूल बातें बताऊंगा। मैं आपको दिखाऊंगा कि दस्तावेज़ों को कैसे फ़िल्टर, सॉर्ट और सर्च किया जाता है।

ऑपरेटिंग सिस्टम से स्वतंत्र होने के लिए, मैं CURL का उपयोग करके ES को सभी अनुरोध करूंगा। Google Chrome के लिए एक प्लगइन भी है जिसे कहा जाता है भावना.

पूरे पाठ में दस्तावेज़ों और अन्य स्रोतों के लिंक दिए गए हैं। अंत में दस्तावेज़ों तक त्वरित पहुँच के लिए लिंक दिए गए हैं। अपरिचित शब्दों की परिभाषाएँ यहाँ पाई जा सकती हैं शब्दावलियों.

ईएस स्थापित करना

ऐसा करने के लिए, हमें सबसे पहले जावा की आवश्यकता है। डेवलपर्स विग्यापन जावा 8 अपडेट 20 या जावा 7 अपडेट 55 से नए जावा संस्करण स्थापित करें।

ES वितरण यहां उपलब्ध है डेवलपर साइट. संग्रह को अनपैक करने के बाद, आपको चलाने की आवश्यकता है bin/elasticsearch। भी उपलब्ध है apt और yum के लिए पैकेज। वहाँ है डॉकर के लिए आधिकारिक छवि. स्थापना के बारे में अधिक जानकारी.

स्थापना और लॉन्च के बाद, आइए कार्यक्षमता की जांच करें:

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

curl -X GET $ES_URL

हमें कुछ इस प्रकार का उत्तर प्राप्त होगा:

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

अनुक्रमण

आइए 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"
}'

सर्वर प्रतिक्रिया:

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

ES स्वचालित रूप से बनाया गया अनुक्रमणिका ब्लॉग और टाइप पोस्ट। हम एक सशर्त सादृश्य बना सकते हैं: इंडेक्स एक डेटाबेस है, और प्रकार इस DB में एक तालिका है। प्रत्येक प्रकार की अपनी योजना है - मानचित्रण, बिल्कुल एक रिलेशनल टेबल की तरह। जब दस्तावेज़ को अनुक्रमित किया जाता है तो मैपिंग स्वचालित रूप से उत्पन्न होती है:

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

सर्वर प्रतिक्रिया में, मैंने टिप्पणियों में अनुक्रमित दस्तावेज़ फ़ील्ड के मान जोड़े:

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

यह ध्यान देने योग्य है कि ES एकल मान और मानों की सरणी के बीच अंतर नहीं करता है। उदाहरण के लिए, शीर्षक फ़ील्ड में सिर्फ़ एक शीर्षक होता है, और टैग फ़ील्ड में स्ट्रिंग्स की एक सरणी होती है, हालाँकि मैपिंग में उन्हें उसी तरह दर्शाया जाता है।
हम बाद में मानचित्रण के बारे में अधिक विस्तार से बात करेंगे।

अनुरोध

किसी दस्तावेज़ को उसकी आईडी द्वारा निकालना:

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

उत्तर में नई कुंजियाँ दिखाई दीं: _version и _sourceसामान्यतः, से शुरू होने वाली सभी कुंजियाँ _ सेवा मदों के रूप में वर्गीकृत किया जाता है।

कुंजी _version दस्तावेज़ का संस्करण दिखाता है। आशावादी लॉकिंग तंत्र के काम करने के लिए इसकी आवश्यकता होती है। उदाहरण के लिए, हम संस्करण 1 वाले दस्तावेज़ को बदलना चाहते हैं। हम बदला हुआ दस्तावेज़ भेजते हैं और संकेत देते हैं कि यह संस्करण 1 वाले दस्तावेज़ में एक संपादन है। यदि किसी और ने भी संस्करण 1 वाले दस्तावेज़ को संपादित किया है और हमसे पहले परिवर्तन भेजे हैं, तो ES हमारे परिवर्तनों को स्वीकार नहीं करेगा, क्योंकि यह दस्तावेज़ को संस्करण 2 के साथ संग्रहीत करता है।

कुंजी _source इसमें वह दस्तावेज़ शामिल है जिसे हमने अनुक्रमित किया है। ES खोज कार्यों के लिए इस मान का उपयोग नहीं करता है, क्योंकि खोज के लिए अनुक्रमित का उपयोग किया जाता है। स्थान बचाने के लिए, ES संपीड़ित स्रोत दस्तावेज़ को संग्रहीत करता है। यदि हमें केवल आईडी की आवश्यकता है, न कि संपूर्ण स्रोत दस्तावेज़ की, तो हम स्रोत को संग्रहीत करना अक्षम कर सकते हैं।

यदि हमें किसी अतिरिक्त जानकारी की आवश्यकता न हो, तो हम केवल _source की सामग्री प्राप्त कर सकते हैं:

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

आप केवल कुछ फ़ील्ड का चयन भी कर सकते हैं:

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

आइए कुछ और पोस्टों को अनुक्रमित करें और अधिक जटिल क्वेरीज़ चलाएं।

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

हमने अंतिम पोस्ट चुन ली है। size जारी किए जाने वाले दस्तावेजों की संख्या को सीमित करता है। total क्वेरी से मेल खाने वाले दस्तावेज़ों की कुल संख्या दिखाता है. sort आउटपुट में पूर्णांकों की एक सरणी होती है जिसके द्वारा सॉर्टिंग की जाती है। यानी, तिथि को पूर्णांक में बदल दिया गया है। आप सॉर्टिंग के बारे में अधिक जानकारी यहाँ पढ़ सकते हैं प्रलेखन.

फ़िल्टर और क्वेरीज़

संस्करण 2 के बाद से ES फ़िल्टर और क्वेरीज़ के बीच अंतर नहीं करता है, इसके बजाय संदर्भ की अवधारणा प्रस्तुत की गई है.
क्वेरी संदर्भ फ़िल्टर संदर्भ से इस मायने में अलग है कि क्वेरी _score उत्पन्न करती है और कैश नहीं होती। मैं आपको बाद में दिखाऊंगा कि _score क्या है।

तिथि के अनुसार फ़िल्टर करें

हम क्वेरी का उपयोग करते हैं रेंज फ़िल्टर के संदर्भ में:

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

टैग द्वारा फ़िल्टर करें

हम उपयोग करते हैं शब्द क्वेरी किसी दिए गए शब्द वाले दस्तावेज़ आईडी की खोज करने के लिए:

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

पूरा पाठ खोजें

हमारे तीन दस्तावेजों में निम्नलिखित विषय-वस्तु सम्मिलित है:

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

हम उपयोग करते हैं मिलान क्वेरी किसी दिए गए शब्द वाले दस्तावेज़ आईडी की खोज करने के लिए:

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

हालाँकि, अगर हम कंटेंट फ़ील्ड में "स्टोरीज़" खोजते हैं, तो हमें कुछ भी नहीं मिलेगा, क्योंकि इंडेक्स में केवल मूल शब्द होते हैं, उनके आधार नहीं। गुणवत्तापूर्ण खोज करने के लिए, आपको विश्लेषक सेट अप करना होगा।

क्षेत्र _score शो प्रासंगिकतायदि क्वेरी को फ़िल्टर संदर्भ में निष्पादित किया जाता है, तो _score मान हमेशा 1 होगा, जिसका अर्थ है फ़िल्टर के साथ पूर्ण अनुपालन।

विश्लेषक

विश्लेषक स्रोत पाठ को टोकनों के एक सेट में बदलने के लिए इनकी आवश्यकता होती है।
विश्लेषकों में एक शामिल है टोकन लेने वाला और कई वैकल्पिक टोकन फ़िल्टर.टोकेनाइजर के पहले कई शब्द आ सकते हैं चारफ़िल्टरटोकनाइज़र स्रोत स्ट्रिंग को टोकन में विभाजित करता है, उदाहरण के लिए, रिक्त स्थान और विराम चिह्नों द्वारा। टोकनफ़िल्टर टोकन बदल सकता है, हटा सकता है या नए जोड़ सकता है, उदाहरण के लिए, केवल शब्द स्टेम को छोड़ दें, पूर्वसर्गों को हटा दें, समानार्थी शब्द जोड़ें। चारफ़िल्टर - स्रोत स्ट्रिंग को पूरी तरह से बदल देता है, उदाहरण के लिए, HTML टैग को काट देता है।

ईएस में कई हैं मानक विश्लेषकउदाहरण के लिए, एक विश्लेषक रूसी.

आइए उपयोग करें एपीआई और आइए देखें कि मानक और रूसी विश्लेषक "बिल्ली के बच्चे के बारे में मजेदार कहानियाँ" स्ट्रिंग को कैसे बदलते हैं:

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

मानक विश्लेषक ने पंक्ति को रिक्त स्थानों से विभाजित किया और सभी चीजों को लोअर केस में परिवर्तित कर दिया, रूसी विश्लेषक ने महत्वहीन शब्दों को हटा दिया, लोअर केस में परिवर्तित कर दिया और शब्द के मूल को छोड़ दिया।

आइए देखें कि रूसी विश्लेषक टोकनइज़र, टोकनफ़िल्टर्स, चारफ़िल्टर्स क्या उपयोग करता है:

{
  "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 टैग्स को काट देगा। आइए इसे डिफ़ॉल्ट कहें, क्योंकि इस नाम वाला विश्लेषक डिफ़ॉल्ट रूप से उपयोग किया जाएगा।

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

सबसे पहले, सभी HTML टैग्स को मूल स्ट्रिंग से हटा दिया जाएगा, फिर इसे टोकेनाइजर मानक द्वारा टोकन में विभाजित किया जाएगा, परिणामी टोकन को लोअर केस में परिवर्तित कर दिया जाएगा, महत्वहीन शब्दों को हटा दिया जाएगा, और शेष टोकन शब्द स्टेम होंगे।

सूचकांक बनाना

ऊपर हमने डिफ़ॉल्ट विश्लेषक का वर्णन किया है। यह सभी स्ट्रिंग फ़ील्ड पर लागू होगा। हमारी पोस्ट में टैग की एक सरणी है, इसलिए टैग को विश्लेषक द्वारा भी संसाधित किया जाएगा। चूंकि हम टैग के सटीक मिलान के आधार पर पोस्ट की तलाश कर रहे हैं, इसलिए टैग फ़ील्ड के लिए विश्लेषण को अक्षम करना आवश्यक है।

आइए विश्लेषक और मैपिंग के साथ एक ब्लॉग2 इंडेक्स बनाएं, जिसमें टैग फ़ील्ड का विश्लेषण अक्षम है:

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

आइए इस इंडेक्स (ब्लॉग 3) में वही 2 पोस्ट जोड़ें। मैं इस प्रक्रिया को छोड़ दूंगा क्योंकि यह ब्लॉग इंडेक्स में दस्तावेज़ जोड़ने जैसा ही है।

अभिव्यक्ति समर्थन के साथ पूर्ण-पाठ खोज

आइये एक अन्य प्रकार के अनुरोधों से परिचित हों:

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

चूंकि हम रूसी स्टेमिंग वाले विश्लेषक का उपयोग कर रहे हैं, इसलिए यह क्वेरी सभी दस्तावेज़ लौटाएगी, भले ही उनमें केवल 'इतिहास' शब्द ही क्यों न हो।

अनुरोध में विशेष वर्ण हो सकते हैं, उदाहरण के लिए:

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

क्वेरी सिंटैक्स:

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

संदर्भों

PS

यदि आप ऐसे लेख-पाठों में रुचि रखते हैं, नए लेखों के लिए विचार रखते हैं या सहयोग के लिए प्रस्ताव रखते हैं, तो मुझे व्यक्तिगत संदेश या मेल m.kuzmin+habr@darkleaf.ru द्वारा संदेश प्राप्त करने में खुशी होगी।

स्रोत: www.habr.com