OpenSearchとCJKを用いたタイポ許容型検索の実装方法

検索結果がゼロのクエリが、私たちの視聴時間を削っていました。

1年間、TopVideoHubは検索にSQLite FTS5を使用してきました。完全一致には機能していましたが、ユーザーがタイポ(打ち間違い)をすると機能しませんでした。

ユーザーは「demon slayer」ではなく「demon slyer」と検索したり、日本語や韓国語のタイトルに余計なスペースを入れたりしました。FTS5は正確なトークンに一致させる仕組みであるため、たった一つのタイポで結果がゼロになってしまいます。ユーザーは再検索することなく、そのまま離脱してしまいました。

私はタイトル検索をOpenSearchに移行しました。多言語ユーザー向けにどのように解決したかを説明します。

標準的なファジー検索の問題点

多くのチュートリアルでは、タイポを修正するために「fuzziness(ファジー性)」を使うよう推奨しています。これは英語には有効ですが、中国語、日本語、韓国語(CJK)のテキストには適していません。

  • CJKにおいて編集距離(Edit distance)は不向きです。 CJKでのタイポは、多くの場合、全く別の文字を使用してしまうことを意味します。
  • 標準的なファジー検索は、意味的に無関係な結果(semantic garbage)を生み出します。 例えば、「fire」と「water」が編集距離1であれば、これらを一致させてしまう可能性があります。
  • トークン化が困難です。 CJK言語には単語間のスペースがありません。

解決策:マルチフィールド・アプローチ

私はすべてのテキストを同じように扱うのをやめました。3つの異なる方法でインデックスを作成する、一つの論理的なタイトルフィールドを作成しました。

  • ラテン文字とローマ字: ファジー検索を有効にした標準的なトークン化を使用しました。prefix length(接頭辞の長さ)を1に設定しました。これにより、「demon」は「demn」に一致しますが、「lemon」が「demon」に一致することはありません。
  • CJKテキスト: CJK bigram analyzerとICU normalizerを使用しました。ファジー検索はOFFにしました。その代わりに、最小一致しきい値(minimum match threshold)を70%に設定しました。
  • オートコンプリート: 検索しながら結果を表示する(search-as-you-type)機能を実現するために、edge-ngramフィールドを使用しました。

アーキテクチャとデータの安全性

SQLiteを「単一の信頼できる情報源(single source of truth)」として維持しました。OpenSearchは、高速で再構築可能なインデックスとして機能します。

  • PHPを使用して、OpenSearchへの更新をバルクバッチでプッシュしています。
  • ユーザーのリクエスト中にインデックス作成を実行することはありません。
  • Pythonスクリプトを実行して「ドリフト(データの乖離)」をチェックしています。これにより、OpenSearchのカウントがSQLiteのカウントと一致することを保証しています。

結果

変化は劇的でした。

  • 検索結果がゼロのクエリが14%から3%未満に減少しました。
  • ユーザーがすぐに目的のものを見つけられるようになったため、検索セッションが長くなりました。
  • レイテンシは40ms前後と低いままを維持しています。

多言語ユーザーを対象とする場合は、覚えておいてください。タイポ許容とCJKのマッチングは、二つの異なる問題です。それぞれに異なる解決策が必要です。

Source: https://dev.to/ahmet_gedik778845/how-i-added-typo-tolerant-video-title-search-with-opensearch-and-cjk-3e5d