The Definitive Guide から学ぶ Elasticsearch(You Know, for Search…)

前回の続き。このペースだと終わらないのでペースアップ。

sfujimoto.hatenablog.com

You Know, for Search…

ここでは Elasticsearch の概要を学びます。Elasticsearch は Apache Lucene を内部で利用した検索エンジンです。Apache LuceneJava 実装の OSS 検索ライブラリです。Elasticsearch だけでなく、Apache Solr でも利用されています。

Apache Lucene 自体でも以下のような機能を持ちます。

しかし、Apache Lucene 自体では以下のような機能は持ちません。

そこで Elasticsearch で Apache Lucene をラップすることで以下を可能としています。

  • RESTful API による操作
    • コマンドベースの操作(Linux であれば、curlコマンドなどで簡単操作 )
    • 各種言語の SDK が提供されていて、Java 言語に限らずその他言語でのデータ操作
  • クラスタリングによるデータのレプリケーションや水平スケールの拡張性

Installing and Running Elasticsearch

ここでは Elasticsearch のインストール・起動について学びます。Elasticsearch の動作要件は最新バージョンの Java がインストールされていることだけです。Java さえインストールされていれば、LinuxWindowsMac どこでも動作します。インストールも非常に簡単です。OS に応じたパッケージをダウンロードし、起動プログラムを実行するだけでインストールが完了します。

Sense という Elasticsearch の操作を簡単に行うことができる Kibana のプラグインのインストール方法が合わせて紹介されていますが、Kibana 5.0.0 以降から Console という名称で標準搭載しています。これから Elasticsearch を触られる方は気にしなくていいです。ただ Console は非常に便利なのでコマンドベースで操作している方は Console を利用されることをオススメします。

ここでは Elasticsearch の操作について学びます。最初に Java Client について紹介されていますが、今は HTTP ベースのクライアントがリリースされていますので紹介されているのは気にしなくていいです。Transport Client に関しても今後は新機能は実装されなくなるのでこれから使い始める方は Java REST Client を覚えましょう。

ここからが本題です。Elasticsearch は RESTful API で操作します。Elasticsearch がデフォルト設定で LISTEN するポートは tpc/9200 です。操作内容は HTTPメソッド、URLパス、JSON 形式の HTTP リクエストボディで決まります。いくつかcurlコマンドベースの例です。

index という名前のインデックス作成
$ curl -XPUT http://localhost:9200/index
index という名前のインデックスの設定内容取得
$ curl -XGET http://localhost:9200/index
インデックス「index」のタイプ「type」に ID「1」でフィールド「name」の値が「elasticsearch」のデータを登録
$ curl -XPUT http://localhost:9200/index/type/1 -d '{
  "name": "elasticsearch"
}'
インデックス「index」を検索
$ curl -XGET http://localhost:9200/index/_search -d '{
  "query": {
    "match": {
      "name": "elasticsearch"
    }
  }
}'

Document Oriented

ここでは Elasticsearch が扱うデータについて学びます。Elasticsearch はドキュメントベース(JSON 形式)でデータを操作、管理します。ドキュメントベースは非常に扱い易いです。RDBMS ではシステム上データを扱う際にそのまま扱うことはほとんどできず、データを正規化して、分割して扱う必要がありました。Elasticsearch では配列やオブジェクトをそのまま扱うことができます。また列指向でデータをインデキシングするため、高パフォーマンスでの検索、ソート、フィルタリングを実現できます。

Finding Your Feet

ここから何節か跨いでサンプルデータを扱うことで Elasticsearch を学んでいきます。サンプルデータは従業員のデータです。HR では従業員のデータを様々な用途で検索します。

Indexing Employee Documents

ここでは Elasticsearch へのデータの登録について学びます。まずは Elasticsearch にサンプルデータを格納します。Elasticsearch にデータを格納することをインデキシングといいます。インデキシングの例は上記でサンプルのコマンドで紹介した通りです。インデックス、タイプ、ID、フィールドなどは下記をご参照ください。

Elasticsearch は 3種類のインデックスという単語が登場します。

  1. 論理的なデータ格納先となるインデックス Elasticsearch の論理構成は RDBMS の論理構成と対比すると理解が早まります。
RDBMSの用語 Elasticsearch
データベース インデックス
テーブル タイプ
カラム フィールド
主キー ID
  1. データ登録のインデックス これは RDBMS の INSERT のようなものです。どちらかというとインデキシングと言われることが多いです。

  2. 転置インデックス 全文検索の手法の一つです。転置インデックス全文検索と同じポジションなのが Grep全文検索(順次走査検索)です。

