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のマッチングは、二つの異なる問題です。それぞれに異なる解決策が必要です。
