Elasticsearch
kibana

はじめての Elasticsearch

この記事は全文検索エンジン「Elasticsearch」の入門チュートリアルです。

公式のチュートリアルビデオ「Elasticsearchを始めてみよう」に沿って進めていきます。

Elasticsearch とは

Elasticsearch は Elastic 社が開発しているオープンソースの全文検索エンジンです。
大量のドキュメントから目的の単語を含むドキュメントを高速に抽出することができます。

Elasticsearch はリレーショナルデータベースではないため、SQL文 は使用できません。
代わりに RESTful インターフェースを使って操作します。

Oracle や MySQL などのリレーショナルデータベースに慣れている人にとっては、最初はとっつきにくく感じるかもしれません。

しかし、Elasticsearch の API はとてもシンプルなので、それほど心配しなくても大丈夫です。

Elastic Stack とは

Elastic Stack は Elasticsearch 関連製品の総称です。
バージョン 2.x 以前は「ELK」と呼ばれていましたが、バージョン 5 からは名称を改め「Elastic Stack」となりました。

製品名 機能
Elasticsearch ドキュメントを保存・検索します。
Kibana データを可視化します。
Logstash データソースからデータを取り込み・変換します。
Beats データソースからデータを取り込みます。
X-Pack セキュリティ、モニタリング、ウォッチ、レポート、グラフの機能を拡張します。

動作環境

  • Mac OS X 10.12.6
  • Java 1.8.0_131
  • Elasticsearch 6.5.0
  • Kibana 6.5.0

インストール

Mac のターミナルを開いて、必要なソフトウェアをインストールします。

Elasticsearch を動作させるには Java 8 が必要です。
すでにインストールされている環境も多いかと思いますが、確認しておきましょう。

$ java -version
$ echo $JAVA_HOME

Elasticsearch、および日本語検索プラグインの kuromoji をインストールします。

$ wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.5.0.tar.gz
$ tar -xzf elasticsearch-6.5.0.tar.gz
$ cd elasticsearch-6.5.0
$ bin/elasticsearch-plugin install analysis-kuromoji

Elasticsearch を起動します。

$ bin/elasticsearch

ブラウザを開いて、http://localhost:9200/ にアクセスしてみましょう。
以下の JSON が表示されれば、起動は成功しています。

{
  "name" : "siyVZnt",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "am2l6fObTvSejEV75SdE-A",
  "version" : {
    "number" : "6.5.0",
    "build_flavor" : "default",
    "build_type" : "tar",
    "build_hash" : "816e6f6",
    "build_date" : "2018-11-09T18:58:36.352602Z",
    "build_snapshot" : false,
    "lucene_version" : "7.5.0",
    "minimum_wire_compatibility_version" : "5.6.0",
    "minimum_index_compatibility_version" : "5.0.0"
  },
  "tagline" : "You Know, for Search"
}

続いて、Kibana をインストールします。

$ wget https://artifacts.elastic.co/downloads/kibana/kibana-6.5.0-darwin-x86_64.tar.gz
$ tar -xzf kibana-6.5.0-darwin-x86_64.tar.gz
$ cd kibana-6.5.0-darwin-x86_64

Kibana を起動します。

$ bin/kibana

ブラウザを開いて、http://localhost:5601/ にアクセスしてみましょう。
以下のような画面が表示されれば、起動は成功しています。
kibana_6_5_0.png

チュートリアルをはじめる前に

このチュートリアルでは、公式のチュートリアルビデオ「Elasticsearchを始めてみよう」に沿って進めていきます。

公式のチュートリアルビデオで使用しているコマンドは GitHub で公開されています。
https://github.com/kosho/elasticsearch-api-usage-examples

この記事で使用しているコマンドも、チュートリアルビデオで使用しているコマンドとほぼ同じですが、GitHub で公開しています。
https://github.com/nskydiving/elasticsearch_examples

Elasticsearch は RESTful インターフェースで操作できますが、このチュートリアルでは Kibana の「Dev Tools」を使用します。

Kibana のメニューの中から「Dev Tools」を選択してください。
以下のような画面が表示されます。

kibana_dev_tools_6_5_0.png

Console の左側エリアにコマンドを入力して実行ボタン(緑色の三角ボタン)をクリックすると、選択されたコマンドが実行され、右側エリアに実行結果が表示されます。

試しに以下のコマンドを実行してみましょう。

GET _search
{
  "query": {
    "match_all": {}
  }
}

右側エリアに実行結果が表示されれば成功です。

※Mac 以外の環境の人は以下のリンクを参照してください。
https://www.elastic.co/jp/downloads/elasticsearch
https://www.elastic.co/jp/downloads/kibana

CRUDオペレーション(RESTful API)

Elasticsearch ではリレーショナルデータベースとは異なる用語を使用しますが、概ね以下のように理解しておけば良いでしょう。

Elasticsearch リレーショナルデータベース
Index データベース
Type テーブル
Document レコード

 ドキュメントを作成

ドキュメントを作成するには、PUT で「/インデックス/タイプ/ドキュメントID」にアクセスし、ドキュメントの中身を JSON で渡します。

コマンド