Retrieving a Document

ここでは Elasticsearch からのデータの取得について学びます。登録したデータを取得します。Elasticsearch は検索はもちろんですが、インデックス名、タイプ名、ID を指定することで一つのデータを取得することもできます。

Search Lite

ここでは Elasticsearch からのデータの検索について学びます。_search API により検索できます。

  • 何も指定しない場合、全件検索します。
  • クエリストリングにqを検索キーワードを指定することで、簡易な条件の検索ができます。
  • HTTP リクエストボディに JSON 形式の Query DSL を指定することで、複雑な検索、集計などを行うことができます。
    • ほとんどはこの方法で検索します。

Search with Query DSL

ここでは Query DSL を利用したデータの検索について学びます。Talking to Elasticsearch で記載したように JSON 形式で Query DSL を記述します。例えば下記のような検索 API

GET /megacorp/employee/_search
{
    "query" : {
        "match" : {
            "last_name" : "Smith"
        }
    }
}

インデックス「megacorp」、タイプ「employee」にあるデータからフィールド「last_name」が「Smith」のデータを検索します。

More-Complicated Searches

ここでは Query DSL についてもう少し学びます。先ほどは単純に名前の文字列を検索しました。Elasticsearch は文字列だけではありません。数値も扱うことができます。

GET /megacorp/employee/_search
{
    "query" : {
        "bool": {
            "must": {
                "match" : {
                    "last_name" : "smith" 
                }
            },
            "filter": {
                "range" : {
                    "age" : { "gt" : 30 } 
                }
            }
        }
    }
}

先ほどの条件に加え、age(年齢)が 30以上のデータを取得しています。JSON が複雑になりましたが、一旦ここでは数値のフィルタリングもできるということを覚えてください。もちろん数値だけではなく、日付や時刻、珍しいところでは位置情報、IPアドレスなども扱うことができます。

Full-Text Search

ここでは転置インデックス全文検索について学びます。RDBMS では物足りなくなり、Elasticsearch を利用する理由の一つになります。RDBMS でも全文検索できます。RDBMS ではLIKE句を利用することで順次走査型全文検索できます。しかし、順次操作型全文検索ではLIKE句ではインデックスを張れないため、データ量が多くなるにつれて全文検索のパフォーマンスが劣化が早いです。また含まれるか、含まれないかの Yes/No しか判断できないため、検索にヒットしたデータがどのくらい検索者の希望するものに近いのか判断することができません。転置インデックス全文検索を利用することで全文検索のインデックス化によるパフォーマンス向上、ランキングによる検索データのスコアリングを行うことができます。詳細は別の章で説明することになるかと思いますのでここでは Elasticsearch だとこんなことができるのかと覚える程度で大丈夫です。

RDBMS でも転置インデックス全文検索できるものもあります)

GET /megacorp/employee/_search
{
    "query" : {
        "match" : {
            "about" : "rock climbing"
        }
    }
}

about フィールドに rock climbing というキーワードで検索します。

{
   ...
   "hits": {
      "total":      2,
      "max_score":  0.16273327,
      "hits": [
         {
            ...
            "_score":         0.16273327, 
            "_source": {
               "first_name":  "John",
               "last_name":   "Smith",
               "age":         25,
               "about":       "I love to go rock climbing",
               "interests": [ "sports", "music" ]
            }
         },
         {
            ...
            "_score":         0.016878016, 
            "_source": {
               "first_name":  "Jane",
               "last_name":   "Smith",
               "age":         32,
               "about":       "I like to collect rock albums",
               "interests": [ "music" ]
            }
         }
      ]
   }
}

Elasticsearch の match クエリはデフォルト「OR検索」のため、「rock」、「climbing」のいずれかが含むデータを返しますが、両方を含むデータの方がスコアリングにより「_score」の値が大きくなっています。

Phrase Search

ここではフレーズ検索について学びます。先ほどロッククライミングで検索したら、ロッククライミングと音楽のロックが検索ヒットしました。ただ音楽のロックは期待したことではありません。ここではノイズにしかなりません。rock climbing を連語と識別して検索方法にフレーズ検索があります。フレーズ検索は Query DSL で指定可能です。

GET /megacorp/employee/_search
{
    "query" : {
        "match_phrase" : {
            "about" : "rock climbing"
        }
    }
}

matchmatch_phraseになっただけですね。簡単です。

