Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】Building a Multilingual Book Search with FastAPI and PostgreSQL Full-Text Search

2026年10月10日に「Dev.to」が公開したITニュース「Building a Multilingual Book Search with FastAPI and PostgreSQL Full-Text Search」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

多言語書籍検索システムをFastAPIとPostgreSQLで構築した。PostgreSQLの全文検索機能を活用し、言語ごとのtsvectorカラムとGINインデックスで高速化を実現した。中国語や日本語など一部言語は別途処理し、数百万件のデータでも高速検索を達成した。

ITニュース解説

「Building a Multilingual Book Search with FastAPI and PostgreSQL Full-Text Search」という記事は、多言語に対応した書籍検索システムをどのように開発したかについて解説している。このシステムは、ユーザーがアップロードした書籍をAIが複数の言語に翻訳し、その翻訳された内容を含めて、あらゆる言語でのキーワード検索を高速に行えるように設計されている。

システム構築における主な課題は、数百万に及ぶ書籍の段落とその膨大な翻訳データを効率的に保存し、どの言語のキーワードに対しても関連性の高い検索結果を、100ミリ秒以内に返すことであった。当初、開発チームは大規模な検索システムによく用いられるElasticsearchの導入も検討した。しかし、システムのデータ規模が数百万段落程度であったため、Elasticsearchの複雑な運用にかかるコストや手間は過剰であると判断された。そこで、PostgreSQLというリレーショナルデータベースに標準で組み込まれている全文検索機能を利用する方針が決定された。PostgreSQLの全文検索機能は、言語ごとの形態素解析(単語の形が変化しても語幹を識別する処理)や、検索結果の関連性に応じたランキング付け、そして高速な検索を可能にするGINインデックスといった、必要な機能を十分に備えていると考えられたためだ。このシステムは、バックエンドAPIの開発にPythonのFastAPIを、データベースとの連携にはSQLAlchemy 2.0と非同期データベースアクセスを実現するasyncpgを利用して構築されている。

データは、書籍、章、段落、翻訳というように、細分化された「正規化された」テーブル構造で格納される。具体的には、booksテーブルに書籍情報、chaptersテーブルに章情報、paragraphsテーブルに元の段落テキスト、そしてtranslationsテーブルに各段落の翻訳テキストが保存される。特に注目すべきは、翻訳テキストを一つのJSON形式のカラムにまとめるのではなく、言語ごとに個別のレコードとしてtranslationsテーブルに格納している点である。これは、それぞれの言語のテキストに対して、専用の全文検索インデックスを効率的に作成できるようにするための重要な設計判断だ。もし全ての翻訳をJSONBのような形式で格納してしまうと、PostgreSQLが個々の言語フィールドに対して効率的なインデックスを作成することが難しくなってしまう。

高速な全文検索の鍵となるのは、「tsvector」という仕組みである。tsvectorは、テキストを検索に適した形式に変換したデータ表現で、例えば「running」のような単語は「run」という語幹に変換され、検索効率を高める。このシステムでは、PostgreSQL 12以降で利用できる「生成カラム(Generated Columns)」という機能を用いて、各翻訳テキストがデータベースに保存されるたびに、自動的にこのtsvectorカラムを生成している。具体的には、to_tsvectorという関数に翻訳テキストと、その言語に対応するテキスト検索設定(例: 'english', 'spanish')を渡してtsvectorを生成する。language::regconfigという記法は、データベースに文字列として保存されている言語名(例: 'english')を、PostgreSQLが認識できる言語設定(regconfig型)に変換する役割を果たす。これにより、言語ごとに最適な形態素解析が適用されたtsvectorが自動的に作成され、データベースに永続的に保存される。

tsvectorカラムが準備できたら、次にそのカラムに「GINインデックス」という特殊なインデックスを作成する。インデックスは、書籍の巻末にある索引のように、データベースが目的のデータを素早く見つけるのを助ける役割を果たす。GINインデックスは全文検索に特化しており、大量のテキストデータから特定のキーワードを含むレコードを高速に検索することを可能にする。さらに効率を高めるため、このシステムでは「部分インデックス(Partial Index)」という手法を採用している。これは、例えば「英語のtsvectorカラムには英語のインデックスのみを作成する」というように、特定の条件(この場合は言語)に合致するレコードに限定してインデックスを作成するものだ。これにより、各インデックスのサイズを小さく保ち、より効率的な検索と管理が可能になる。