#    +--- Index name
#    |       +--- Type name
#    |       |     +--- Document ID
#    |       |     |
#    V       V     V
PUT /library/books/1
{
  "title": "Norwegian Wood",
  "name": {
    "first": "Haruki",
    "last": "Murakami"
  },
  "publish_date": "1987-09-04T00:00:00+0900",
  "price": 19.95
}

実行結果

{
  "_index": "library",
  "_type": "books",
  "_id": "1",
  "_version": 1,
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "created": true
}

 ドキュメントを取得

ドキュメントを取得するには、GET で「/インデックス/タイプ/ドキュメントID」にアクセスします。

コマンド

GET /library/books/1

実行結果

{
  "_index": "library",
  "_type": "books",
  "_id": "1",
  "_version": 1,
  "found": true,
  "_source": {
    "title": "Norwegian Wood",
    "name": {
      "first": "Haruki",
      "last": "Murakami"
    },
    "publish_date": "1987-09-04T00:00:00+0900",
    "price": 19.95
  }
}

 ドキュメントIDを指定せずにドキュメントを作成

ドキュメントIDを指定せずにドキュメントを作成すると、自動的にドキュメントIDが割り当てられます。
自動的に割り当てられたドキュメントIDは実行結果で確認できます。

コマンド

POST /library/books/
{
  "title": "Kafka on the Shore",
  "name": {
    "first": "Haruki",
    "last": "Murakami"
  },
  "publish_date": "2002-09-12T00:00:00+0900",
  "price": 19.95
}

実行結果

{
  "_index": "library",
  "_type": "books",
  "_id": "AVvbMpWIT3Zks7DdP3lQ",
  "_version": 1,
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "created": true
}

 「ドキュメントIDを指定せずにドキュメントを作成」の確認

コマンド

GET /library/books/AVvbMpWIT3Zks7DdP3lQ

実行結果

{
  "_index": "library",
  "_type": "books",
  "_id": "AVvbMpWIT3Zks7DdP3lQ",
  "_version": 1,
  "found": true,
  "_source": {
    "title": "Kafka on the Shor",
    "name": {
      "first": "Haruki",
      "last": "Murakami"
    },
    "publish_date": "2002-09-12T00:00:00+0900",
    "price": 19.95
  }
}

 ドキュメントを上書き更新

ドキュメントを上書き更新するには、PUT で「/インデックス/タイプ/ドキュメントID」にアクセスします。

コマンド

PUT /library/books/1
{
  "title": "Norwegian Wood",
  "name": {
    "first": "Haruki",
    "last": "Murakami"
  },
  "publish_date": "1987-09-04T00:00:00+0900",
  "price": 29.95
}

GET /library/books/1

実行結果

{
  "_index": "library",
  "_type": "books",
  "_id": "1",
  "_version": 2,
  "result": "updated",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "created": false
}

 「ドキュメントを上書き更新」の確認

コマンド

GET /library/books/1

実行結果

{
  "_index": "library",
  "_type": "books",
  "_id": "1",
  "_version": 2,
  "found": true,
  "_source": {
    "title": "Norwegian Wood",
    "name": {
      "first": "Haruki",
      "last": "Murakami"
    },
    "publish_date": "1987-09-04T00:00:00+0900",
    "price": 29.95
  }
}

 ドキュメントを部分的に更新

ドキュメントを部分的に更新するには、POST で「/インデックス/タイプ/ドキュメントID/_update」にアクセスし、 JSON で "doc" クエリを指定します。

コマンド

POST /library/books/1/_update
{
  "doc": {
    "price": 10
  }
}

実行結果

{
  "_index": "library",
  "_type": "books",
  "_id": "1",
  "_version": 3,
  "result": "updated",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  }
}

 「ドキュメントを部分的に更新」の確認

コマンド

GET /library/books/1

実行結果

{
  "_index": "library",
  "_type": "books",
  "_id": "1",
  "_version": 3,
  "found": true,
  "_source": {
    "title": "Norwegian Wood",
    "name": {
      "first": "Haruki",
      "last": "Murakami"
    },
    "publish_date": "1987-09-04T00:00:00+0900",
    "price": 10
  }
}

 ドキュメントに項目を追加

コマンド

ドキュメントに項目を追加するには、POST で「/インデックス/タイプ/ドキュメントID/_update」にアクセスし、JSON で "doc" クエリを指定します。

POST /library/books/1/_update
{
  "doc": {
    "price_jpy": 1800
  }
}

実行結果

{
  "_index": "library",
  "_type": "books",
  "_id": "1",
  "_version": 4,
  "result": "updated",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  }
}

 「ドキュメントに項目を追加」の確認

コマンド

GET /library/books/1

実行結果

{
  "_index": "library",
  "_type": "books",
  "_id": "1",
  "_version": 4,
  "found": true,
  "_source": {
    "title": "Norwegian Wood",
    "name": {
      "first": "Haruki",
      "last": "Murakami"
    },
    "publish_date": "1987-09-04T00:00:00+0900",
    "price": 10,
    "price_jpy": 1800
  }
}

 ドキュメントを削除

