Elasticsearch の基本

Elasticsearch は、Json REST API を備えた検索エンジンで、Lucene を使用し、Java で書かれています。 このエンジンのすべての利点の説明は、次の場所で参照できます。 公式サイト。 以下では、Elasticsearch を ES と呼びます。

文書データベース内の複雑な検索には、同様のエンジンが使用されます。 たとえば、言語の形態を考慮した検索や地理座標による検索などです。

この記事では、ブログ記事のインデックス作成を例にESの基礎についてお話します。 ドキュメントをフィルター、並べ替え、検索する方法を説明します。

OSに依存しないように、ESへのリクエストはすべてCURLを使用して行います。 Google Chrome 用のプラグインもあります。 センス.

テキストにはドキュメントやその他のソースへのリンクが含まれています。 最後に、ドキュメントに簡単にアクセスできるリンクがあります。 馴染みのない用語の定義は、次の場所にあります。 用語集.

インストール

これを行うには、まず Java が必要です。 開発者 推奨する Java 8 Update 20 または Java 7 Update 55 より新しい Java バージョンをインストールします。

ES ディストリビューションは次の場所から入手できます。 開発者サイト。 アーカイブを解凍した後、次を実行する必要があります bin/elasticsearch. こちらも利用可能 apt と yum のパッケージ。 あります docker の公式イメージ. インストールの詳細.

インストールして起動したら、機能を確認してみましょう。

# для удобства запомним адрес в переменную
#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が自動作成される インデックス ブログと тип 役職。 条件付きの類似性を引き出すことができます。つまり、インデックスはデータベースであり、タイプはこのデータベース内のテーブルです。 各タイプには独自のスキームがあります- マッピング、リレーショナルテーブルと同じです。 マッピングは、ドキュメントのインデックス作成時に自動的に生成されます。

# Получим 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 によるドキュメントの取得:

# извлечем документ с 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 は圧縮されたソースドキュメントを保存します。 ソースドキュメント全体ではなく、ID のみが必要な場合は、ソースストレージを無効にすることができます。

追加情報が必要ない場合は、_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" }
    }
  }
}'

タグでフィルタリングする

を使用しております 用語クエリ 指定された単語を含むドキュメント ID を検索するには:

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

全文検索

XNUMX つのドキュメントには、コンテンツ フィールドに次の内容が含まれています。

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

を使用しております 照合クエリ 指定された単語を含むドキュメント ID を検索するには:

# 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 になります。これは、フィルターと完全に一致することを意味します。

アナライザー

アナライザー ソーステキストをトークンのセットに変換するために必要です。
アナライザーは XNUMX つで構成されます トークナイザー およびいくつかのオプション トークンフィルター。 Tokenizer の前にいくつかの文字が続く場合があります 文字フィルター。 トークナイザーは、ソース文字列をスペースや句読点などのトークンに分割します。 TokenFilter は、トークンを変更したり、新しいトークンを削除または追加したりできます。たとえば、単語の語幹のみを残したり、前置詞を削除したり、同義語を追加したりできます。 CharFilter - ソース文字列全体を変更します。たとえば、HTML タグを切り取ります。

ESにはいくつかあります 標準アナライザー。 たとえば、アナライザー ロシア.

使ってみよう API 標準アナライザーとロシア語アナライザーが文字列「子猫に関する面白い話」をどのように変換するかを見てみましょう。

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

標準アナライザーは文字列をスペースに分割し、すべてを小文字に変換します。ロシア語アナライザーは重要でない単語を削除して小文字に変換し、単語の語幹を残します。

ロシア語アナライザーがどの Tokenizer、TokenFilters、CharFilters を使用するかを見てみましょう。

{
  "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 タグがソース文字列から削除され、次にトークナイザー標準によってトークンが分割され、結果のトークンが小文字に移動され、重要でない単語が削除され、残りのトークンは単語の語幹のままになります。

インデックスの作成

上記では、デフォルトのアナライザーについて説明しました。 すべての文字列フィールドに適用されます。 投稿にはタグの配列が含まれているため、タグもアナライザーによって処理されます。 なぜならタグと完全に一致する投稿を探しているので、タグ フィールドの分析を無効にする必要があります。

アナライザーとマッピングを使用して、タグ フィールドの分析が無効になっているインデックス blog2 を作成しましょう。

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 つの投稿をこのインデックス (blog2) に追加しましょう。 この工程は割愛しますので… これは、ブログのインデックスにドキュメントを追加するのと似ています。

式サポート付きの全文検索

別のタイプのリクエストを見てみましょう。

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

なぜならロシア語ステミングを備えたアナライザーを使用しているため、このリクエストは「history」という単語のみを含むすべてのドキュメントを返します。

リクエストには次のような特殊文字が含まれる場合があります。

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

同様の記事レッスンに興味がある場合、新しい記事のアイデアがある場合、または協力の提案がある場合は、個人メッセージまたは電子メールでメッセージを喜んで受け取ります。 [メール保護].

出所: habr.com