$30 off During Our Annual Pro Sale. View Details »

Elasticsearch クエリとスキーマ定義の細かい話

jtodo
February 13, 2015

Elasticsearch クエリとスキーマ定義の細かい話

http://elasticsearch.doorkeeper.jp/events/19923

第8回elasticsearch勉強会 #elasticsearch #elasticsearchjp
2015-02-13(金)19:30 - 21:30

[訂正 2015/02/13]
負荷試験で心にとめていることの誤字を修正

jtodo

February 13, 2015
Tweet

Other Decks in Technology

Transcript

  1. 藤堂 淳也 / @junya_todo
    クエリとスキーマ定義の細かい話
    \すごい/
    Elasticsearch
    2015/02/13

    View Slide

  2. 先日、静岡の焼津へ旅行してきました
    資料は @junya_todo

    View Slide

  3. すばらしいオーシャンビュー!
    資料は @junya_todo

    View Slide

  4. 仕事も忘れ・・?
    資料は @junya_todo

    View Slide

  5. !!(そうだ、ES勉強会だ)
    資料は @junya_todo

    View Slide

  6. 自己紹介
    藤堂 淳也
    株式会社ドワンゴ
    Twitter
    Qiita
    jtodo (@junya_todo)
    jtodo
    検索チームに所属、ES勉強会は聴く側に5回
    プラットフォーム事業部 サービス基盤開発部 汎用コアセクション

    View Slide

  7. 発 表 の テ ー マ
    Elasticsearchは検索基盤として運用しやすい
    1 検 索 基 盤 と し て 運 用 し や す い 理 由
    要件に柔軟|よく気がつく公式ドキュメント
    2 要 件 対 応 | 簡 単 な ケ ー ス
    フィールド追加|データ受け入れ|クエリ受け入れ
    4 まとめ
    3 要 件 対 応 | 複 雑 な ケ ー ス
    クエリとスキーマ定義の細かい話|負荷検証

    View Slide

  8. の全サービスを統合する検索基盤へ移行中
    統合検索基盤は、Elasticsearchに支えられている
    これまでは、各サービスで個別に検索を開発していた
    Elasticsearch
    背景

    View Slide

  9. Elasticsearchは要件に柔軟
    ...etc
     新しいコンテンツが増えたから検索のフィルタを増やしたい
     他サービスのデータを増やしたい
    ドワンゴの検索基盤は、多くのサービスと連携している
    当然、運用していくなかでサービス側から要望が出てくる
    フィールドの追加の対応が多いため、追加が楽だと運用も楽
     多様な無停止スキーマ変更の手段
     RESTなので手順が書きやすい|自動化しやすい

    View Slide

  10. よく気がつく公式ドキュメント
    困りごとはオンラインの公式ガイドラインで調べれば大抵みつかる
    特に検索が便利
    Elasticsearch Reference Elasticsearch - The Definitive Guide
    初心者から中級者向けガイド 初心者から上級者向けガイド
    イメージ

    View Slide

  11. 要件への対応|簡単なケース

    View Slide

  12. 要件への対応|簡単なケース
    フィールド
    追加
    データ
    受け入れ
    完了
    検索基盤では、フィールド追加は、以下のような手順で実施する
    クエリ
    受け入れ

    View Slide

  13. フィールドの追加
    データ長を決める
    int(10), varchar(128), text
    index.mapper.dynamic: false
    { }
    { }
    DDL
    検索基盤スキーマ(json)
    ES Index
    Mysqlへ
    Elasticsearchへ
    PUT _mapping API
    PUT _settings API
    Rolling restart
    ALTER TABLE
    indexするか|storeだけするか
    文字列型ならアナライザを決める
    検索対象なのか格納するだけなのか
    追加するフィールドの型を決める
    boolean, integer, long, float, date, string
    社内製アナライザ(kuromoji移行中?)
    前提として
    ○ フィールド追加時に決めること

    View Slide

  14. データの受け入れ
    ○ データ送信フロー (niconicoの検索を支えるElasticsearch by @shoito から引用)

    View Slide

  15. データの受け入れ
    ○ データ送信フロー (niconicoの検索を支えるElasticsearch by @shoito から引用)
    Indexer
     Java api
    transport client で簡単に
    bulk API
    Elasticsearch
     refresh_interval
    15s
    まとまった数をキューから
    取り出して、indexing
    1分以内反映という
    性能要件から

    View Slide

  16. データの受け入れ
    ○ データ送信フロー (niconicoの検索を支えるElasticsearch by @shoito から引用)


    ① サービス側から新しいフィールドのデータを送信してもらう
    ② 検索基盤スキーマと比べて正しいフィールドのみElasticsearchへ
    デプロイするとElasticsearchへIndexingが開始される

    View Slide

  17. クエリの受け入れ
    ○ 同様に検索基盤スキーマで管理
    デプロイするとElasticsearchへのクエリに含めることができるようになる
    ○ 利用しているクエリ
    query, filter, sort, aggregations など基本的なものだけ

    View Slide

  18. クエリの受け入れ
    ○ 利用しているクエリ
    Query match query フィルタない時に利用
    filtered query フィルタある時に利用
    bool query
    Filter term filter
    query filter not_analyzrdな文字列でのフィルタなど
    range filter
    and / or / not filter
    Sort sort / function score sort 整数、日時、not_analyzrdな文字列など
    Aggregations Bucketing terms aggs
    Metric top hits aggs
    max / min aggs
    cardinality aggs グルーピング後の件数試算

    View Slide

  19. 要件への対応|簡単なケース
    フィールド
    追加
    データ
    受け入れ
    完了
    検索基盤では、フィールド追加は、以下のような手順で実施する
    クエリ
    受け入れ

    View Slide

  20. 要件への対応|複雑なケース

    View Slide

  21. 要件への対応|複雑なケース
    フィールド
    追加
    データ
    受け入れ
    完了
    フィールド追加のフローは同じだが、事前に調査や検証が必要になる
    調査
    要件定義 検証
    クエリ
    受け入れ
    事前に

    View Slide

  22. 様々な要件|調査
    テキスト検索のノイズを減らしたい
    環境依存文字(Unicode)で検索したい
    ある時点の検索結果を並べ替えつつ全件取得したい
    フィールドの追加についての調査
    クエリの受け入れについての調査

    View Slide

  23. テキスト検索のノイズを減らしたい
    『マリオ』で検索したら『リオでカーニバルが始まりました』が引っかかった
    マリオ出てない!ナンデ!!
    起きたこと
    ノイズなので引っかからないようにしたい

    View Slide

  24. ドキュメントに書いてある
    フレーズ検索を使うと、隣り合ったトークンの位置まで比較するので、
    『quick brown fox』が『quick fox』で引っかからないようにできる
    意訳














    boolean ○ ○ - ○ ○
    phrase 1 2 3 ×
     boolean
    – 順不同でも一致
    – 位置情報不要
     phrase (フレーズ検索)
    – 順通りで一致
    – 位置情報が必須
    コンテンツ「ニコニココラボ企画」の場合
    ⇒ phraseならノイズが減る
    調査
    例)「ニコニコラボ」でbigram検索

    View Slide

  25. ただし副作用がある
    phraseの副作用: booleanと比べて検索時の負荷が高い
    全文検索の世界では一般的、フレーズ検索はパフォーマンスに影響する
    これらのコストは、単語が出現する回数によって異なり、出現回数が低い単語での
    検索であれば、非常に高速という特徴がある

    View Slide

  26. 実は、それもドキュメントに書いてある
    Luceneのベンチマークでは、フレーズクエリは単純な term クエリの20倍遅い
    けど、これは termがそれほど早いということを表しているもので、
    フレーズ検索のコストは、この数字で見るより怖いものではない 意訳
    負荷検証したら、その通りで、よく使われるキーワードと
    フィールドで負荷検証したところ、負荷は問題なかった

    View Slide

  27. ただ、すんなりはいかなかった
    Luceneのベンチマークでは、フレーズクエリは単純な term クエリの20倍遅い
    けど、これは termがそれほど早いということを表しているもので、
    フレーズ検索のコストは、この数字で見るより怖いものではない 意訳
    けど、ステージングで試したらpositionsがないと怒られた

    一体なにが・・・

    View Slide

  28. なんと、ドキュメントに書いてある
    フレーズには位置情報が必要
    index_optionsでインデックスサイズを削減してたのが原因
    フレーズ検索が必要なく、精密なスコア必要ないのなら、
    転置インデックスから、トークンの位置情報や出現数を省くことで、
    インデックスサイズの節約が可能
    その設定を行っていたが、ここで問題になった

    View Slide

  29. フレーズ検索はフィールド単位で検討することにした
    index
    options
    文章
    ID
    単語
    出現
    回数
    位置
    情報
    オフ
    セット
    docs ○
    freqs ○ ○
    positions
    (default)
    ○ ○ ○
    offsets ○ ○ ○ ○
     長文の用途を検討する
    – スコアに影響するか
    – フレーズ検索を行うか
    – フィールド長はどの程度か
     docsが可能なら削減する
    – Wikipedia『アニメ』の冒頭4000文字
    9.7%の削減になる
    * サイズはデフォルトアナライザによる計測
    index optionsと転置インデックスの関係

    View Slide

  30. 様々な要件|調査
    テキスト検索のノイズを減らしたい
    環境依存文字(Unicode)で検索したい
    ある時点の検索結果を並べ替えつつ全件取得したい
    フィールドの追加についての調査
    クエリの受け入れについての調査

    View Slide

  31. 環境依存文字(Unicode)で検索したい
     ユーザーは人と違うことをやりたがる
    – ㋷㋜㋤㊀、ココロオ㌦
     シリーズものコンテンツが多い
    – パート①パート⑳、実況動画のまとめ㊤㊦
    この手の表記ゆれの対策をすることも検索基盤として重要

    View Slide

  32. やっぱり、ドキュメントに書いてある
    調査
    ドキュメントの検索に「ICU」って何気なく打ち込んだら
    書いてあった!!

    View Slide

  33. アナライザを作らなくて済んだ
    elasticsearch-analysis-icu
    ICUを用いたES用アナライザプラグイン
    以下を用いてNFKC正規化を行う
    ICUを用いて正規化を行う
    正規化で要求されているキーワードと等価性のあるコンテンツを返す
    やったこと
    icu_normalyzer filterのみ、アナライザに組み込んで利用

    View Slide

  34. 様々な要件|調査
    テキスト検索のノイズを減らしたい
    環境依存文字(Unicode)で検索したい
    ある時点の検索結果を並べ替えつつ全件取得したい
    フィールドの追加についての調査
    クエリの受け入れについての調査

    View Slide

  35. ある時点の検索結果を並べ替えつつ全件取得したい
     以下が合わなかった
    1. ソートとディープページングが必要だったが、scanではソートが実現できず、
    要件に合わなかった
    2. scrollはリアルタイムなユーザーリクエストのためのものではない
     scanとは
    scanは、ディープページングのコストを安くするためにソートを無効にする検索タイプ
     scrollとは
    scrollは、ある時点のスナップショットを作成して、結果を順次返していくAPI
    scan & scrollでは要件を満たせなかった

    View Slide

  36. 細かいことでもドキュメントに書いてある
    なぜできないのか、そもそも検索ってなんなのか
    調査が必要と思ったが、それもドキュメントに書いてある
    調査

    View Slide

  37. 1. リクエストをうけたノード(コーディネーティングノード)は、必要なデータを持つ
    シャードに対して、from+size 件を要求する
    2. 要求をうけたシャードは、from+size 件のdocidとソートに使うvalueなどを返す
    3. 結果を受け取ったコーディネーティングノードは、全てをマージして、グローバル
    ソートを行って、from~size 件のdocidを生成する
    from: 90, size: 10
    100件ずつ
    100件ずつ
    Query phase
    (Definitive Guideから引用)
    「scanとは」の前に
    検索タイプ query then fetch

    View Slide

  38. 1. Query phaseで生成したdocidのドキュメントを要求する
    2. 要求をうけたシャードは、ドキュメントをロードしてdocidと合わせて返す
    3. 全てのドキュメントがfetchされた後、クライアントに結果を返す
    Fetch phase
    10件
    合計10件
    合計10件
    「scanとは」の前に
    検索タイプ query then fetch
    (Definitive Guideから引用)

    View Slide

  39. Query phase
    from: 1000000, size: 10
    1000010件ずつ
    1000010件ずつ
    ディープページングを行う度に
    グラフが跳ねる様子
    =負荷のため、通常検索での
    ディープページング提供は難しい
    「scanとは」の前に
    だからディープページングは重い

    View Slide

  40. 検索タイプ scanとは(私の理解)
    1. Query phase同様、コーディネーティングノードが from+size 件を要求する
    2. 要求をうけたシャードは、from+size 件の文章をロードして、ソートせずに返す
    3. 結果を受け取ったシャードは、ソートせずにクライアントに結果を返す
    (このケースでは、10件要求して20件返される)
    from: 1000000, size: 10
    10件ずつ
    10件ずつ
    ソートしないのでディープページングに強い検索タイプ、という理解

    View Slide

  41. >ある時点のスナップショットを作成して、結果を順次返していくAPI
    search context を保持しつづける特殊なAPI。オプションに有効期限
    Marvelで確認できる
    scrollとは

    View Slide

  42. search contextで参照があたっているデータは残す必要がある
    merge & remove
    A D
    Segments
    A B C
    ×
    scrollとは

    scroll中もmergeは行うが、 removeだけ停止する

    View Slide

  43. Segments
    A B C E F ・・・
    scroll中はどんどんセグメントらしき(本来削除された)ものが溜まっていく
    よって、リアルタイムなユーザーリクエストのためのものではない、という理解
    ドキュメントには、scrollは異なる構成のデータストアに大量のデータを送り込むときに使うも
    のであると、最初の最初に書かれている
    D
    =リソース(ファイルディスクリプタなど)の消費も増えていく
    scrollとは(私の理解)

    View Slide

  44. ある時点の検索結果を並べ替えつつ全件取得したい
    別のインデックスへdumpした
    scan & scrollでは要件を満たせなかったが、dumpで解決できた
    やったこと

    View Slide

  45. 要件への対応|複雑なケース
    フィールド
    追加
    データ
    受け入れ
    完了
    フィールド追加のフローは同じだが、事前に調査や検証が必要になる
    調査
    要件定義 検証
    クエリ
    受け入れ
    事前に

    View Slide

  46. 複雑な要件では、調査と同時に負荷検証を行う
    負荷検証には、『es crasher』という自作の負荷検証ツールを使っている
    人によって手順やツールが違う
    共通して言えることは、負荷検証では、キャッシュを知ることが重要
    負荷検証

    View Slide

  47. $ curl -XPOST localhost:9200/video/_search -d '{
    "query": {
    "match": {
    "title": {
    "query": "ゲーム",
    "type": "boolean",
    "operator": "AND",
    "boost": 5
    }
    }
    }
    }'
    round took
    1回目 3550
    2回目 11
    3回目 16
    結果
    round took
    1回目 92
    2回目 7
    3回目 11
    結果 (再起動後)
    なぜmatchクエリは、二回目の方が早いのか
    疑問
    起動直後のElasticsearchへ、matchクエリを実行したときの 検索実行時間(ms)
    1

    View Slide

  48. 検索の効率化に必要なセグメントをディスクからメモリに読みこんでいる
    MarvelのLUCENE MEMORYは、total.segments.memory_in_bytes を描画しており、
    indices _segments APIでも確認できる
    二度目以降、読み込みが必要なくなり実行が早くなる
    再起動後は、ファイルI/OにOSのページキャッシュが使われている
    Marvel
    なぜmatchクエリは、二回目の方が早いのか
    疑問 1
    メモリへのセグメントの読み込みが要因

    View Slide

  49. $ curl -XPOST localhost:9200/video/_search?pretty -d '
    {
    "query": {
    "filtered": {
    "query": {
    "match": {
    "title": {
    "query": "ゲーム",
    "type": "boolean",
    "operator": "AND",
    "boost": 5
    }
    }
    },
    "filter": {
    "term": {
    "view_counter": "0"
    }
    }
    }
    },
    "sort": [
    {
    "view_counter": {
    "order": "desc"
    }
    }
    ]
    }'
    round took
    1回目 75
    2回目 4
    3回目 4
    filter のみ結果
    round took
    1回目 537
    2回目 4
    3回目 5
    sort のみ結果
    なぜfilterやsortは、二回目の方が早いのか
    疑問 2
    そのまま Elasticsearch へ、filter と sort を実行したときの 検索実行時間(ms)
    表示の都合上、同一クエリとして記載

    View Slide

  50. Marvel
    なぜfilterやsortは、二回目の方が早いのか
    疑問 2
    filter 句ごとに filter cache を作成している
    別のクエリでも、filter 句さえ同じなら、キャッシュHitする
    bitset で容量的にはたいしたことない
    filter 高速化の要因 - filter cache の生成

    View Slide

  51. sort 対象フィールドごとに fielddataを作成している
    二度目以降、fielddataが利用されるため、高速化する
    別のクエリでも、ソート対象さえ同じなら、キャッシュHitする
    なぜfilterやsortは、二回目の方が早いのか
    疑問 2
    sort 高速化の要因 - fielddataの生成
    Marvel
    容量がでかい、作り直しに時間がかかる

    View Slide

  52. • ソートに限らず、広く利用される
    – field valuesへのアクセスを必要とする問い合わせで必要
    – Aggregations、geolocation filters等のフィルタ、scriptで参照したとき・・
    • 構造は非転置インデックス
    – 転置インデックスは単語からdoc idを探すのは得意だけど、逆にdoc idから単語
    を取得するのが苦手なので、キャッシュに持つ
    • field valuesやシャードを跨いだGlobal ordinalsという序数を持つ
    – 容量は大きく、文字列フィールドだと1フィールドで500MBを超えることもある
    • デフォルトのフォーマットでは問い合わせ時にメモリ上に作成される
    – メモリに置かずかつ高速なdoc valueというフォーマットもあるが、まだデフォ
    ルトにはなってない
    – こちらはLUCENE 4.0で追加されたDocValuesを利用しているとのこと
    – デフォルトと違いIndex時に作成されるらしい
    なぜfilterやsortは、二回目の方が早いのか
    疑問 2
    f i e l d d a t a と は

    View Slide

  53. Segments
    A B C
    ドキュメントの更新によりキャッシュはどうなるのか
    疑問 3
    filter cache|fielddata は、更に細かくはセグメント単位になっている
    ESのドキュメントは不変。作成や更新は新しいセグメントに積まれていく
    新しいセグメント
    (lucene flush)
    3. New Query
    結果をCが持ってる
    1. Update
    Aにあった文章は削除
    Cに作り直したい
    2. Refresh

    View Slide

  54. 更新が発生しても、全てのキャッシュを作り直す必要はなく、新しいセグメントの
    キャッシュを追加すればよい
    Segments
    A B C
    ドキュメントの更新によりキャッシュはどうなるのか
    疑問 3
    filter cache|fielddata は、更に細かくはセグメント単位になっている
    Cだけ作り直し

    View Slide

  55. ドキュメントの更新によりキャッシュはどうなるのか
    疑問 3
    refreshは、index.refresh_interval の間隔で実施
    デフォルトで1s
    index.translog.flush_threshold_sizeのサイズを超えるとflash
    デフォルトで200MB
    index.translog.flush_threshold_periodの間隔でflash
    デフォルトで30m
    cacheを無効にすると、refresh後の問い合わせの度キャッシュが作り直される
    検証として、リアルな数値が必要なときは、キャッシュを無効にしないほうがいい
    更新の反映は、refresh, flashのタイミング

    View Slide

  56. Segments
    削除は文章へのマークのみ。実削除はmerge時に行われる
    A B C
    文章1に削除マーク
    (lucene flush)
    1. Remove
    Bにあった文章1は削除
    2. Refresh
    1
    ×
    2...
    ドキュメントの削除によりキャッシュはどうなるのか
    疑問 4
    filter cache|fielddata は、更に細かくはセグメント単位になっている

    View Slide

  57. Segments
    A B C
    文章1に削除マーク
    (lucene flush)
    1. Remove
    Bにあった文章1は削除
    2. Refresh 3. New Query
    ヒット対象にならない
    1
    ×
    2...
    ドキュメントの削除によりキャッシュはどうなるのか
    疑問 4
    filter cache|fielddata は、更に細かくはセグメント単位になっている
    削除は文章へのマークのみ。実削除はmerge時に行われる
    どうしてなの?

    View Slide

  58. mergeされるまでは、ディスク容量も使うし、fielddataも残ることになる
    このスキップのために、文章が削除されたときからスループットは低くなっていく
    ドキュメントの削除によりキャッシュはどうなるのか
    疑問 4
    mergeされるまで、問い合わせ時にフラグを見てスキップしてるらしい
    って、最近のelasticsearch blogに書いてあった!

    View Slide

  59. 負荷検証で心にとめていること
    フィルタのキャッシュを無効にしない
    試験前に何度か動かして温めておく
    更新をかけつつ
    複数シャードのインデックスで行う
    GCの検証も兼ねて30分くらい苛める
    実際に使われるキーワードを使う
    ソートはランダム







    応答時間|CPU|メモリ|GCなど総合的に監視して、
    問題がなければ、フィールドの追加を行う

    View Slide

  60. Benchmark API
    APIで負荷検証ができる
    クエリの指定
    リクエスト回数や繰り返し回数
    キャッシュの細かい制御
    ウォーミングアップ
    など揃ってるので、人によって違う問題は解決できるかも
    この機能は実験中で、 1.4.2では使えない
    おまけ

    View Slide

  61. 発 表 の テ ー マ
    Elasticsearchは検索基盤として運用しやすい
    負荷検証やクエリやスキーマ定義の細かい話は、
    ほとんどドキュメントに書いてあること
    深い内容までドキュメントに記載されているので、
    複雑な要件時の細かい挙動を調査しやすい
    要件対応にはスキーマ定義が付きまとうが、
    Elasticsearchはフィールド追加の対応が、とても楽
    まとめ

    View Slide

  62. ご清聴、ありがとうございました。
    ご質問やご指摘ございましたら、頑張って答えます。

    View Slide

  63. 企業PR
    • たぶん、本物のエンジニアを募集してる
    • ブラック働きとかない、自由な社風
    • 詳しくはホームページを見てください

    View Slide