ドキュメントを削除するには、DELETE で「/インデックス/タイプ/ドキュメントID」にアクセスします。

コマンド

DELETE /library/books/1

実行結果

{
  "found": true,
  "_index": "library",
  "_type": "books",
  "_id": "1",
  "_version": 5,
  "result": "deleted",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  }
}

 「ドキュメントを削除」の確認

コマンド

GET /library/books/1

実行結果

{
  "_index": "library",
  "_type": "books",
  "_id": "1",
  "found": false
}

 インデックスを削除

インデックスを削除するには、DELETE で「/インデックス」にアクセスします。

コマンド

DELETE /library

実行結果

{
  "acknowledged": true
}

 「インデックスを削除」の確認

コマンド

GET /libray/books/2

実行結果

{
  "error": {
    "root_cause": [
      {
        "type": "index_not_found_exception",
        "reason": "no such index",
        "resource.type": "index_expression",
        "resource.id": "library",
        "index_uuid": "_na_",
        "index": "library"
      }
    ],
    "type": "index_not_found_exception",
    "reason": "no such index",
    "resource.type": "index_expression",
    "resource.id": "library",
    "index_uuid": "_na_",
    "index": "library"
  },
  "status": 404
}

ドキュメントの検索

インデックスに保存されたドキュメントを検索します。

下準備として、インデックスを一度削除して、テストデータのドキュメントを作成します。

複数のドキュメントを一気に作成するには、POST で「/インデックス/タイプ/_bulk」にアクセスします。

コマンド

DELETE /library

POST /library/books/_bulk
{"index": {"_id": 1}}
{"title": "The quick brown fox", "price": 5}
{"index": {"_id": 2}}
{"title": "The quick brown fox jumps over the lazy dog", "price": 15}
{"index": {"_id": 3}}
{"title": "The quick brown fox jumps over the quick dog", "price": 8}
{"index": {"_id": 4}}
{"title": "Brown fox and brown dog", "price": 2}
{"index": {"_id": 5}}
{"title": "Lazy dog", "price": 9}

 すべてのドキュメントを検索

すべてのドキュメントを検索するには、GET で「/インデックス/タイプ/_search」にアクセスします。

また、「/library/books/_search?size=3」のように size パラメータで検索件数を指定することもできます。

コマンド

GET /library/books/_search

実行結果

{
  "took": 5,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 5,
    "max_score": 1,
    "hits": [
      {
        "_index": "library",
        "_type": "books",
        "_id": "5",
        "_score": 1,
        "_source": {
          "title": "Lazy dog",
          "price": 9
        }
      },
      {
        "_index": "library",
        "_type": "books",
        "_id": "2",
        "_score": 1,
        "_source": {
          "title": "The quick brown fox jumps over the lazy dog",
          "price": 15
        }
      },
      {
        "_index": "library",
        "_type": "books",
        "_id": "4",
        "_score": 1,
        "_source": {
          "title": "Brown fox and brown dog",
          "price": 2
        }
      },
      {
        "_index": "library",
        "_type": "books",
        "_id": "1",
        "_score": 1,
        "_source": {
          "title": "The quick brown fox",
          "price": 5
        }
      },
      {
        "_index": "library",
        "_type": "books",
        "_id": "3",
        "_score": 1,
        "_source": {
          "title": "The quick brown fox jumps over the quick dog",
          "price": 8
        }
      }
    ]
  }
}

 指定した単語を含むドキュメントを検索

指定した単語を含むドキュメントを検索するには、GET で「/インデックス/タイプ/_search」にアクセスし、JSON で "match" クエリを指定します。

ここでは "title" に "fox" を含むドキュメントを検索しています。

コマンド

GET /library/books/_search
{
  "query": {
    "match": {
      "title": "fox"
    }
  }
}

実行結果

{
  "took": 96,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 4,
    "max_score": 0.2876821,
    "hits": [
      {
        "_index": "library",
        "_type": "books",
        "_id": "1",
        "_score": 0.2876821,
        "_source": {
          "title": "The quick brown fox",
          "price": 5
        }
      },
      {
        "_index": "library",
        "_type": "books",
        "_id": "3",
        "_score": 0.27233246,
        "_source": {
          "title": "The quick brown fox jumps over the quick dog",
          "price": 8
        }
      },
      {
        "_index": "library",
        "_type": "books",
        "_id": "4",
        "_score": 0.2034302,
        "_source": {
          "title": "Brown fox and brown dog",
          "price": 2
        }
      },
      {
        "_index": "library",
        "_type": "books",
        "_id": "2",
        "_score": 0.15329506,
        "_source": {
          "title": "The quick brown fox jumps over the lazy dog",
          "price": 15
        }
      }
    ]
  }
}

 OR条件でドキュメントを検索

OR条件でドキュメントを検索するには、GET で「/インデックス/タイプ/_search」にアクセスし、JSON で "match" クエリを指定します。

ここでは "title" に "quick" または "dog" を含むドキュメントを検索しています。

コマンド

GET /library/books/_search
{
  "query": {
    "match": {
      "title": "quick dog"
    }
  }
}

検索結果