ユーザーが検索を実行する際は、まずFastAPIのAPIエンドポイントを通じて検索クエリ(検索したい語句)と、オプションで検索対象言語が送られてくる。システムは、websearch_to_tsqueryという関数を使ってユーザーの入力した検索クエリを、PostgreSQLが理解できる全文検索クエリの形式に変換する。この関数は、ダブルクォーテーションで囲まれたフレーズ検索や、OR条件、除外条件など、ユーザーがより柔軟な検索を行えるように解釈する。その後、SQLAlchemyを利用して、必要な情報(書籍ID、タイトル、段落の順序、翻訳言語、翻訳テキスト)をデータベースから取得するクエリが組み立てられる。このクエリでは、translationsテーブルを起点に、paragraphs、chapters、booksといった関連するテーブルが結合される。そして、Translation.search_vector.op('@@')(tsquery)という条件で、生成されたtsvectorカラムと変換された検索クエリを比較し、関連するレコードを抽出する。さらに、ts_rank関数を使って、どの結果がより関連性が高いかを示すスコアを計算し、そのスコアが高い順に結果を並べ替えて、ユーザーに返す仕組みだ。もしユーザーが検索言語を指定していれば、その言語の翻訳のみが対象となる。

多言語対応において特に工夫が必要だったのは、中国語、日本語、韓国語(CJK言語)への対応である。これらの言語は、欧州言語のように単語間にスペースがないため、PostgreSQLのデフォルトの形態素解析では正しく単語を認識できない。例えば、日本語の「今日はいい天気」という文は、デフォルト設定では一文字ずつに区切られてしまい、意味のある単語単位での検索ができない。いくつかの解決策が検討された結果、このシステムでは、データを取り込む際にPythonのライブラリ(中国語にはjieba、日本語にはfugashiとmecab-ipadic、韓国語にはmecab-ko)を使って、テキストを事前に単語に分割する(トークナイズする)方法が採用された。分割されたテキストは、translated_text_segmentedという別のカラムに保存され、そのカラムに対して通常の欧州言語と同様にtsvectorを生成し、GINインデックスを作成する。この方法により、PostgreSQL自体に複雑な拡張機能を追加することなく、CJK言語でも正確な単語単位での検索が可能になった。この単語分割の処理は、AI翻訳にかかる時間に比べてわずかな追加時間で済むため、全体の性能にはほとんど影響を与えない。

システムの稼働後、パフォーマンスが大幅に向上したことが確認された。GINインデックスを導入する前は、検索クエリの実行に2〜4秒かかっていたが、言語ごとの部分GINインデックスを生成カラムに適用したところ、検索にかかる時間は中央値で35ミリ秒、最も遅い場合でも80ミリ秒にまで短縮された。これは、目標としていた100ミリ秒以内という要件を十分に満たしている。GINインデックスの追加によってデータベースのサイズはテーブル全体の約15%増加したが、これは許容範囲内のオーバーヘッドであった。

この構築経験から得られた知見として、PostgreSQLの全文検索機能は強力ではあるが、シノニム辞書(類義語検索)や高度なカスタムアナライザー、分散検索のような非常に専門的な機能が必要な場合は、Elasticsearchのような専用の検索エンジンが依然として優位であるという点が挙げられる。しかし、このシステムのデータ規模であれば、PostgreSQLで十分な性能を発揮できることが確認された。また、生成カラムはtsvectorの更新を自動化し、メンテナンスを非常に簡素化するが、元テキストが更新されるたびにtsvectorとインデックスも更新されるため、書き込み性能にはわずかな影響があることも理解しておく必要がある。CJK言語対応にはデータベース外での単語分割が必要となるが、これは避けられないトレードオフだ。非同期処理を行うasyncpgとSQLAlchemyの組み合わせは、高い並行処理能力を実現し、大量の検索リクエストを効率的に処理するのに役立っている。

将来的には、より高度な関連性チューニングや、ユーザーの誤字・脱字に対する許容度を高めるために、MeilisearchやTypesenseといった専用検索エンジンへの移行も検討されている。しかし、現時点ではPostgreSQLの全文検索機能がこのシステムの要件を十分に満たしており、信頼性の高い基盤となっている。

まとめると、もし中規模の多言語対応アプリケーションを構築し、高速な検索機能が必要であれば、すぐに複雑な専用検索エンジンに手を出す必要はない。PostgreSQLの組み込み全文検索機能は、生成カラムと部分GINインデックスを組み合わせることで、数十の言語にわたる検索でも100ミリ秒未満の高速応答を実現でき、インフラの運用コストを最小限に抑えながら強力な検索システムを構築する非常に有効な選択肢である。

関連コンテンツ

関連IT用語

関連ITニュース