Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Fess/Elasticsearchを使った業務で使える?全文検索への道

 Fess/Elasticsearchを使った業務で使える?全文検索への道

FessやElasticsearchを通して検索システムを作る際に遭遇する課題などを紹介します。

Shinsuke Sugaya

November 26, 2017
Tweet

More Decks by Shinsuke Sugaya

Other Decks in Technology

Transcript

  1. DBFluteフェス 2017 ▪ 名前: 菅谷信介 ▪ オープンソース活動: ➔ Fess, DBFlute関連,

    Apache PredictionIO,... ▪ Blog: http://www.chazine.com/ ▪ Twitter: https://twitter.com/shinsuke_sugaya/ ▪ DBFluteフェスのこの枠で話すのは3回目… 自己紹介 2
  2. DBFluteフェス 2017 ▪ OSSの分散リアルタイム検索&分析エンジン ▪ 特徴 ➔ドキュメント指向な検索エンジン(Apache Lucene) ➔RESTfulなAPI ➔スキーマフリー

    ➔分散システム (高可用性、スケールアウト) ➔プラグインによる拡張 ▪ DBFlute的に使うならESFluteも ➔https://github.com/lastaflute/lastaflute-example-waterfront/ Elasticsearchとは 6
  3. DBFluteフェス 2017 ▪ オープンソースの全文検索システム ➔オールインワンで簡単に利用可能 ▪ Apacheライセンスで提供 ▪ Elasticsearchを検索エンジンとして利用 ▪

    現在の最新バージョンは11.4 ➔2009/09にSolrベースで1.0をリリース ▪ 商用サポートも提供 (N2 Search) Fessとは 7 http://fess.codelibs.org/ja/
  4. DBFluteフェス 2017 こんな感じとか? 10 { "from": 0, "size":20, "query": {

    "match_phrase": { "content": "テスト" } } } 検索クエリー例1
  5. DBFluteフェス 2017 もうちょっとがんばってこんな感じとか? 11 { "from": 0, "size": 20, "query":

    { "bool": { "should": [ { "match_phrase": { "title": "テスト" } }, { "match_phrase": { "content": "テスト" } } ] } } } 検索クエリー例2
  6. DBFluteフェス 2017 { "from" : 0, "size" : 20, "timeout"

    : "10000ms", "query" : { "bool" : { "must" : [ { "function_score" : { "query" : { "bool" : { "should" : [ { "match_phrase" : { "title" : { "query" : "テスト", "slop" : 0, "boost" : 0.2 } } }, { "match_phrase" : { "content" : { "query" : "テスト", "slop" : 0, "boost" : 0.1 } } }, { "match_phrase" : { "title_ja" : { "query" : "テスト", "slop" : 0, "boost" : 1.0 } } }, { "match_phrase" : { "content_ja" : { "query" : "テスト", "slop" : 0, "boost" : 0.5 } } }, { "match_phrase" : { "title_en" : { "query" : "テスト", "slop" : 0, "boost" : 1.0 } } }, 13 { "match_phrase" : { "content_en" : { "query" : "テスト", "slop" : 0, "boost" : 0.5 } } } ], "disable_coord" : false, "adjust_pure_negative" : true, "boost" : 1.0 } }, "functions" : [ { "filter" : { "match_all" : { "boost" : 1.0 } }, "field_value_factor" : { "field" : "boost", "factor" : 1.0, "modifier" : "none" } } ], "score_mode" : "multiply", "max_boost" : 3.4028235E38, "boost" : 1.0 } } ], "filter" : [ { "bool" : { "should" : [ { "term" : { "role" : { "value" : "1guest", "boost" : 1.0 } } }, { "term" : { "role" : { "value" : "Rguest", "boost" : 1.0 } } } ], "disable_coord" : false, "adjust_pure_negative" : true, "boost" : 1.0 } } ], "disable_coord" : false, "adjust_pure_negative" : true, "boost" : 1.0 } }, ・ ・ ・ 実際にはもっと長い… Fessの検索クエリー
  7. DBFluteフェス 2017 ▪ テキストを分解して単語群を生成する ▪ LuceneはAnalyzerがある ▪ CharFilter/Tokenizer/TokenFilterで構成される ▪ 組み合わせることで自由自在の解析可能

    ▪ インデックスの生成時に設定する 単語に分割する 16 「今日の天気は晴れです」      ↓ 「今日」「天気」「晴れ」
  8. DBFluteフェス 2017 Analyzer 17 「東京スカイツリーの①番出口」 CharFilter (文字単位で変換) TokenFilter (単語単位で変換) Tokenizer

    (単語に分割) 「東京スカイツリーの1番出口」 「東京」「スカイツリー」「の」「1番」「出口」 「東京」「スカイツリ」「1番」「出口」 Analyzer CharFilter (文字単位で変換) CharFilter (文字単位で変換) TokenFilter (単語単位で変換) TokenFilter (単語単位で変換)
  9. DBFluteフェス 2017 検索の全体像 18 インデックス 検索 Analyzer Analyzer 検索対象 検索時

    クロール/インデクシング時 ▪ 検索とインデックス時にAnalyzerを利用 ▪ それぞれで別なAnalyzerも指定可能 ▪ 同じ単語にならなければヒットしない
  10. DBFluteフェス 2017 ▪ 構造的なQuery DSLを利用 ▪ HTTPまたはTransportでリクエスト ▪ 様々なクエリーをサポート(aggs, geo,...)

    検索クエリー 20 $ curl -XPOST ‘localhost:9200/company/_search -d ‘{ “query” : { "match_phrase" : { "content" : "fess" } }, “size”: 10 }
  11. DBFluteフェス 2017 ▪ 解析された文字列にマッチするクエリー ➔ 全文検索で利用 ➔ Match〜QueryはAnalyzerが適用される ▪ 解析された文字列にマッチするので、lowercaseさ

    れるならfluteはFluteにも一致する ➔ フレーズとしてはマッチしない ➔ 日本語bi-gramでは期待通りの結果にならない Match Query { "query": { "match": { "product_name": "flute" } } } 22
  12. DBFluteフェス 2017 ▪ 解析された文字列のフレーズでマッチする ➔ タームの順番も含めて一致する ➔ 日本語の場合、ほぼこのクエリーを利用する ➔ Match

    Queryの場合、並び順に関係なく、ヒットする ➔ Match Queryでもphase指定で検索可能 Match Phrase Query { "query": { "match_phrase": { "product_name": "Low Price Flute" } } } 23
  13. DBFluteフェス 2017 ▪ 範囲指定検索 ➔ 数値や日付を範囲指定する場合に利用する ➔ gt, gte, lt,

    lteで範囲を指定 Range Query { "query": { "range": { "latest_purchase_date": { "gte": "2017-11-01", "lte": "2017-11-30" } } } } 26
  14. DBFluteフェス 2017 ▪ and/or/not条件を合成するクエリー ➔ and条件: mustで指定する(複数指定可能) ➔ or条件: shouldで指定する(複数指定可能)

    ➔ not条件: mustNotで指定する(複数指定可能) ➔ 絞り込み条件: filterで指定する(複数指定可能) Bool Query { "query": { "bool": { "must": [...], "should": [...], "must_not": [...], "filter": [...] } } } 27
  15. DBFluteフェス 2017 ▪ スコアを調整するクエリー ➔ functionsにマッチしたものをもとにスコアを変える ➔ queryには通常の検索条件を指定する Function Score

    Query { "query": { "function_score": { "query": { ...ここに通常の条件を書く ... }, "functions": [ { "filter": { "match": {"test": "cat"} ←スコアを調整したい条件 }, "weight": 42 } ] } } } 28
  16. DBFluteフェス 2017 ▪ 検索時に集計処理も同時に行う ➔ 統計情報とかヒストグラムとか ➔ ファセットとかドリルダウンとか Aggregation 29

    { "query": { ...検索条件... }, "aggs": { "category": { "terms": { "field": "product_category", "size": 10 } } } }
  17. DBFluteフェス 2017 ▪ フィールド値が範囲内にある件数を取得する ➔ Rangeが返却される Range Aggregation { "query":

    { ...検索条件...}, "aggs": { "price": { "range": { "field": "price", "ranges": [ { "from": 1000, "to": 5000 }, …集計したい範囲の条件を記述... ] } } } } 30
  18. DBFluteフェス 2017 ▪ 指定した単語が含まれる件数を取得する ➔ Termsが返却される ➔ 含まれる単語と件数が取得できる Terms Aggregation

    { "query": { ...検索条件... }, "aggs": { "category": { "terms": { "field": "product_category", "size": 10 } } } } 31
  19. DBFluteフェス 2017 ▪ 指定したフィルタで項目を絞り込む ➔ 子のAggregationをフィルタするときに利用 ▪ ➔ ➔ Filter

    Aggregation { "query": { ...検索条件... }, "aggs": { "handle_code": { "filter": { "term": { "product_handle_code": "FLUTE-01" } }, "aggs": { ←フィルタされたあとの結果に対してのアグリゲーションを記述 "category": { "terms": { "field": "product_category" } } } } } } 32
  20. DBFluteフェス 2017 ▪ 文字列: keyword, text ▪ 数値: long, integer,

    short, byte, double, float, half_float, scaled_float ▪ 日付: date ▪ 論理値: boolean ▪ バイナリ: binary ▪ 範囲: integer_range, float_range, double_range, date_range ▪ その他: object, geo_point, geo_shape, ip, completion, token_count, …(独自定義も可能) 基本的な型 33
  21. DBFluteフェス 2017 ▪ bi-gramのインデックスではヒットしない ➔2文字単位で単語になっているため ➔「魚料理」→「魚料」「料理」 ▪ uni-gramのインデックスにする? ➔インデックスサイズが大きくなる… ▪

    形態素解析のインデックスにする? ➔一文字に分割されないものもある ➔「魚料理」→「魚」「料理」(この場合「理」とか☓) ▪ bi-gramのままでPrefix Queryにする ➔魚?で「魚料」にヒットさせる ➔インデックスする文字列にも最後に1文字加える 一文字検索 38
  22. DBFluteフェス 2017 ▪ クリック数やLike数をドキュメントに保持する ▪ クリック数は検索結果クリック時に集計 ➔リンクを書き換えてリダイレクト ▪ Function Score

    Queryでスコアに反映する 検索ログの取得 40 利用者 Fess 検索結果を表示 検索結果をクリック 検索結果へリダイレクト 検索結果のサイト ここでクリック情報を取得
  23. DBFluteフェス 2017 ▪ 全各言語用に様々なAnalyzerが必要 ➔日本語だとKuromojiとか ▪ 言語固有とbi-gramの2つのインデックス作成 ➔日本語なら形態素解析とbi-gramの2種類 ➔or検索(boolのshould等)をする ▪

    Fessでは多言語の設定を利用 ➔いろいろと書いてあるので必要なところ参考に https://github.com/codelibs/fess/blob/11.4.x/src/main/resources/fess_indices/fess.json ▪ さらにFessでは言語自動判定も利用 多言語での検索 42
  24. DBFluteフェス 2017 ▪ Google Search Applianceが保有する機能なので 普通に要求される場合が多い ▪ Analyzerで同義語辞書で対応可能な場合もある ➔リアルタイムな反映が求められると☓

    ▪ 関連クエリーと同等なものを作るしかない ➔Fessでは実装済み ➔関連コンテンツ的な機能もあったり… 関連クエリー 46
  25. DBFluteフェス 2017 ▪ 検索システムを作るのもけっこう大変です ▪ Lucene/Elasticsearchを直接使わなくても良い場合が ほとんどでは? ➔ Fessを使ってください!(たぶんFessに任せたほうが楽) ➔

    DB連携もJDBCでSQLを書くだけです ➔ 検索結果はJSONで取得できます ▪ 検索自体を作りたい場合はLuceneやElasticsearchを 直接使うのが良いと思います まとめ 77