Ubuntu16.04のPostgreSQLで全文検索を使用する方法

提供:Dev Guides
移動先:案内検索

序章

全文検索(FTS)は、検索エンジンがデータベース内の結果を検索するために使用する手法です。 ショップ、検索エンジン、新聞などのWebサイトの検索結果を強化するために使用できます。

具体的には、FTSは、検索条件に完全に一致しないテキストデータを含むデータベースエンティティであるドキュメントを取得します。 つまり、ユーザーが「猫と犬」を検索すると、たとえば、FTSに基づくアプリケーションは、単語を個別に含む結果(「猫」または「犬」のみ)を返し、異なる順序で単語を含めることができます。 (「犬と猫」)、または単語の変形を含む(「猫」または「犬」)。 これにより、アプリケーションは、ユーザーが何を意味するのかを推測し、より関連性の高い結果をより早く返すことができるという利点があります。

技術的に言えば、PostgreSQLのようなデータベース管理システム(DBMS)は通常、LIKE句を使用した部分的なテキストルックアップを許可します。 ただし、これらのリクエストは、大規模なデータセットではパフォーマンスが低下する傾向があります。 また、ユーザーの入力と完全に一致するように制限されています。つまり、関連情報を含むドキュメントがある場合でも、クエリで結果が生成されない可能性があります。

FTSを使用すると、より高度なツールに余分な依存関係を導入することなく、より強力なテキスト検索エンジンを構築できます。 このチュートリアルでは、PostgreSQLを使用して、架空のニュースWebサイトの記事を含むデータを保存し、FTSを使用してデータベースにクエリを実行し、最適なものだけを選択する方法を学習します。 最後のステップとして、全文検索クエリのパフォーマンスを改善します。

前提条件

このガイドを開始する前に、次のものが必要です。

  • このUbuntu16.04 ガイドを使用した初期サーバーセットアップ(sudo非rootユーザーを含む)に従ってセットアップされた1つのUbuntu16.04サーバー。
  • Ubuntu 16.04にPostgreSQLをインストールして使用する方法ガイドに従って、PostgreSQLをインストールします。 このチュートリアルでは、sammyデータベースとそのガイドで設定されたユーザーを使用します。

上記のチュートリアルに従わずにPostgreSQLサーバーをセットアップする場合は、sudo apt-get list postgresql-contribを使用してpostgresql-contribパッケージがあることを確認してください。

ステップ1—サンプルデータの作成

まず、全文検索プラグインをテストするためのデータが必要になるので、いくつかのサンプルデータを作成しましょう。 すでにテキスト値を含む独自のテーブルがある場合は、手順2にスキップして、フォローしながら適切な置換を行うことができます。

それ以外の場合、最初のステップはサーバーからPostgreSQLデータベースに接続することです。 同じホストから接続しているため、デフォルトでは、パスワードを入力する必要はありません。

sudo -u postgres psql sammy

これにより、操作しているデータベース名(この場合はsammy)を示すインタラクティブなPostgreSQLセッションが確立されます。 sammy=#データベースコマンドプロンプトが表示されます。

次に、newsという名前のデータベースにサンプルテーブルを作成します。 この表の各エントリは、タイトル、コンテンツ、作成者の名前、および一意の識別子を含むニュース記事を表します。

CREATE TABLE news (
   id SERIAL PRIMARY KEY,
   title TEXT NOT NULL,
   content TEXT NOT NULL,
   author TEXT NOT NULL
);

idは、特別なタイプSERIALを持つテーブルのプライマリインデックスであり、テーブルの自動インクリメントカウンターを作成します。 これは、データベースインデックスに自動的に移動する一意の識別子です。 このインデックスについては、パフォーマンスの向上を確認するときに、ステップ3で詳しく説明します。

次に、INSERTコマンドを使用して、いくつかのサンプルデータをテーブルに追加します。 以下のコマンドのこのサンプルデータは、いくつかのサンプルニュース記事を表しています。

INSERT INTO news (id, title, content, author) VALUES 
 (1, 'Pacific Northwest high-speed rail line', 'Currently there are only a few options for traveling the 140 miles between Seattle and Vancouver and none of them are ideal.', 'Greg'),
 (2, 'Hitting the beach was voted the best part of life in the region', 'Exploring tracks and trails was second most popular, followed by visiting the shops and then checking out local parks.', 'Ethan'),
 (3, 'Machine Learning from scratch', 'Bare bones implementations of some of the foundational models and algorithms.', 'Jo');

データベースに検索するデータがいくつかあるので、いくつかのクエリを書いてみることができます。

ステップ2—ドキュメントの準備と検索

ここでの最初のステップは、データベーステーブルから複数のテキスト列を持つ1つのドキュメントを作成することです。 次に、結果の文字列を単語のベクトルに変換できます。これは、クエリで使用するものです。

注:このガイドでは、psql出力はexpanded displayフォーマットを使用して、出力の各列を新しい行に表示し、長いテキストを画面に合わせやすくします。 次のように有効にできます。

\x
OutputExpanded display is on.

まず、PostgreSQL連結関数||と変換関数to_tsvector()を使用してすべての列をまとめる必要があります。

SELECT title || '. ' || content as document, to_tsvector(title || '. ' || content) as metadata FROM news WHERE id = 1;

これにより、ドキュメント全体として最初のレコードと、検索に使用される変換されたバージョンが返されます。

Output-[ RECORD 1 ]-----------------------------------------------------
document    | Pacific Northwest high-speed rail line. Currently there are only a few options for traveling the 140 miles between Seattle and Vancouver and none of them are ideal.

metadata    | '140':18 'current':8 'high':4 'high-spe':3 'ideal':29 'line':7 'mile':19 'none':25 'northwest':2 'option':14 'pacif':1 'rail':6 'seattl':21 'speed':5 'travel':16 'vancouv':23