{
  "took": 26,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 5,
    "max_score": 0.7360897,
    "hits": [
      {
        "_index": "library",
        "_type": "books",
        "_id": "2",
        "_score": 0.7360897,
        "_source": {
          "title": "The quick brown fox jumps over the lazy dog",
          "price": 15
        }
      },
      {
        "_index": "library",
        "_type": "books",
        "_id": "3",
        "_score": 0.6531391,
        "_source": {
          "title": "The quick brown fox jumps over the quick dog",
          "price": 8
        }
      },
      {
        "_index": "library",
        "_type": "books",
        "_id": "1",
        "_score": 0.2876821,
        "_source": {
          "title": "The quick brown fox",
          "price": 5
        }
      },
      {
        "_index": "library",
        "_type": "books",
        "_id": "5",
        "_score": 0.25811607,
        "_source": {
          "title": "Lazy dog",
          "price": 9
        }
      },
      {
        "_index": "library",
        "_type": "books",
        "_id": "4",
        "_score": 0.2034302,
        "_source": {
          "title": "Brown fox and brown dog",
          "price": 2
        }
      }
    ]
  }
}

 空白を含む単語を含むドキュメントを検索

空白を含む単語を含むドキュメントを検索するには、GET で「/インデックス/タイプ/_serach」にアクセスし、JSON で 「match_phrase」 クエリを指定します。

ここでは "title" に "quick dog" を含むドキュメントを検索しています。

コマンド

GET /library/books/_search
{
  "query": {
    "match_phrase": {
      "title": "quick dog"
    }
  }
}

実行結果

{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 1,
    "max_score": 0.5446649,
    "hits": [
      {
        "_index": "library",
        "_type": "books",
        "_id": "3",
        "_score": 0.5446649,
        "_source": {
          "title": "The quick brown fox jumps over the quick dog",
          "price": 8
        }
      }
    ]
  }
}

 ドキュメント検索のスコアを表示

ドキュメントを検索するときに、指定された単語との関連性がスコアとして計算されます。

コマンドに explain パラメータを指定することで、どのようにスコアが計算されたか確認することができます。

コマンド

GET /library/books/_search?explain
{
  "query": {
    "match": {
      "title": "quick"
    }
  }
}

実行結果

{
  "took": 5,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 3,
    "max_score": 0.58279467,
    "hits": [
      {
        "_shard": "[library][2]",
        "_node": "-0neWhLWTi-XzbtqlgjU_Q",
        "_index": "library",
        "_type": "books",
        "_id": "2",
        "_score": 0.58279467,
        "_source": {
          "title": "The quick brown fox jumps over the lazy dog",
          "price": 15
        },
        "_explanation": {
          "value": 0.58279467,
          "description": "weight(title:quick in 0) [PerFieldSimilarity], result of:",
          "details": [
            {
              "value": 0.58279467,
              "description": "score(doc=0,freq=1.0 = termFreq=1.0\n), product of:",
              "details": [
                {
                  "value": 0.6931472,
                  "description": "idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:",
                  "details": [
                    {
                      "value": 1,
                      "description": "docFreq",
                      "details": []
                    },
                    {
                      "value": 2,
                      "description": "docCount",
                      "details": []
                    }
                  ]
                },
                {
                  "value": 0.840795,
                  "description": "tfNorm, computed as (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength)) from:",
                  "details": [
                    {
                      "value": 1,
                      "description": "termFreq=1.0",
                      "details": []
                    },
                    {
                      "value": 1.2,
                      "description": "parameter k1",
                      "details": []
                    },
                    {
                      "value": 0.75,
                      "description": "parameter b",
                      "details": []
                    },
                    {
                      "value": 7,
                      "description": "avgFieldLength",
                      "details": []
                    },
                    {
                      "value": 10.24,
                      "description": "fieldLength",
                      "details": []
                    }
                  ]
                }
              ]
            }
          ]
        }
      },
      {
        "_shard": "[library][4]",
        "_node": "-0neWhLWTi-XzbtqlgjU_Q",
        "_index": "library",
        "_type": "books",
        "_id": "3",
        "_score": 0.38080662,
        "_source": {
          "title": "The quick brown fox jumps over the quick dog",
          "price": 8
        },
        "_explanation": {
          "value": 0.38080665,
          "description": "weight(title:quick in 0) [PerFieldSimilarity], result of:",
          "details": [
            {
              "value": 0.38080665,
              "description": "score(doc=0,freq=2.0 = termFreq=2.0\n), product of:",
              "details": [
                {
                  "value": 0.2876821,
                  "description": "idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:",
                  "details": [
                    {
                      "value": 1,
                      "description": "docFreq",
                      "details": []
                    },
                    {
                      "value": 1,
                      "description": "docCount",
                      "details": []
                    }
                  ]
                },
                {
                  "value": 1.3237065,
                  "description": "tfNorm, computed as (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength)) from:",
                  "details": [
                    {
                      "value": 2,
                      "description": "termFreq=2.0",
                      "details": []
                    },
                    {
                      "value": 1.2,
                      "description": "parameter k1",
                      "details": []
                    },
                    {
                      "value": 0.75,
                      "description": "parameter b",
                      "details": []
                    },
                    {
                      "value": 9,
                      "description": "avgFieldLength",
                      "details": []
                    },
                    {
                      "value": 10.24,
                      "description": "fieldLength",
                      "details": []
                    }
                  ]
                }
              ]
            }
          ]
        }
      },
      {
        "_shard": "[library][3]",
        "_node": "-0neWhLWTi-XzbtqlgjU_Q",
        "_index": "library",
        "_type": "books",
        "_id": "1",
        "_score": 0.2876821,
        "_source": {
          "title": "The quick brown fox",
          "price": 5
        },
        "_explanation": {
          "value": 0.2876821,
          "description": "weight(title:quick in 0) [PerFieldSimilarity], result of:",
          "details": [
            {
              "value": 0.2876821,
              "description": "score(doc=0,freq=1.0 = termFreq=1.0\n), product of:",
              "details": [
                {
                  "value": 0.2876821,
                  "description": "idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:",
                  "details": [
                    {
                      "value": 1,
                      "description": "docFreq",
                      "details": []
                    },
                    {
                      "value": 1,
                      "description": "docCount",
                      "details": []
                    }
                  ]
                },
                {
                  "value": 1,
                  "description": "tfNorm, computed as (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength)) from:",
                  "details": [
                    {
                      "value": 1,
                      "description": "termFreq=1.0",
                      "details": []
                    },
                    {
                      "value": 1.2,
                      "description": "parameter k1",
                      "details": []
                    },
                    {
                      "value": 0.75,
                      "description": "parameter b",
                      "details": []
                    },
                    {
                      "value": 4,
                      "description": "avgFieldLength",
                      "details": []
                    },
                    {
                      "value": 4,
                      "description": "fieldLength",
                      "details": []
                    }
                  ]
                }
              ]
            }
          ]
        }
      }
    ]
  }
}

 AND条件でドキュメントを検索

