データベース●●●○○

全文検索

Full-Text Search

文章を語に分解して索引化し、キーワードで大量の文書から高速に探し出す検索の仕組み。

概要

全文検索は、大量の文書の「本文そのもの」を対象に、キーワードで高速に探し出すための仕組みです。ブログの記事検索、EC サイトの商品検索、社内ドキュメントの横断検索、ログ基盤での障害調査 — 「入力した言葉を含む文書を、関連の強い順に一瞬で返す」場面の裏側には、ほぼ必ず全文検索エンジンがいます。

ポイントは「文書を語(トークン)に分解して索引を作っておく」ことです。検索のたびに全文書を頭から読むのではなく、あらかじめ「どの語がどの文書に出てくるか」の一覧表 — 転置インデックスと呼ばれるデータ構造 — を作っておき、検索時はその表を引くだけにします。書籍の巻末索引で「この用語は何ページに載っているか」を引くのと同じ発想を、機械的に全語彙へ適用したものと言えます。

なぜ生まれたか

素朴に考えると、文字列検索はRDBMSLIKE '%キーワード%' でもできます。しかしこの方法は、部分一致のためにDBのインデックスがほとんど効かず、文書が増えるほど全行を総なめする線形走査になってしまいます。数万件ならまだしも、数百万・数億の文書に対しては応答が秒単位・分単位に膨らみ、実用になりません。さらに LIKE は「文字列がそのまま含まれるか」しか見ないため、「走る」で検索して「走った」を見つける、複数キーワードの関連度で並べ替える、といった「検索らしい振る舞い」が一切できません。

この問題は Web 検索エンジンの登場で決定的になりました。増え続ける文書に対して検索時間をほぼ一定に保つには、「検索のときに探す」のをやめて「書き込みのときに索引を作っておく」しかない — 計算コストを検索時から登録時へ移すこの転換が、転置インデックスを核とする全文検索エンジンを生みました。情報検索(Information Retrieval)という研究分野で培われた索引構造とスコアリング理論が、Lucene のようなオープンソース実装を通じて誰でも使える部品になった、という系譜です。

詳細

転置インデックス — 「文書→語」を「語→文書」にひっくり返す

文書は本来「文書1には東京、晴れ、…という語が含まれる」という文書→語の向きで存在します。転置インデックスはこれを文字どおり転置し、「東京は文書1と2に出現する」という語→文書の向きの表を作ります。各語に対応する文書IDの並びをポスティングリストと呼び、検索時は検索語のポスティングリストを引くだけで候補文書が得られます。複数語の AND 検索はリスト同士の共通部分、OR 検索は和集合を取るだけで、文書総数が増えてもリスト操作のコストしかかかりません。実際のインデックスには文書IDに加えて出現位置(フレーズ検索用)や出現頻度(スコアリング用)も一緒に格納されます。

文書群トークナイズ転置インデックス文書1今日の東京は晴れ文書2東京の天気予報文書3明日は雨のち晴れ語に分解形態素解析など東京→ 文書1, 2晴れ→ 文書1, 3天気→ 文書2→ 文書3検索「東京 晴れ」 → 2つの一覧の共通部分を取るだけ → 文書1文書が何億件あっても、表を引いてリスト同士を突き合わせるコストで済む
転置インデックスの構造 — 文書を語に分解し、語から文書を引ける表を作る

トークナイズ — 形態素解析と N-gram

索引の質を決めるのがトークナイズ、つまり文章をどう語に切るかです。英語は空白で区切ればおおよそ語になりますが、日本語には分かち書きがないため、2つの流儀が使い分けられます。1つは形態素解析で、辞書と文法知識を使って「東京都に住む」を「東京都/に/住む」のように意味のある単位へ切る方式です。索引が小さく検索精度も高い一方、辞書にない新語や固有名詞が切れず、検索漏れの原因になります。もう1つは N-gram で、意味を無視して機械的に N 文字ずつずらしながら切る方式です(2文字なら bigram: 「東京都」→「東京/京都」)。辞書いらずで検索漏れがない代わりに、索引が肥大化し、「京都」で検索すると「東京都」までヒットするようなノイズが混ざります。実務では、取りこぼしを嫌う場面は N-gram、精度を重視する場面は形態素解析、あるいは両者の併用が選ばれます。このほか、大文字小文字の正規化、全角半角の統一といった文字コードまわりの正規化や、活用形を揃えるステミング(「走った」→「走る」)も、この段階の重要な仕事です。

スコアリング — 「関連の強い順」を作る

全文検索が LIKE 検索と決定的に違うもう1つの点が、ヒットした文書を関連度順に並べることです。古典的な考え方が TF-IDF で、「その文書内での出現頻度(TF)が高い語ほど重要、ただしどの文書にも出てくるありふれた語(IDF が低い語)は割り引く」という重み付けをします。現在の主流はその改良版である BM25 で、文書の長さによる不公平を補正しつつ、出現頻度の効果に飽和点を設けたものです。Elasticsearch などのデフォルトスコアもこれです。さらに近年は、キーワードの一致ではなく意味の近さで探すベクトル検索 — 文書を埋め込みベクトルに変換してベクトルDB的に近傍を探す方式 — を BM25 と組み合わせるハイブリッド検索が広がっており、RAG の文脈でも両者の併用が定石になりつつあります。

実装と実務 — Elasticsearch と「検索は別システム」という設計

代表的な実装は、Java 製ライブラリの Apache Lucene と、それを分散サーバ化した Elasticsearch・OpenSearch、同系の Solr です。ほかに軽量な Meilisearch や Typesense、SaaS の Algolia、RDBMS組み込みの機能(PostgreSQL の全文検索や pg_trgm、MySQL の FULLTEXT)もあります。実務でよくある構成は、業務データの本体は RDBMS に置き、検索用のコピーを Elasticsearch へ同期して検索だけを任せる形です。このとき検索インデックスは本体データの派生物なので、同期の遅延や失敗によって「DB にはあるのに検索に出ない」というズレが起こり得ます。これは結果整合性を受け入れる設計であり、再インデックス(索引の作り直し)の手段を最初から用意しておくことが運用の定石です。また、トークナイズ設定は後から変えると既存インデックスと不整合になるため、アナライザの変更は再インデックスとセットで計画する — この2点が、全文検索を運用に組み込むときの典型的な落とし穴です。