上記の出力では、元のdocumentよりも、変換されたバージョンmetadataの単語が少ないことに気付くかもしれません。 一部の単語は異なり、すべての単語にはセミコロンと数字が追加されています。 これは、関数to_tsvector()が各単語を正規化して、同じ単語の異形を検索できるようにし、結果をアルファベット順に並べ替えるためです。 数字はdocumentでの単語の位置です。 正規化された単語が複数回出現する場合は、追加のコンマ区切りの位置が存在する可能性があります。

これで、この変換されたドキュメントを使用して、「探索」という用語を検索することにより、FTS機能を利用できます。

SELECT * FROM news WHERE to_tsvector(title || '. ' || content) @@ to_tsquery('Explorations');

ここで使用した関数と演算子を調べてみましょう。

関数to_tsquery()は、直接またはわずかに調整されたユーザー検索である可能性のあるパラメーターを、to_tsvector()と同じ方法で入力を減らすテキスト検索基準に変換します。 さらに、この関数を使用すると、使用する言語と、すべての単語を結果に含める必要があるのか、それらの1つだけを含める必要があるのかを指定できます。

@@演算子は、tsvectortsqueryまたは別のtsvectorと一致するかどうかを識別します。 trueまたはfalseを返すため、WHERE基準の一部として簡単に使用できます。

Output-[ RECORD 1 ]-----------------------------------------------------
id      | 2
title   | Hitting the beach was voted the best part of life in the region
content | Exploring tracks and trails was second most popular, followed by visiting the shops and then checking out local parks.
author  | Ethan

検索に使用した単語が「Explorations」であったにもかかわらず、クエリは「Exploring」という単語を含むドキュメントを返しました。 ここでFTSの代わりにLIKE演算子を使用すると、空の結果が得られます。

FTS用のドキュメントを準備する方法とクエリを構造化する方法がわかったので、FTSのパフォーマンスを向上させる方法を見てみましょう。

ステップ3—FTSパフォーマンスの改善

FTSクエリを使用するたびにドキュメントを生成すると、大規模なデータセットや小規模なサーバーを使用する場合にパフォーマンスの問題になる可能性があります。 ここで実装するこれに対する1つの優れた解決策は、行を挿入するときに変換されたドキュメントを生成し、それを他のデータと一緒に保存することです。 このようにして、毎回生成するのではなく、クエリを使用して取得することができます。

まず、既存のnewsテーブルにdocumentという追加の列を作成します。

ALTER TABLE news ADD "document" tsvector;

次に、別のクエリを使用してデータをテーブルに挿入する必要があります。 手順2とは異なり、ここでは、変換されたドキュメントを準備して、次のように新しいdocument列に追加する必要があります。

INSERT INTO news (id, title, content, author, document)
VALUES (4, 'Sleep deprivation curing depression', 'Clinicians have long known that there is a strong link between sleep, sunlight and mood.', 'Patel', to_tsvector('Sleep deprivation curing depression' || '. ' || 'Clinicians have long known that there is a strong link between sleep, sunlight and mood.'));

既存のテーブルに新しい列を追加するには、最初にdocument列に空の値を追加する必要があります。 次に、生成された値で更新する必要があります。

UPDATEコマンドを使用して、不足しているデータを追加します。

UPDATE news SET document = to_tsvector(title || '. ' || content) WHERE document IS NULL;

これらの行をテーブルに追加するとパフォーマンスが向上しますが、大規模なデータセットでは、データベースがテーブル全体をスキャンして検索条件に一致する行を見つける必要があるため、問題が発生する可能性があります。 これに対する簡単な解決策は、インデックスを使用することです。

データベースインデックスは、メインデータとは別にデータを格納するデータ構造であり、データ取得操作のパフォーマンスを向上させます。 テーブルの内容が変更されると更新されますが、追加の書き込みと比較的少ないストレージスペースが必要になります。 サイズが小さく、データ構造が調整されているため、メインの表スペースを使用して照会を選択するよりも、索引をより効率的に操作できます。

最終的に、インデックスは、特別なデータ構造とアルゴリズムを使用して検索することにより、データベースが行をより速く見つけるのに役立ちます。 PostgreSQLには、特定のタイプのクエリに適したいくつかのタイプのインデックスがあります。 このユースケースに最も関連するものは、GiSTインデックスとGINインデックスです。 それらの主な違いは、テーブルからドキュメントを取得する速度です。 GINは、新しいデータを追加するときのビルドには時間がかかりますが、クエリには高速です。 GISTはより高速にビルドされますが、追加のデータ読み取りが必要です。

GiSTはGINよりもデータの取得に約3倍遅いため、ここでGINインデックスを作成します。

CREATE INDEX idx_fts_search ON news USING gin(document);

インデックス付きのdocument列を使用すると、SELECTクエリももう少し簡単になります。

SELECT title, content FROM news WHERE document @@ to_tsquery('Travel | Cure');

出力は次のようになります。

Output-[ RECORD 1 ]-----------------------------------------------------
title   | Sleep deprivation curing depression
content | Clinicians have long known that there is a strong link between sleep, sunlight and mood.
-[ RECORD 2 ]-----------------------------------------------------
title   | Pacific Northwest high-speed rail line
content | Currently there are only a few options for traveling the 140 miles between Seattle and Vancouver and none of them are ideal.

完了したら、\qを使用してデータベースコンソールを終了できます。

結論

このガイドでは、メタデータドキュメントの準備と保存、パフォーマンスを向上させるためのインデックスの使用など、PostgreSQLで全文検索を使用する方法について説明しました。 PostgreSQLのFTSについて詳しく知りたい場合は、全文検索に関するPostgreSQLの公式ドキュメントをご覧ください。