AND条件でドキュメントを検索するには、GET で「/インデックス/タイプ/_search」にアクセスし、JSON で "bool" クエリを指定します。

ここでは、"title" に "quick" と "lazy dog" の両方を含むドキュメントを検索しています。

コマンド

GET /library/books/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "title": "quick"
          }
        },
        {
          "match_phrase": {
            "title": "lazy dog"
          }
        }
      ]
    }
  }
}

実行結果

{
  "took": 21,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 1,
    "max_score": 1.3188844,
    "hits": [
      {
        "_index": "library",
        "_type": "books",
        "_id": "2",
        "_score": 1.3188844,
        "_source": {
          "title": "The quick brown fox jumps over the lazy dog",
          "price": 15
        }
      }
    ]
  }
}

 ドキュメント検索のスコアに重み付け

ドキュメント検索のスコアに重み付けするには、GET で「/インデックス/タイプ/_search」にアクセスし、JSON で "boost" クエリを指定します。

ここでは、"title" に "quick dog" を含むドキュメントのスコアを半分にしています。

コマンド

GET /library/books/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match_phrase": {
            "title": {
              "query": "quick dog",
              "boost": 0.5
            }
          }
        },
        {
          "match_phrase": {
            "title": {
              "query": "lazy dog"
            }
          }
        }
      ]
    }
  }
}

実行結果

{
  "took": 4,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 3,
    "max_score": 0.7360897,
    "hits": [
      {
        "_index": "library",
        "_type": "books",
        "_id": "2",
        "_score": 0.7360897,
        "_source": {
          "title": "The quick brown fox jumps over the lazy dog",
          "price": 15
        }
      },
      {
        "_index": "library",
        "_type": "books",
        "_id": "5",
        "_score": 0.51623213,
        "_source": {
          "title": "Lazy dog",
          "price": 9
        }
      },
      {
        "_index": "library",
        "_type": "books",
        "_id": "3",
        "_score": 0.27233246,
        "_source": {
          "title": "The quick brown fox jumps over the quick dog",
          "price": 8
        }
      }
    ]
  }
}

 ドキュメント検索結果をハイライト表示

ドキュメント検索結果をハイライト表示するには、GET で「/インデックス/タイプ/_search」にアクセスし、JSON で "highlight" クエリを指定します。

検索でヒットした文字列が em タグで囲われて出力されます。

コマンド

GET /library/books/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match_phrase": {
            "title": {
              "query": "quick dog",
              "boost": 0.5
            }
          }
        },
        {
          "match_phrase": {
            "title": {
              "query": "lazy dog"
            }
          }
        }
      ]
    }
  },
  "highlight": {
    "fields": {
      "title": {}
    }
  }
}

実行結果