{
   ...
   "hits": {
      "total":      1,
      "max_score":  0.23013961,
      "hits": [
         {
            ...
            "_score":         0.23013961,
            "_source": {
               "first_name":  "John",
               "last_name":   "Smith",
               "age":         25,
               "about":       "I love to go rock climbing",
               "interests": [ "sports", "music" ]
            }
         }
      ]
   }
}

今度は音楽のロックは返ってきませんでした

Highlighting Our Searches

ここでは Elasticsearch のハイライト機能について学びます。例えば、Google 検索であるキーワードで検索した時、大体全て見きれない多くの結果が返ってきます。あなたはその多くある結果から該当するページを探す時、どう探していますか?上から順に見る方もいるかもしれません、タイトルで判断する方もいるかもしれません。タイトルの下の3,4行の説明文を見ていませんか?説明文には検索キーワードを含む文が表示されています。文脈を読むことができ、検索キーワードから期待する結果に近いのかどうか大きな判断材料となります。Elasticsearch ではハイライト機能という標準機能でそのような結果を返すことができます。また検索キーワードは特定の HTML タグを付与することで強調表示することも可能です。

GET /megacorp/employee/_search
{
    "query" : {
        "match_phrase" : {
            "about" : "rock climbing"
        }
    },
    "highlight": {
        "fields" : {
            "about" : {}
        }
    }
}

先ほどのmatch_phraseクエリにハイライトの Query DSL を付与するだけです。

{
   ...
   "hits": {
      "total":      1,
      "max_score":  0.23013961,
      "hits": [
         {
            ...
            "_score":         0.23013961,
            "_source": {
               "first_name":  "John",
               "last_name":   "Smith",
               "age":         25,
               "about":       "I love to go rock climbing",
               "interests": [ "sports", "music" ]
            },
            "highlight": {
               "about": [
                  "I love to go <em>rock</em> <em>climbing</em>" 
               ]
            }
         }
      ]
   }
}

文が短いのであまりありがたみが分からない結果となりましたが、rock と climbing にタグが付与されました。

Analytics

ここでは集計機能(Aggregation)について学びます。RDBMSSQL では GROUP BY 句により、データ件数や数値データの合算などを集計することができます。Elasticsearch でも集計できます。Query DSL に Aggregation という機能があります。

GET /megacorp/employee/_search
{
  "aggs": {
    "all_interests": {
      "terms": { "field": "interests.keyword" }
    }
  }
}

aggsは Aggregation 機能の宣言です。all_interestsは変数名、メソッド名みたいなものです。termsは Terms Aggregation(Term の集計)を利用する宣言です。fieldは Terms Aggregation の必須オプションで集計対象とするフィールドを指定します。今回はinterests.keywordフィールドを指定しています。

{
   ...
   "hits": { ... },
   "aggregations": {
      "all_interests": {
         ...
         "buckets": [
            {
               "key": "music",
               "doc_count": 2
            },
            {
               "key": "forestry",
               "doc_count": 1
            },
            {
               "key": "sports",
               "doc_count": 1
            }
         ]
      }
   }
}

フィールドinterests.keywordからキーワード毎のデータ件数を集計します。musicを含むデータが 2件、forestrysportsを含むデータが 1件づつあります。

検索結果を集計することもできます。

GET /megacorp/employee/_search
{
  "query": {
    "match": {
      "last_name": "smith"
    }
  },
  "aggs": {
    "all_interests": {
      "terms": {
        "field": "interests.keyword"
      }
    }
  }
}

集計結果を更に集計することもできます。

GET /megacorp/employee/_search
{
    "aggs" : {
        "all_interests" : {
            "terms" : {
                "field" : "interests.keyword"
            },
            "aggs" : {
                "avg_age" : {
                    "avg" : { "field" : "age" }
                }
            }
        }
    }
}

これは興味があること毎の従業員の平均年齢を集計しています。

Tutorial Conclusion

ここではサンプルデータを使った説明のまとめです。ここまでで紹介したことは Elasticsearch の機能のほんの一部です。他にも様々な検索、集計ができますが、ここまでで学んだ Query DSL が変わるだけで複雑にはなりません。

Distributed Nature

ここでは Elasticsearch の分散性について学びます。チュートリアルのデータ量では意識する必要ありませんでしたが、Elasticsearch は数百(または数千)のサーバーにスケールアウトし、ペタバイトのデータを処理できます。Elasticsearch は簡単に複数ノードによるクラスタを組むことができます。