【ITニュース解説】Why our product search returned nothing for misspelled Hebrew and English queries
2026年11月05日に「Dev.to」が公開したITニュース「Why our product search returned nothing for misspelled Hebrew and English queries」について初心者にもわかりやすく解説しています。
ITニュース概要
商品検索でユーザーのタイポや多言語入力ミスにより商品が見つからない問題があった。これを解決するため、PostgreSQLのpg_trgm機能を導入。文字列を3文字単位で比較し類似度をスコア化することで、表記揺れがあっても最適な商品を返せるようになった。キーボードレイアウトの違いもアプリ側で対応し、検索精度が向上した。
ITニュース解説
システムエンジニアを目指す初心者の皆さん、ECサイトなどの製品検索機能は、ユーザーが欲しい商品を素早く見つけるために非常に重要だ。しかし、一見簡単そうに見えるこの検索機能も、実はさまざまな課題を抱えている。今回は、あるECサイトが直面した検索の課題と、それをどのように解決したのかを具体的に見ていこう。
このECサイトでは、取り扱う商品名がヘブライ語とラテン語(英語)が混ざっていたり、型番やサイズなどの数字が含まれていたりして、非常に複雑だった。例えば、「高圧洗浄機」というヘブライ語の商品名と「karcher」というブランド名が併記されているようなケースだ。顧客はスマートフォンで片手で検索することが多く、タイプミスやスペルミスは日常的に発生する。問題は、たった1文字の入力ミスがあるだけで、目的の商品が検索結果に全く表示されないことだった。
具体的な例を挙げよう。「karcher」というブランド名を「karchr」と打ち間違えたり、ヘブライ語の「קרשר」を「קרכר」と間違えたりすると、検索結果は空っぽになってしまう。顧客サポート担当者は、同じ間違ったワードを管理画面に入力しても、少し注意して見れば目的の商品のSKU(在庫管理番号)を見つけることができた。これは、商品が在庫にあるにもかかわらず、顧客が見つけられないという、サイト運営者にとっては非常に恥ずかしい状況だった。特に工具カタログのように、商品名が長く、多言語で、多くの数字を含む場合、この問題は深刻だった。「karcher 190」と「קרשר 190」は同じ商品を意図しているのに、どちらかの言語で入力ミスがあれば、商品が見つからないのだ。
当初、この問題への対策として、商品名に同義語(シノニム)を追加する方法が試された。しかし、これは手動での作業となり、記憶にあるブランド名しか追加できず、あらゆる種類のタイプミスに対応できるわけではなかった。次に検討されたのは、ステミング(語幹抽出)という技術だ。これは、単語の語形変化に対応するもので、タイプミスを修正する機能はなく、今回の問題の本質とは異なっていたのだ。
そこで目を向けたのが、PostgreSQLというデータベースで使える「pg_trgm」という拡張機能だった。これは、文字列を「トリグラム」と呼ばれる3文字の塊に分割し、検索クエリとデータベースに保存されたテキストが、どれだけ多くのトリグラムを共有しているかをスコア化して類似度を測るものだ。例えば「karcher」と「karchr」のように1文字間違えても、共通するトリグラムは多く残るため、類似度がゼロになることはなく、十分に似ていると判断されれば検索結果として表示されるようになる。さらに、この拡張機能はUTF-8という文字コードに対応しているため、ヘブライ語のような多言語のトリグラムも問題なく処理できる。言語に特化した特別なインデックスを用意する必要もなかった。
この検索機能を実現するために、まずデータベースに工夫を凝らした。各商品に対して「search_text」という専用のカラムを用意し、そこに小文字に変換したヘブライ語の商品名、ラテン語のブランド名、そしてSKU(在庫管理番号)をスペースで区切って結合して保存した。例えば、「高圧洗浄機 karcher 12345」といった具合だ。この結合された単一のsearch_textカラムに対して、「GINインデックス」という特殊なインデックスをgin_trgm_opsという演算子クラスとともに作成した。これにより、トリグラムを使った検索が高速に行えるようになった。複数のカラムを個別に検索して結果を組み合わせる方法も試したが、その場合は、たまたまSKUの一部が検索ワードと一致した商品が本来欲しい商品よりも上位に来てしまったり、同じ商品が検索結果に何度も表示されたりする問題があったため、単一の結合カラムにインデックスを貼る方法が最適と判断された。
実際の検索クエリでは、pg_trgmが提供するsimilarity()関数とword_similarity()関数を組み合わせて使った。similarity()は文字列全体を比較して類似度を出す一方、word_similarity()は検索対象の文字列の中で最も一致度の高い部分を見つけ出してスコアを出す。この二つの関数のうち、より高いスコアをGREATEST関数で採用することで、全体として高い一致度を保ちつつ、部分的な一致も逃さない検索が実現した。また、pg_trgmには%という演算子があり、これは設定した類似度閾値(similarity_threshold)を超えれば「十分似ている」と判断する機能だ。この閾値を0.3に設定し、4文字以上のクエリに適用した。さらに、完全に一致する部分文字列を見つけるために、従来のILIKEによる検索も残しておいた。これは、例えば「190」のような数字の型番でトリグラムのスコアがうまく出ない場合でも、確実に商品を見つけられるようにするためだ。
ただし、トリグラムによる検索にも限界はある。例えば、「בר」や「מס」といった4文字未満の短いクエリの場合、トリグラムのデフォルトの類似度閾値では、データベース内の多くの商品にマッチしてしまい、検索結果が膨大になる問題があった。そのため、これらの短いクエリについては、prefix ILIKEという、前方一致の検索方法で対応している。
また、pg_trgmが解決できないもう一つの大きな問題は、キーボードの入力レイアウトの間違いだ。例えば、ヘブライ語のキーボードレイアウトで英語の「karcher」と打ってしまうと、これは「karcher」とは全く異なる文字の並びになってしまう。この問題に対しては、アプリケーション側で対応した。ヘブライ語と英語のキーボードレイアウトのマッピングテーブルを作成し、顧客が入力したクエリと、そのクエリを別のレイアウトで入力した場合の二通りの形で検索を実行し、より高いスコアの結果を表示するようにした。もしどちらの形式でも適切なスコアが得られなければ、その時は商品が見つからないことを示す空の検索結果を表示するのが正しい。
このような高度な検索機能を導入すると、システムにいくつかの影響が出ることも忘れてはならない。GINインデックスは、通常のB-treeインデックスと比較してサイズが大きくなり、新しい商品をデータベースに登録する際の書き込み処理がわずかに遅くなった。このECサイトは数千SKUという規模だったため、この速度低下は許容範囲だった。しかし、もし何百万もの商品を取り扱う大規模なカタログであれば、まず従来のILIKE検索やtsvector(全文検索)を使った検索を最初に実行し、もし結果がゼロだった場合にのみpg_trgmを使ったよりコストのかかる類似度検索を実行するという、ハイブリッドなアプローチが有効だろう。これにより、一般的な正確なクエリは高速に処理しつつ、タイプミスの場合でも確実に商品を見つけられる柔軟性を保つことができる。
これらの変更の結果、ついに「קרכר」や「karchr 190」といったスペルミスのクエリでも、Karcher社の高圧洗浄機、具体的には190バールのモデルなどが検索結果にきちんと表示されるようになった。顧客に「これはあいまい検索です」といったメッセージを表示することもなく、シームレスな体験を提供できるようになったのだ。
今回の事例は、ユーザーの実際の行動パターンを理解し、それに合わせてシステムを改善していく重要性を示している。単にデータベースに商品を登録するだけでなく、顧客がどのようにその商品を探すのかを深く考察し、適切な技術を選択・実装することで、より良いユーザー体験を生み出すことができるという良い教訓だ。