{
  "took": 211,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 3,
    "max_score": 0.7360897,
    "hits": [
      {
        "_index": "library",
        "_type": "books",
        "_id": "2",
        "_score": 0.7360897,
        "_source": {
          "title": "The quick brown fox jumps over the lazy dog",
          "price": 15
        },
        "highlight": {
          "title": [
            "The quick brown fox jumps over the <em>lazy</em> <em>dog</em>"
          ]
        }
      },
      {
        "_index": "library",
        "_type": "books",
        "_id": "5",
        "_score": 0.51623213,
        "_source": {
          "title": "Lazy dog",
          "price": 9
        },
        "highlight": {
          "title": [
            "<em>Lazy</em> <em>dog</em>"
          ]
        }
      },
      {
        "_index": "library",
        "_type": "books",
        "_id": "3",
        "_score": 0.27233246,
        "_source": {
          "title": "The quick brown fox jumps over the quick dog",
          "price": 8
        },
        "highlight": {
          "title": [
            "The quick brown fox jumps over the <em>quick</em> <em>dog</em>"
          ]
        }
      }
    ]
  }
}

 フィルタリングしてドキュメントを検索

フィルタリングしてドキュメントを検索するには、GET で「/インデックス/タイプ/_search」にアクセスし、JSON で "filter" クエリを指定します。

ここでは、"price" が 5 〜 10 のドキュメントを検索しています。

コマンド

GET /library/books/_search
{
  "query": {
    "bool": {
      "filter": {
        "range": {
          "price": {
            "gte": 5,
            "lte": 10
          }
        }
      }
    }
  }
}

実行結果

{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 3,
    "max_score": 0,
    "hits": [
      {
        "_index": "library",
        "_type": "books",
        "_id": "5",
        "_score": 0,
        "_source": {
          "title": "Lazy dog",
          "price": 9
        }
      },
      {
        "_index": "library",
        "_type": "books",
        "_id": "1",
        "_score": 0,
        "_source": {
          "title": "The quick brown fox",
          "price": 5
        }
      },
      {
        "_index": "library",
        "_type": "books",
        "_id": "3",
        "_score": 0,
        "_source": {
          "title": "The quick brown fox jumps over the quick dog",
          "price": 8
        }
      }
    ]
  }
}

 他のクエリとフィルタリングを組み合わせてドキュメントを検索

他のクエリとフィルタリングを組み合わせてドキュメントを検索するには、GET で「/インデックス/タイプ/_search」にアクセスし、JSON で 他のクエリと "filter" クエリを合わせて指定します。

ここでは、"title" に "lazy dog" を含み、"price" が 5 以上のドキュメントを検索しています。

コマンド

GET /library/books/_search
{
  "query": {
    "bool": {
      "must": [{
        "match_phrase": {
          "title": "lazy dog"
        }
        }],
      "filter": {
        "range": {
          "price": {
            "gte": 5
          }
        }
      }
    }
  }
}

実行結果

{
  "took": 2,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 2,
    "max_score": 0.7360897,
    "hits": [
      {
        "_index": "library",
        "_type": "books",
        "_id": "2",
        "_score": 0.7360897,
        "_source": {
          "title": "The quick brown fox jumps over the lazy dog",
          "price": 15
        }
      },
      {
        "_index": "library",
        "_type": "books",
        "_id": "5",
        "_score": 0.51623213,
        "_source": {
          "title": "Lazy dog",
          "price": 9
        }
      }
    ]
  }
}

Mapping

Elasticsearch はスキーマレスのデータベースですが、あらかじめマッピングを設定しておくこともできます。

下準備として、インデックスを一度削除して、テストデータのドキュメントを作成します。

コマンド

DELETE /library

POST /library/books/_bulk
{"index": {"_id": 1}}
{"title": "The quick brown fox", "price": 5}
{"index": {"_id": 2}}
{"title": "The quick brown fox jumps over the lazy dog", "price": 15}
{"index": {"_id": 3}}
{"title": "The quick brown fox jumps over the quick dog", "price": 8}
{"index": {"_id": 4}}
{"title": "Brown fox and brown dog", "price": 2}
{"index": {"_id": 5}}
{"title": "Lazy dog", "price": 9}

 マッピングを取得

コマンド

GET /library/_mapping

実行結果

{
  "library": {
    "mappings": {
      "books": {
        "properties": {
          "price": {
            "type": "long"
          },
          "title": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          }
        }
      }
    }
  }
}

 マッピングを追加

新しいマッピング "my_new_field" を追加します。

コマンド

PUT /library/books/_mapping
{
  "books": {
    "properties": {
      "my_new_field": {
        "type": "text"
      }
    }
  }
}

実行結果

{
  "acknowledged": true
}

 「マッピングを追加」の確認

コマンド

GET /library/_mapping

実行結果

{
  "library": {
    "mappings": {
      "books": {
        "properties": {
          "my_new_field": {
            "type": "text"
          },
          "price": {
            "type": "long"
          },
          "title": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          }
        }
      }
    }
  }
}

 アナライザを設定してマッピングを追加

マッピングに設定するアナライザは "analyzer" クエリを指定します。

コマンド

PUT /library/books/_mapping
{
  "books": {
    "properties": {
      "english_field": {
        "type": "text",
        "analyzer": "english"
      }
    }
  }
}

実行結果

{
  "acknowledged": true
}

 「アナライザを設定してマッピングを追加」の確認

コマンド

GET /library/_mapping

実行結果

{
  "library": {
    "mappings": {
      "books": {
        "properties": {
          "english_field": {
            "type": "text",
            "analyzer": "english"
          },
          "my_new_field": {
            "type": "text"
          },
          "price": {
            "type": "long"
          },
          "title": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          }
        }
      }
    }
  }
}

 マッピングは変更することはできない

