Techブログ - MNTSQ, Ltd.

リーガルテック・カンパニー「MNTSQ(モンテスキュー)」のTechブログです。

UnifiedHighlighterのOffset strategyに関して調べてみた

MNTSQ Tech Blog TOP > 記事一覧 > UnifiedHighlighterのOffset strategyに関して調べてみた

f:id:kensaku_m:20210326120630j:plain

MNTSQで検索エンジニアをしている溝口です。 今回はElasticsearchでハイライト処理を行う際に利用するUnifiedHighlighterの挙動について簡単に調べる機会があったので、それを簡単に記事にしました。

ハイライト処理とは

検索結果一覧が表示された際に、以下のようにヒットした該当箇所が強調表示される機能のことです。(以下のスクリーンショットだと検索キーワードの「ハイライト」が太文字になっていると思います。)

f:id:kensaku_m:20210326120816p:plain

この機能を実現するために、Elasticsearchでは以下の3種類のハイライターが利用可能です。

  • PlainHighlighter
  • FastVectorHighlighter
  • UnifiedHighlighter

それぞれ特徴は異なりますが、今回はハイライトするにあたってそれぞれのハイライターが必要とするリソースにフォーカスします。

PlainHighlighter

かつてデフォルトだったハイライターです。ハイライト処理をするためにテキストを再解析します。 必要なリソースは少ないですが、テキストの再解析を行うため、ハイライト処理にかかる時間は長くなりがちです。

FastVectorHighlighter

名前の通り高速なハイライト処理が可能ですが、ハイライト処理をするためにTermVectorという属性を必要とします。TermVectorはディスクに保存する必要があるので、必要なディスク容量は大きくなります。

UnifiedHighlighter

最も新しいハイライターで現在のデフォルトです。かつて存在したPostingsHighlighterというハイライターを改善して作られました。ハイライト処理をする際に以下を適切に選択して処理の高速化をはかります。

  • POSTINGS (posting listを利用する)
  • TERM_VECTORS (FastVectorHighlighterと同じく、TermVectorを利用する)
  • ANALYSIS (PlainHighlighterと同じく、ハイライト時にテキストを再解析する)
  • POSTINGS_WITH_TERM_VECTORS (posting listとTermVectorの両方を利用する)

さて、前置きが長くなりましたが、ElasticsearchのReferenceを読むと、上記のようにそれぞれのハイライターを使う場合にどのようなフィールドのオプションを指定すれば良いかが書いてあります。

https://www.elastic.co/guide/en/elasticsearch/reference/7.11/highlighting.html#offsets-strategy

しかしながら、上記のうちUnifiedHighlighterのPOSTINGS_WITH_TERM_VECTORSの記述が見当たらないことに気づきます。

そのため、今回はElasticsearchからUnifiedHighlighterを使うときのOffset strategy(ハイライトを高速化するときに必要な追加の情報)で、

  • そもそもPOSTINGS_WITH_TERM_VECTORSは利用可能なのか

を明らかにし、それに加えて

  • それぞれのOffset strategyを利用した場合のディスク使用量と性能の向上度合い

を簡単に計測してみようと思います。

Setup

以下の条件でデータを登録して、indexサイズ、ハイライトの平均処理時間を簡単にみてみたいと思います。

  • MacBook Pro
    • CPU: 2.4GHz 8Core Intel Core i9
    • RAM: 32GB DDR4
    • OS: BigSur
    • Docker: Docker Desktop for Mac 3.2.2
      • CPUs: 4
      • Memory: 8GB
    • Elasticsearch: 7.11.2

Elasticsearchは以下のDockerfileでanalysis-kuromojiだけ入れて、イメージをbuildし、そのイメージを元にしたコンテナを以下のコマンドで起動します。

Dockerfile

FROM docker.elastic.co/elasticsearch/elasticsearch:7.11.2
RUN /usr/share/elasticsearch/bin/elasticsearch-plugin install analysis-kuromoji

起動コマンド

$ docker run -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" 6571a46f9f37 

検証に使うデータには、日本語Wikipediaのダンプデータから適当に10万件の本文情報のみを抜き取ったものを使います。 利用するindex名と、調査対象のフィールドの設定差分は以下の通りです。

  • 共通
    • analyzer = kuromoji
  • wiki_text_a (上述のANALYSISに相当)
    • index_options = positions
    • term_vector = no
  • wiki_text_p (上述のPOSTINGSに相当)
    • index_options = offsets
    • term_vector = no
  • wiki_text_t (上述のTERM_VECTORSに相当)
    • index_options = positions
    • term_vector = with_positions_offsets
  • wiki_text_pt (上述のPOSTINGS_WITH_TERM_VECTORSに相当)
    • index_options = offsets
    • term_vector = yes

