Sansan Tech Blog

Sansanのものづくりを支えるメンバーの技術やデザイン、プロダクトマネジメントの情報を発信

大規模同義語辞書でElasticsearchのIndex作成が重くなる問題とその対策

こんにちは。技術本部 Contract One Engineering Unit の伊藤です。Sansan株式会社で AI 契約データベース「Contract One」の開発を担当しています。

Contract Oneでは契約書検索をコア機能として提供していますが、実運用では社名の表記揺れ新旧社名による検索漏れが課題となっていました。たとえば「さんさん」と入力しても「Sansan株式会社」にヒットしてほしいですし、旧社名「三三株式会社」を検索しても新社名に紐づく契約書がヒットするようになって欲しいという要望がありました。

そこでElasticsearchの同義語検索機能を導入し、複数の表記を吸収して検索できる仕組みを追加しました。これにより検索精度は大きく改善しましたが、大規模な同義語辞書を使う中で一部性能が劣化する部分がありました。本記事では、その調査過程と得られた知見を共有します。



実装のアプローチ

検索フローの全体イメージ

例:ユーザーが「さんさん」と入力した場合

1. ユーザーが入力したクエリを解析し、同義語辞書で「Sansan株式会社」/「三三株式会社」/「サンサン」などに展開 (これを同義語展開と呼びます)
2. 展開後のクエリで全パターンを検索

使った機能

Elasticsearch の公式機能である Synonym Graph Token Filter を採用しました。

辞書について

同義語辞書は弊社の研究開発部にて作成したものを利用しています。公開データや弊社のデータ基盤に存在するデータなどを利用し、表記揺れ・新旧社名の対応辞書を作成しています。
全体で数十万行・数十MB程度のテキストファイルです。

同義語展開のタイミング

同義語展開は「検索時(query-time)」に実施する方針を採用しました。
これは Elasticsearch 公式の推奨*1にも沿う考え方で、インデックスに余計な冗長性を持たせず、辞書更新時の運用コストも抑えられます。
別の方法として「インデックス時にすべての文書に対して展開する」方法もあり、この方法だと検索時に都度展開を行わなくても良くなるので検索時のオーバーヘッドは減らせますが、ドキュメントの作成・更新時の処理負荷が上がることやインデックスサイズの増加、辞書更新時の再インデックスなどのデメリットがあります。
検索対象ドキュメント側はプレーンなトークン化のみとし、ユーザーのクエリ側で同義語を展開して検索候補を広げる構成にしました。

発生した問題

同義語辞書を導入した後、検索結果の精度は改善しましたが、インデックス作成時の性能が大きく悪化する問題が見つかりました。

具体的には、通常であればおよそ 100ms 程度で完了していた index 作成処理が、同義語辞書を組み込んだ後は約 2 分にまで増加しました。さらに、処理中のCPU 使用率は 60〜80%に達し、クラスタ全体の負荷が明らかに高まる状況となりました。

インデックス作成時のElasticsearchのCPU使用率

一方で、検索時のレスポンス性能はほとんど影響を受けず、負荷試験でも従来通りの応答速度を維持していました。そのため問題は検索体験には現れず、運用観点(設定やドキュメント構造変更に伴うインデックス再作成)で顕在化するものであることが分かりました。

原因調査

インデックス作成時の性能悪化が再現できたため、ローカル環境で段階的に原因を切り分ける検証しました。結論から述べると、tokenizer の選定がボトルネックの根本原因でした。

ローカルでの再現状況: 処理時間約2分、CPU 使用率100%

根本原因

検索対象フィールドで使用していたN-gram tokenizerSudachi tokenizerに変更してテストしたところ、劇的な改善が見られました。

処理時間約 18 秒、CPU 使用率 40%に改善

インデックス作成完了までの処理時間約 18 秒、CPU 使用率 40%まで改善することが確認できました。

この結果から、tokenizer の選定がインデックス作成時の性能に直結していることが明らかになりました。

効果がなかった調整項目

問題の切り分けのため、次の調整も試行しましたが効果は限定的でした。

expand オプションの調整

Synonym Graph Token Filter のオプションである expand を false に変更。expand は O(n^2) で処理量が増えていく仕様のため改善を期待しましたが、インデックス作成の時間と CPU 使用率は大きく変わりませんでした。ハイライトの位置などに副作用もあり、採用しませんでした。

シャード数の調整

シャード数を減らしてオーバーヘッド削減を図りましたが、結果はほとんど改善が見られず、根本原因ではないと判断しました。

問題の本質

根本要因は「N-gram × 大規模同義語辞書」の掛け算で、インデックス作成時に内部で構築される FST(Finite State Transducer)の処理が重くなることでした。

FSTについて