一度追加したマッピングは変更することはできないため、以下のコマンドはエラーとなります。

コマンド

PUT /library/books/_mapping
{
  "books": {
    "properties": {
      "english_field": {
        "type": "double"
      }
    }
  }
}

実行結果

{
  "error": {
    "root_cause": [
      {
        "type": "illegal_argument_exception",
        "reason": "mapper [english_field] of different type, current_type [text], merged_type [double]"
      }
    ],
    "type": "illegal_argument_exception",
    "reason": "mapper [english_field] of different type, current_type [text], merged_type [double]"
  },
  "status": 400
}

 型の違いによる検索結果への影響

「/log/transactions」へ id が「234571」と「1392.223」の2つのドキュメントを追加し、検索条件に「id が 1392 以上」を指定して検索を実行します。

「234571」と「1392.223」の両方が検索にヒットすることが期待されますが、実際には「234571」しかヒットしません。

コマンド

POST /log/transactions
{
  "id": 234571
}

POST /log/transactions
{
  "id": 1392.223
}

GET /log/transactions/_search
{
  "query": {
    "bool": {
      "filter": {
        "range": {
          "id": {
            "gt": 1392
          }
        }
      }
    }
  }
}

実行結果

# POST /log/transactions
{
  "_index": "log",
  "_type": "transactions",
  "_id": "AVvcjJRrT3Zks7DdP3l2",
  "_version": 1,
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "created": true
}

# POST /log/transactions
{
  "_index": "log",
  "_type": "transactions",
  "_id": "AVvcjJSuT3Zks7DdP3l3",
  "_version": 1,
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "created": true
}

# GET /log/transactions/_search
{
  "took": 4,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 1,
    "max_score": 0,
    "hits": [
      {
        "_index": "log",
        "_type": "transactions",
        "_id": "AVvcP2FmT3Zks7DdP3lq",
        "_score": 0,
        "_source": {
          "id": 234571
        }
      }
    ]
  }
}

 「型の違いによる検索結果への影響」の確認

マッピングを取得すると "id" の型は long 型です。

また、すべての "log" インデックスを検索すると、"id" の型が合わない「1392.223」のドキュメントも追加されていることが分かります。

コマンド

GET /log/_mapping

GET /log/_search

実行結果

# GET /log/_mapping
{
  "log": {
    "mappings": {
      "transactions": {
        "properties": {
          "id": {
            "type": "long"
          }
        }
      }
    }
  }
}

# GET /log/_search
{
  "took": 0,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 2,
    "max_score": 1,
    "hits": [
      {
        "_index": "log",
        "_type": "transactions",
        "_id": "AVvcjdU-T3Zks7DdP3l4",
        "_score": 1,
        "_source": {
          "id": 234571
        }
      },
      {
        "_index": "log",
        "_type": "transactions",
        "_id": "AVvcjdVfT3Zks7DdP3l5",
        "_score": 1,
        "_source": {
          "id": 1392.223
        }
      }
    ]
  }
}

Analysis

Elasticsearch がどのように文字列を分析しているのか確認することができます。

下準備として、インデックスを一度削除して、テストデータのドキュメントを作成します。

コマンド

DELETE /library

POST /library/books/_bulk
{"index": {"_id": 1}}
{"title": "The quick brown fox", "price": 5}
{"index": {"_id": 2}}
{"title": "The quick brown fox jumps over the lazy dog", "price": 15}
{"index": {"_id": 3}}
{"title": "The quick brown fox jumps over the quick dog", "price": 8}
{"index": {"_id": 4}}
{"title": "Brown fox and brown dog", "price": 2}
{"index": {"_id": 5}}
{"title": "Lazy dog", "price": 9}

 文字列の分析結果を表示

"Brown fox brown dog" の分析結果を表示します。

"Brown" "fox" "brown" "dog" の 4 つの単語に分解されていることが分かります。 

コマンド

GET /library/_analyze
{
  "tokenizer": "standard",
  "text": "Brown fox brown dog"
}

実行結果

{
  "tokens": [
    {
      "token": "Brown",
      "start_offset": 0,
      "end_offset": 5,
      "type": "<ALPHANUM>",
      "position": 0
    },
    {
      "token": "fox",
      "start_offset": 6,
      "end_offset": 9,
      "type": "<ALPHANUM>",
      "position": 1
    },
    {
      "token": "brown",
      "start_offset": 10,
      "end_offset": 15,
      "type": "<ALPHANUM>",
      "position": 2
    },
    {
      "token": "dog",
      "start_offset": 16,
      "end_offset": 19,
      "type": "<ALPHANUM>",
      "position": 3
    }
  ]
}

 フィルターを指定して文字列の分析結果を表示

"filter" クエリに "lowercase" を指定すると、文字列を小文字に変換して分析します。

最初の "Brown" が "brown" として分析されていることが分かります。 

コマンド

GET /library/_analyze
{
  "tokenizer": "standard",
  "filter": ["lowercase"],
  "text": "Brown fox brown dog"
}