一例を挙げるとこんな感じです。

$ curl -XPUT localhost:9200/wiki_text_a -H 'Content-Type: application/json' -d '{
  "mappings": {
    "properties": {
      "text": {
        "type": "text", 
        "analyzer": "kuromoji",
        "index_options": "positions",
        "term_vector": "no"
      }
    }
  },
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 0
  }
}'

なお、TermVectorを有効にする時の指定がwith_positions_offsetsとyesになっているなど、微妙な違いがありますが、これはElasticsearchではTermVectors/TermPositions/TermOffsetsという異なる情報をterm_vectorという属性にまとめていることに起因しています。 Offset strategyがPOSTINGS_WITH_TERM_VECTORSの場合には、TermVectorsのみを、TERM_VECTORSの場合にはTermVectors/TermPositions/TermOffsetsの3種を全て利用するので、上記のような設定になっています。

indexingが終わったら、以下の通りそれぞれのindexをforce mergeします。

$ curl -XPOST "localhost:9200/wiki*/_forcemerge?max_num_segments=1"

$ curl -XGET "localhost:9200/_cat/segments?v"
index        shard prirep ip         segment generation docs.count docs.deleted    size size.memory committed searchable version compound
wiki_text_a  0     p      172.17.0.2 _2               2     100000            0 389.1mb        1172 true      true       8.7.0   false
wiki_text_p  0     p      172.17.0.2 _2               2     100000            0 471.8mb        1172 true      true       8.7.0   false
wiki_text_pt 0     p      172.17.0.2 _3               3     100000            0 584.6mb        3004 true      true       8.7.0   false
wiki_text_t  0     p      172.17.0.2 _2               2     100000            0 638.7mb        3004 true      true       8.7.0   false

結果の取りまとめ前ですが、上記でとりわけ目につくのは、wiki_test_t(term_vector: with_positions_offsets)を指定しているインデックスが、デフォルトのindexオプションのものの1.6倍にもなっていることです。 なお、wiki_test_pt(postings + term vector)は、上述の通りterm_vectorの一部の属性のみを追加しているので、それよりは大きくなっていないことが確認できます。

ハイライト性能は、jmeterを使って簡易に測ります。 今回は簡単に以下の設定で測りたいと思います。

  • スレッド数: 2
  • Ramp-Up期間: 1
  • ループ回数: 10000

ベンチマークに使う検索キーワードはindexに含まれているtermで出現回数が多い&3文字以上のものを10000件ピックして使うことにしました。

クエリには以下を使います。(${text}の部分は10000件のtermが代入されます)

{
    "query": {
        "match": {
            "text": "${text}"
        }
    },
    "size": 10,
    "_source": ["id"],
    "highlight": {
        "fields": {
            "text": {}
        },
        "type": "unified"
    }
}

また、日本語検索ではmatchクエリが使われることはあまりないので、上記の他にmatch_phrase_prefixクエリでも計測してみます。

結果

結果をまとめると以下のようになりました。

matchクエリ

index名 Throughput Average Median 95% Line 99% Line
wiki_test_a 77.1 25 22 49 71
wiki_test_p 226.6 8 8 11 13
wiki_test_t 208.6 9 8 13 14
wiki_test_pt 220.1 8 8 12 14

ANALYSISとそれ以外では大きな違いが見受けられますが、これならPOSTINGSが最も優秀そうです。

match_phrase_prefixクエリ

index名 Throughput Average Median 95% Line 99% Line
wiki_test_a 74.6 26 23 43 51
wiki_test_p 73.6 26 24 44 52
wiki_test_t 205.9 9 8 13 14
wiki_test_pt 195.6 9 9 14 16

matchクエリの時とは一転して、POSTINGSはANALYSISと大して変わらない性能となってしまいました。

まとめ

ElasticsearchにおけるUnifiedHighlighterとindexの属性指定の関係をまとめて、簡単な性能比較をしてみました。

結果としては、 1. POSTINGS_WITH_TERM_VECTORSは有効 2. 多く情報を保存することでハイライト処理は高速になる。一方で、必要となるディスク領域が大きくなっていく 3. 利用するクエリに応じてどの情報を保存するべきかを判断すべき

ということが確認できました。

今回は簡単にディスク容量とハイライトの速度の関係をまとめてみました。それぞれのStrategyにおける使用メモリ量なども機会があれば記事にできればと思います。

この記事を書いた人

f:id:kensaku_m:20201130161518p:plain

溝口泰史

MNTSQ社で検索エンジニアをしています。