FST(Finite State Transducer)は Elasticsearch が内部で使用する効率的なデータ構造です。Elasticsearch は同義語辞書を使うアナライザーを持つインデックスの作成時や設定変更時に、この同義語情報を高速に処理できるようFSTを構築しています。

N-gram について

N-gramとは文字列をトークン化(分割)する時に、互いに重なりを持ったn文字の連鎖をトークンとして取り扱う方法です。日本語文書の検索は英語に比べて難しく、単語間の境界が明示的でないため、N-gramアプローチは日本語の全文検索でよく使われる手法です。

例えば「株式会社」という文字列を2-gram(bi-gram)で分割すると、「株式」「式会」「会社」という3つのトークンに分割されます。これにより「株式」や「会社」といった部分文字列での検索も可能になり、柔軟な検索を実現できます。

N-gramは便利な反面、インデックスサイズの増大と効率の悪化というトレードオフがあります。書籍「情報検索 :検索エンジンの実装と評価」では「N-gram を使うとインデックスサイズが最大6倍、クエリ処理は30倍以上になる」と指摘されています。

N グラム法を使う際には、インデックスサイズの増大と効率の悪化という代償を払わなければならない。圧縮をしても、対応する語単位のトークンによるインデックス(平均語長6文字の一般的な英語コーパス)に対して、最大で6倍の大きさになる。トークン化によりクエリに含まれるターム数が増加するため、クエリの処理時間は30倍以上になると考えられる。

N-gram との組み合わせでの問題

N-gram は文字列を細かく分割して多数のトークンを生成します。そこに大量の同義語エントリを扱う仕組みが加わると、トークン数の増加に対して指数関数的に FST 構築のコスト(時間・CPU)が増大し、パフォーマンスが著しく劣化する構造になっていました。

「Sansan株式会社」という文字列をN-gramで分割した場合、17個のトークンが生成されます。("S", "Sa", "an", "ns"...) これに対して日本語tokenizerであるSudachiを使うと、2個のトークンに抑えられます。("Sansan", "株式会社")。同義語辞書が数十万行規模になると、N-gramでは膨大なトークンが生成され、FST構築の負荷が急増します。

Contract One の検索機能は初期実装時には RDB のクエリで行っており、そのときに必要だった検索要件(部分一致検索など)を Elasticsearch でも満たすため、対象フィールドのアナライザに N-gram を採用していました。今回の調査で、この設計判断が大規模な同義語辞書との組み合わせによって大きな負荷につながっていたことが明らかになりました。

以上から、N-gram によるトークン数増大と、インデックス作成時の FST 構築コストの指数関数的増大が組み合わさった結果として、同義語辞書導入後に index 作成の負荷が上がっていた、というのが問題の本質です。

対応

現在は、デグレードが発生しないことを確認したうえで、対象フィールドを N-gram 用と同義語用で分けるなどの対応を検討しています。
また、ユーザーからは「検索結果が純粋な入力キーワードによる一致なのか」「同義語による表記揺れによる一致なのか」「新旧社名による一致なのか」を区別できるようにしてほしい、というフィードバックも寄せられています。
こうした要望に対応するため、フィールドの分離とあわせて、辞書の用途や運用方針についても検討を進めています。

まとめ

N-gram の便利さとトレードオフ

  • N-gram を使うことで日本語における部分一致検索を簡単に実現できる。
  • ただし、トークン数が爆発的に増えるため、インデックス作成や同義語展開との相性が悪く、性能劣化を引き起こすリスクが高い
  • 部分一致を優先する場面と、性能を優先する場面を切り分けられる設計が必要になる。

日本語 tokenizer の有効性

  • 日本語 tokenizer(例: Sudachi)は、通常は検索精度の観点で検討されることが多い。
  • しかし今回の検証で明らかになったのは、検索精度だけでなくパフォーマンス要件にも直結する重要な選択肢であるという点。
  • 適切な tokenizer を選ぶことで、トークン数を抑えつつ検索品質と性能を両立できる。

今後について

Contract One では、約半年前に検索基盤を Elasticsearch に移行し、それ以来、検索機能の強化に向けてさまざまな施策を行ってきました。
利用シーンを見ても、社名や契約条件の表記揺れへの対応にとどまらず、入力ミスや揺らぎへの耐性、さらには 意味的な関連度に基づいた検索 へのニーズがますます高まっています。

こうしたニーズに応えるため、研究開発部と協力しながら検索評価の仕組みを整備し、同義語辞書のアップデートや検索体験の改善に取り組んでいます。今後も検索精度と性能の両立を追求しながら、プロダクトのコア機能である検索を進化させていきます。

また、私たちは検索や自然言語処理まわりに興味を持っているエンジニアを絶賛募集しています。カジュアル面談なども大歓迎ですので、ぜひお気軽にご連絡ください。

media.sansan-engineering.com

© Sansan, Inc.