Elasticsearchのパフォーマンスについて考える

Elasticsearchのパフォーマンスについて考える

はじめに

こんにちは。

今回はElasticsearchのパフォーマンスについて考えたいと思います。

Elasticserarchはオープンソースの全文検索エンジンです。 https://github.com/elastic/elasticsearch

Webサービスでは、何かしらの検索機能があるケースが多いと思います。

ECサイトにしてもSNSにしてもDBに接続しデータを扱う以上、Webサービスと検索は親密な関係にあると考えています。

それほどデータ数も多くなく、検索の条件も複雑ではない場合SQLを使って検索機能を作ることが一般的でこれはよくあるパターンだと思います。

しかし、例えばECサイトの場合、商品の説明や商品のカテゴリ、商品の値段などなど。検索対象のデータというのはどんどん増えていきます。 現在では、クライアント側のマシンスペックの向上(PCやスマホ)やネットワーク速度の向上、クラウドの普及による大規模なデータ管理などにより、扱えるデータは益々増えていると感じています。

そのため、通常のSQLではやりたいことができない、もしくはやりたいことパフォーマンスがでないというケースが出てきます。

そこで、Elasticsearchのような検索に特化したものが必要になるということだと思います。

僕自身もElasticsearchを使う機会があり、Elasticsearchを使う上でのパフォーマンスについて考えることがあったのでそのことについて書きたいと思います。

大まかな流れ

ElasticsearchではRDBと同様にElasticsearch自体にINDEXという形で、データ構造の定義と実際のデータを投入をする必要があります。

詳しい流れはここでは省きますが、簡単に試せるようにDockerを使った環境を作成しました。↓ https://github.com/hirotoyoshidome/elasticsearch-query-etc

INDEXとデータを投入したら検索ができるようになります。

直接コマンドラインからcurlでクエリを実行することもできますし、「Kibana」というElasticsearchを開発しているelastic社が出しているGUIのツールもあるので、GUIからクエリを実行することも可能です。 https://github.com/elastic/kibana

クエリの結果はJSON形式で受け取ることができるので、Webサービスにも組み込みやすいと思います。

また、プログラムからElasticsearchに接続する場合は、各メジャーな言語のクライアントライブラリも充実しているため、そちらを使うのが良いと思います。 https://www.elastic.co/guide/en/elasticsearch/client/index.html

実際にハマったパフォーマンスについて

1.INDEXを深いネスト構造に設計してしまう

これはネットでも検索するとよくヒットする事例です。

Elasticsearchでは、データ構造をネストする形で保持することが可能です。

関係としては親子になるようなデータの場合、↓のようにネストした形でデータを保持させたいケースがあると思います。

{
    "store_id": 1,
    "store_name": "sample",
    "employee": [
        {
            "name": "Mike",
            "age": 30
        },
        {
            "name": "John",
            "age": 25
        }
    ],
    "location": {
        "prefecture": "Tokyo",
        "city": "Minato-ku"
    }
}

このネストが深くなっていくと検索パフォーマンスが低下します。

データ数が少ない時はそれほどパフォーマンスの劣化を感じなくても増えてくると顕著に劣化します。

そのためネストは深くならないように注意した方が良いです。(データ構造の変更は大変なので最初に知っておく方が良いですね。。)

ちなみに検索にヒットした子要素をinner_hitsというクエリを使って取得することが可能です。 このinner_hitsもデータ数が増えるとパフォーマンスが劣化してくるので調整が必要な部分だと思います。

2.条件を絞り込みせずにaggregationsしてしまう

これはSQLでも同じことが言えますね。

aggregations はデータを集約する時のクエリです。

SQLだと GROUP BY に相当します。

SQでも同様ですが、集約する前にデータを絞り込みしておく方がパフォーマンスが良くなります。

Elasticsearchではaggregationsを使ったあとに絞り込みをすることができますが、こちらはパフォーマンスが良くないです。(SQLだと HAVING にあたる操作)

そのため、可能な限り先に条件を絞り込みしておいて、aggregationsで集約するという方がパフォーマンスが向上します。

(SQLで例えるならばWHEREで絞りこみをした上でGROUP BYするという意味です。)

3.scriptを用いたソートでパラメータをscript作成タイミングで動的に作成してしまう

ElasticsearchではINDEXのデータ構造で定義したフィールドの昇順、降順でソートをすることが可能です。

しかし、実際に利用してみるともっと複雑な条件でソートしたいときってあると思います。

その時に利用できるのがscriptという構文です。

scriptを使うと Painless というJavaのような言語でプログラムを書くことができます。

Painless で扱えるデータはINDEXの中にあるデータとは別にパラメータとしてデータを渡すことが可能です。

このPainlessは実際に使われる時にコンパイルされますが、この時パラメータをPainlessの中に直接埋め込んでしまうと当然scriptの中身自体が変わるため再度コンパイルが行われます。

再度コンパイルが行われるとパフォーマンスが悪くなります。

また、一定時間内のコンパイル回数は制限がありますので現実的ではありません。(緩和することは可能です。)

そのため、scriptにパラメータを渡す場合はしっかりとparamの要素を指定して値を渡す必要があります。

param要素を指定してパラメータを渡すことでscriptの中身自体は変更されていないため再度コンパイルが走ることはなくなります。

最後に

簡単ではありますが、実際にElasticsearchを触っていてパフォーマンス関連でハマった部分について書いてみました。

Elasticsearchは非常に一般的な全文検索エンジンであり、ドキュメンタリも充実しています。

そのため、触る際はまずドキュメントの中身をしっかり把握してから実装していくのが大切だと実感しています。

今回は触れていませんが、同義語辞書を使った検索ワードの揺れに対応することもできます。(これはいつか書きたい。)

例えば、「ジーパン」「デニム」「ジーンズ」のような同じ意味だけど言葉が違うケースに対応するのはSQLだと困難です。 このようなケースに対して辞書を準備することでElasticsearchでは簡単に同義語としてヒットさせることが可能になります。

僕自身もまだまだElasticsearchについて知らないことが多く、勉強が必要だと感じていますが、今回の記事が役に立てば幸いです。