実行結果

{
  "tokens": [
    {
      "token": "brown",
      "start_offset": 0,
      "end_offset": 5,
      "type": "<ALPHANUM>",
      "position": 0
    },
    {
      "token": "fox",
      "start_offset": 6,
      "end_offset": 9,
      "type": "<ALPHANUM>",
      "position": 1
    },
    {
      "token": "brown",
      "start_offset": 10,
      "end_offset": 15,
      "type": "<ALPHANUM>",
      "position": 2
    },
    {
      "token": "dog",
      "start_offset": 16,
      "end_offset": 19,
      "type": "<ALPHANUM>",
      "position": 3
    }
  ]
}

 複数のフィルターを指定して文字列の分析結果を表示

"filter" クエリに "lowercase" と "unique" を指定すると、文字列を小文字に変換し、さらに重複する単語を削除して分析します。

"Brown" が小文字に変換され、重複した 2 つ目の "brown" は削除されていることが分かります。

コマンド

GET /library/_analyze
{
  "tokenizer": "standard",
  "filter": ["lowercase","unique"],
  "text": "Brown brown brown fox brown dog"
}

実行結果

{
  "tokens": [
    {
      "token": "brown",
      "start_offset": 0,
      "end_offset": 5,
      "type": "<ALPHANUM>",
      "position": 0
    },
    {
      "token": "fox",
      "start_offset": 18,
      "end_offset": 21,
      "type": "<ALPHANUM>",
      "position": 1
    },
    {
      "token": "dog",
      "start_offset": 28,
      "end_offset": 31,
      "type": "<ALPHANUM>",
      "position": 2
    }
  ]
}

 トークナイザの違いによる分析結果を表示

トークナイザに "standard" を指定した場合と、"letter" を指定した場合で、分析結果を比較します。

"quick.brown_fox" の部分の分析結果が異なることが分かります。

コマンド

GET /library/_analyze
{
  "tokenizer": "standard",
  "filter": ["lowercase"],
  "text": "THE quick.brown_FOx Jumped! $19.95 @ 3.0"
}

GET /library/_analyze
{
  "tokenizer": "letter",
  "filter": ["lowercase"],
  "text": "THE quick.brown_FOx Jumped! $19.95 @ 3.0"
}

実行結果

# GET /library/_analyze
{
  "tokens": [
    {
      "token": "the",
      "start_offset": 0,
      "end_offset": 3,
      "type": "<ALPHANUM>",
      "position": 0
    },
    {
      "token": "quick.brown_fox",
      "start_offset": 4,
      "end_offset": 19,
      "type": "<ALPHANUM>",
      "position": 1
    },
    {
      "token": "jumped",
      "start_offset": 20,
      "end_offset": 26,
      "type": "<ALPHANUM>",
      "position": 2
    },
    {
      "token": "19.95",
      "start_offset": 29,
      "end_offset": 34,
      "type": "<NUM>",
      "position": 3
    },
    {
      "token": "3.0",
      "start_offset": 37,
      "end_offset": 40,
      "type": "<NUM>",
      "position": 4
    }
  ]
}

# GET /library/_analyze
{
  "tokens": [
    {
      "token": "the",
      "start_offset": 0,
      "end_offset": 3,
      "type": "word",
      "position": 0
    },
    {
      "token": "quick",
      "start_offset": 4,
      "end_offset": 9,
      "type": "word",
      "position": 1
    },
    {
      "token": "brown",
      "start_offset": 10,
      "end_offset": 15,
      "type": "word",
      "position": 2
    },
    {
      "token": "fox",
      "start_offset": 16,
      "end_offset": 19,
      "type": "word",
      "position": 3
    },
    {
      "token": "jumped",
      "start_offset": 20,
      "end_offset": 26,
      "type": "word",
      "position": 4
    }
  ]
}

 日本語を含むドキュメントの分析結果を表示

Elasticsearch は標準で日本語に対応したトークナイザがインストールされないため 、kuromoji プラグインをインストールしておく必要があります。

コマンド

GET /library/_analyze
{
  "tokenizer": "kuromoji_tokenizer",
  "text": "記者が汽車で帰社した"
}

実行結果

{
  "tokens": [
    {
      "token": "記者",
      "start_offset": 0,
      "end_offset": 2,
      "type": "word",
      "position": 0
    },
    {
      "token": "が",
      "start_offset": 2,
      "end_offset": 3,
      "type": "word",
      "position": 1
    },
    {
      "token": "汽車",
      "start_offset": 3,
      "end_offset": 5,
      "type": "word",
      "position": 2
    },
    {
      "token": "で",
      "start_offset": 5,
      "end_offset": 6,
      "type": "word",
      "position": 3
    },
    {
      "token": "帰社",
      "start_offset": 6,
      "end_offset": 8,
      "type": "word",
      "position": 4
    },
    {
      "token": "し",
      "start_offset": 8,
      "end_offset": 9,
      "type": "word",
      "position": 5
    },
    {
      "token": "た",
      "start_offset": 9,
      "end_offset": 10,
      "type": "word",
      "position": 6
    }
  ]
}