PostgreSQL: PostgreSQL拡張エコシステム、外部データラッパー、ベクトル検索

最終更新:2026-08-26

1. 学習内容


2. ストーリー

AliceのEコマース企業は3つのことを行う必要があります:(1)AI商品レコメンデーションの統合(ベクトル検索が必要)、(2)レガシーMySQLデータのPGへの移行(クロスデータベースクエリが必要)、(3)遅いあいまい検索の高速化(pg_trgmが必要)。彼女はPostgreSQLの拡張エコシステムがこれらすべてを1か所で解決できることを発見しました — pgvectorは商品エンベディングを保存し、postgres_fdwは移行のためにMySQLに接続し、pg_trgmはあいまい検索のパフォーマンスを向上させます。1つのデータベースがElasticsearch + ミドルウェア + 自前のレコメンデーションサービスを置き換えます。


3. 概念:拡張メカニズムの概要

(1) PostgreSQL拡張とは

拡張はPostgreSQLのプラグインシステムで、関連するSQLオブジェクト(関数、型、演算子、インデックスメソッド)を単一のユニットとしてパッケージ化し、1つのコマンドでインストール・アンインストールできます。

SQL
-- 利用可能な拡張を一覧表示
SELECT name, default_version, installed_version, comment
FROM pg_available_extensions
ORDER BY name;

(2) 拡張管理コマンド

コマンド 目的
CREATE EXTENSION ext_name 拡張をインストール
CREATE EXTENSION IF NOT EXISTS ext_name 冪等なインストール
CREATE EXTENSION ext_name VERSION '1.2' 特定バージョンをインストール
DROP EXTENSION ext_name 拡張をアンインストール(CASCADEで設定を保持)
ALTER EXTENSION ext_name UPDATE TO '2.0' 拡張をアップグレード
\dx(psql) インストール済み拡張を一覧表示

(3) 拡張インストールの前提条件

条件 説明
共有ライブラ��ファイル .so / .dllshared_preload_libraries または dynamic_library_path にあること
コントロールファイル extension_name.controlSHAREDIR/extension/ にあること
SQLスクリプト extension_name--version.sql がオブジェクトを定義
権限 現在のデータベースへの CREATE 権限 + 一部の拡張にはスーパーユーザーが必要

4. 操作:一般的な拡張

▶ サンプル:uuid-osspでUUIDを生成

SQL
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

-- UUID v4(ランダム)を生成
SELECT uuid_generate_v4();

-- UUID v1(時刻ベース)を生成
SELECT uuid_generate_v1();

-- 列のデフォルト値として使用
CREATE TABLE api_keys (
    id          UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
    user_id     BIGINT NOT NULL,
    key_name    TEXT,
    created_at  TIMESTAMPTZ DEFAULT now()
);

Output:

TEXT 📖 参照専用
CREATE TABLE

▶ サンプル:pgcryptoデータ暗号化

SQL
CREATE EXTENSION IF NOT EXISTS pgcrypto;

-- ソルト付きでパスワードをハッシュ化
SELECT crypt('MySecret123', gen_salt('bf'));

-- パスワードを検証
SELECT crypt('MySecret123', stored_hash) = stored_hash AS is_match;

-- AES暗号化
SELECT encode(encrypt('credit card data'::bytea,
    'secret_key_16bytes'::bytea, 'aes'), 'hex');

-- ランダムトークンを生成
SELECT encode(gen_random_bytes(32), 'hex');

Output:

TEXT 📖 参照専用
CREATE TABLE

▶ サンプル:pg_trgmあいまい検索

SQL
CREATE EXTENSION IF NOT EXISTS pg_trgm;

-- 類似度スコアを表示
SELECT similarity('PostgreSQL', 'Postgres');

-- 高速あいまい検索用のGINトライグラムインデックス
CREATE INDEX idx_products_name_trgm ON products
    USING GIN (name gin_trgm_ops);

-- しきい値付きあいまい検索
SELECT name, similarity(name, 'iphon') AS score
FROM products
WHERE name % 'iphon'
ORDER BY score DESC;
TEXT 📖 参照専用
     name      | score
---------------+-------
 iPhone 15 Pro |  0.42
 iPhone 14     |  0.38
(2 rows)

▶ サンプル:pg_stat_statementsスロークエリ統計

SQL
-- 最初にshared_preload_librariesに含める必要あり
-- postgresql.conf: shared_preload_libraries = 'pg_stat_statements'

CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

-- 合計実行時間でトップ10クエリ
SELECT query,
       calls,
       round(total_exec_time::numeric, 2) AS total_ms,
       round(mean_exec_time::numeric, 2)  AS avg_ms,
       rows
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;

-- 統計をリセット
SELECT pg_stat_statements_reset();

Output:

TEXT 📖 参照専用
  result  
----------
   42.50
(1 row)

▶ サンプル:PostGIS空間データ入門

SQL
CREATE EXTENSION IF NOT EXISTS postgis;

-- ポイントジオメトリを保存
CREATE TABLE stores (
    id       SERIAL PRIMARY KEY,
    name     TEXT,
    location GEOMETRY(POINT, 4326)
);

-- 座標を挿入(経度、緯度)
INSERT INTO stores (name, location)
VALUES ('Dubai Mall', ST_SetSRID(ST_MakePoint(55.2796, 25.1972), 4326));

-- 5km以内の店舗を検索
SELECT name,
    ST_Distance(location::geography,
        ST_SetSRID(ST_MakePoint(55.2700, 25.2000), 4326)::geography
    ) AS distance_m
FROM stores
WHERE ST_DWithin(location::geography,
    ST_SetSRID(ST_MakePoint(55.2700, 25.2000), 4326)::geography, 5000);

Output:

TEXT 📖 参照専用
INSERT 0 1

5. 概念:外部データラッパー

(1) FDWアーキテクチャ

100%
flowchart LR
    A["ローカルPG<br/>postgres_fdw"] -->|"CREATE SERVER"| B["リモートPG<br/>(またはMySQL/Oracle)"]
    A -->|"CREATE SERVER"| C["file_fdw<br/>(CSV/ログファイル)"]
    A -->|"CREATE SERVER"| D["その他のFDW<br/>(Redis/MongoDB...)"]
    B --> E["IMPORT FOREIGN SCHEMA"]
    C --> F["CREATE FOREIGN TABLE"]
    D --> G["クロスシステムJOIN"]
FDW名 対象データソース ユースケース
postgres_fdw リモートPostgreSQL クロスデータベースクエリ、データ移行
mysql_fdw リモートMySQL MySQL→PG移行
file_fdw ローカルCSVファイル ログ分析、データインポート
redis_fdw Redis キャッシュクエリ
mongo_fdw MongoDB ドキュメントクエリ

(2) FDW設定手順

ステップ コマンド
1. 拡張をインストール CREATE EXTENSION postgres_fdw
2. SERVERを作成 CREATE SERVER remote FOREIGN DATA WRAPPER postgres_fdw OPTIONS (...)
3. USER MAPPINGを作成 CREATE USER MAPPING FOR local_user SERVER remote OPTIONS (...)
4. FOREIGN TABLEを作成 CREATE FOREIGN TABLE ft_xxx SERVER remote OPTIONS (...)
5. またはSCHEMA全体をインポート IMPORT FOREIGN SCHEMA public LIMIT TO (orders) FROM SERVER remote INTO remote_schema

6. 操作:postgres_fdwクロスデータベースクエリ

▶ サンプル:リモートPostgreSQL接続の設定

SQL
-- ステップ1: 拡張をインストール
CREATE EXTENSION IF NOT EXISTS postgres_fdw;

-- ステップ2: サーバー接続を作成
CREATE SERVER legacy_db
    FOREIGN DATA WRAPPER postgres_fdw
    OPTIONS (host '192.168.1.100', port '5432', dbname 'legacy');

-- ステップ3: ローカルユーザーをリモート認証情報にマッピング
CREATE USER MAPPING FOR current_user
    SERVER legacy_db
    OPTIONS (user 'admin', password 'secret123');

-- ステップ4: 外部スキーマをインポート
IMPORT FOREIGN SCHEMA public
    LIMIT TO (users, products, orders)
    FROM SERVER legacy_db
    INTO legacy_schema;

-- これでリモートテーブルをローカルのようにクエリ可能
SELECT u.name, COUNT(o.id) AS order_count
FROM legacy_schema.users u
JOIN legacy_schema.orders o ON u.id = o.user_id
GROUP BY u.name;

Output:

TEXT 📖 参照専用
 count 
-------
     5
(1 row)

▶ サンプル:ローカルとリモートのクロスデータベースJOIN

SQL
-- ローカル:新しいPG商品カタログ
-- リモート:postgres_fdw経由のレガシーMySQL注文データ

SELECT p.name,
       SUM(loi.quantity) AS total_sold,
       SUM(loi.quantity * loi.unit_price) AS revenue
FROM products p
JOIN legacy_schema.order_items loi ON p.id = loi.product_id
WHERE p.category = 'Electronics'
GROUP BY p.name
ORDER BY revenue DESC;

Output:

TEXT 📖 参照専用
  result  
----------
   42.50
(1 row)

▶ サンプル:file_fdwで外部CSVを読み取り

SQL
CREATE EXTENSION IF NOT EXISTS file_fdw;

CREATE SERVER csv_server
    FOREIGN DATA WRAPPER file_fdw;

CREATE FOREIGN TABLE access_logs_csv (
    ip_address    TEXT,
    request_time  TIMESTAMP,
    method        TEXT,
    path          TEXT,
    status_code   INT,
    response_time NUMERIC
) SERVER csv_server
OPTIONS (filename '/var/log/nginx/access.csv', format 'csv', header 'true');

-- nginxログをSQLで分析
SELECT path,
       COUNT(*) AS hits,
       AVG(response_time) AS avg_ms
FROM access_logs_csv
WHERE status_code = 200
GROUP BY path
ORDER BY hits DESC
LIMIT 20;

Output:

TEXT 📖 参照専用
 count 
-------
     5
(1 row)

▶ サンプル:FDWデータ移行の実践

SQL
-- レガシーからローカルにユーザーを移行
INSERT INTO users (name, email, created_at)
SELECT name, email, created_at
FROM legacy_schema.users
WHERE id NOT IN (SELECT legacy_id FROM users);

-- 増分同期にdblinkを使用
CREATE EXTENSION IF NOT EXISTS dblink;

SELECT dblink_connect('legacy', 'host=192.168.1.100 dbname=legacy user=admin password=secret123');

SELECT * FROM dblink('legacy',
    'SELECT id, name, email FROM users WHERE created_at > now() - interval ''1 day'''
) AS t(id BIGINT, name TEXT, email TEXT);

Output:

TEXT 📖 参照専用
INSERT 0 1

7. 概念:pgvectorベクトル検索

(1) ベクトル検索の仕組み

AIモデルはテキスト/画像を高次元ベクトル(エンベディング)にエンコードし、ベクトル間の距離が意味的な類似度を表します。pgvectorにより、PostgreSQLはネイティブにベクトルを保存・検索できます。

距離メトリック 計算式 ユースケース
L2距離(<=>) ユークリッド距離 空間距離
内積(<#>) ドット積 正規化ベクトル
コサイン距離(<=>) 1 - cos(θ) 意味的類似度

(2) pgvectorインデックス選択

100%
flowchart TD
    A["ベクトル列を作成"] --> B{"行数 < 10K?"}
    B -->|はい| C["厳密検索<br/>(インデックス不要)"]
    B -->|いいえ| D{"再現率要件?"}
    D -->|"高再現率(> 99%)"| E["IVFFlat<br/>(probes=lists)"]
    D -->|"高速+良好な再現率"| F["HNSW<br/>(ef_search調整)"]
    E --> G["CREATE INDEX ... USING ivfflat<br/>(vector_cosine_ops)"]
    F --> H["CREATE INDEX ... USING hnsw<br/>(vector_cosine_ops)"]
インデックス 構築速度 クエリ速度 再現率 最適な用途
インデックスなし(総当たり) N/A 遅い 100% < 10K
IVFFlat 中程度 高速 95-99% 10K-1M
HNSW 遅い 最速 97-99.5% 100K-10M+

8. 操作:pgvectorの実践

▶ サンプル:pgvectorをインストールしてベクトル列を作成

SQL
-- pgvectorをインストール
CREATE EXTENSION IF NOT EXISTS vector;

-- エンベディングベクトル付き商品テーブル(OpenAIの1536次元)
CREATE TABLE products_vec (
    id          SERIAL PRIMARY KEY,
    name        TEXT NOT NULL,
    category    TEXT,
    price       NUMERIC(10,2),
    embedding   vector(1536)
);

Output:

TEXT 📖 参照専用
CREATE TABLE

▶ サンプル:コサイン類似度による挿入と検索

SQL
-- エンベディング付き商品を挿入
INSERT INTO products_vec (name, category, price, embedding)
VALUES ('ワイヤレスヘッドホン', 'Electronics', 79.99,
    '[0.012, -0.034, 0.056, ...]'::vector);

-- コサイン距離で類似商品トップ5を検索
SELECT p.id, p.name, p.category, p.price,
       p.embedding <=> '[0.015, -0.030, 0.050, ...]'::vector AS distance
FROM products_vec p
ORDER BY p.embedding <=> '[0.015, -0.030, 0.050, ...]'::vector
LIMIT 5;

Output:

TEXT 📖 参照専用
INSERT 0 1

▶ サンプル:HNSWインデックスを作成

SQL
-- 高速近似検索用HNSWインデックス
CREATE INDEX idx_products_vec_hnsw ON products_vec
    USING hnsw (embedding vector_cosine_ops)
    WITH (m = 16, ef_construction = 64);

-- 検索精度と速度を調整
SET hnsw.ef_search = 100;

-- インデックス付きクエリ(大規模データセットではるかに高速)
SELECT name,
       embedding <=> '[0.015, -0.030, 0.050, ...]'::vector AS distance
FROM products_vec
ORDER BY embedding <=> '[0.015, -0.030, 0.050, ...]'::vector
LIMIT 10;

Output:

TEXT 📖 参照専用
CREATE TABLE

▶ サンプル:IVFFlatインデックスとチューニング

SQL
-- IVFFlatインデックス(最適なセントロイドのためにデータロード後に構築)
CREATE INDEX idx_products_vec_ivf ON products_vec
    USING ivfflat (embedding vector_cosine_ops)
    WITH (lists = 100);

-- 再現率向上のためにprobesを増加
SET ivfflat.probes = 10;

SELECT name, embedding <=> '[0.015, -0.030, 0.050, ...]'::vector AS dist
FROM products_vec
ORDER BY embedding <=> '[0.015, -0.030, 0.050, ...]'::vector
LIMIT 5;

Output:

TEXT 📖 参照専用
CREATE TABLE

▶ サンプル:完全なAI商品レコメンデーションフロー

SQL
-- AIモデルから商品エンベディングを保存
INSERT INTO products_vec (name, category, price, embedding)
VALUES
    ('ランニングシューズ',     'Sports',   129.99, array_to_vec(ARRAY[0.1,0.2,0.3])::vector),
    ('ヨガマット',          'Sports',    39.99, array_to_vec(ARRAY[0.11,0.19,0.31])::vector),
    ('Bluetoothスピーカー', 'Electronics', 49.99, array_to_vec(ARRAY[0.5,0.1,0.2])::vector);

-- ユーザーが「ランニングシューズ」を閲覧、類似商品をレコメンド
WITH target AS (
    SELECT embedding FROM products_vec WHERE name = 'ランニングシューズ'
)
SELECT p.name, p.category, p.price,
       p.embedding <=> (SELECT embedding FROM target) AS similarity
FROM products_vec p
WHERE p.name != 'ランニングシューズ'
ORDER BY similarity
LIMIT 3;

Output:

TEXT 📖 参照専用
INSERT 0 1

9. 操作:拡張管理のベストプラクティス

(1) バージョン管理

SQL
-- 現在の拡張バージョンを確認
SELECT extname, extversion FROM pg_extension ORDER BY extname;

-- 拡張をアップグレード
ALTER EXTENSION pgvector UPDATE TO '0.7.0';

-- 全拡張をアップグレード
SELECT extname,
       installed_version,
       default_version
FROM pg_available_extensions
WHERE installed_version IS NOT NULL
  AND installed_version != default_version;

(2) 権限管理

シナリオ 推奨プラクティス
本番環境での拡張インストール スーパーユーザーがCREATE EXTENSIONを実行
一般ユーザーの利用 GRANT USAGE ON SCHEMA / 関数の実行権限
FDW接続 USER MAPPINGで認証情報を保存、パスワードをハードコードしない
拡張アップグレード まずステージングでテストし、本番で実行

(3) 本番監査チェックリ��ト

チェック項目 説明
shared_preload_libraries pg_stat_statements/pgvectorはプリロードが必要
拡張のソース 公式または信頼できるサードパーティのみ使用
バージョンロック 本番の拡張バージョンを記録、自動アップグレードを避ける
セキュリティ監査 pgcryptoのキー管理、FDW認証情報の保護
アンインストールテスト DROP EXTENSIONの影響範囲を確認

10. 総合的な例

SQL
-- フルセットアップ:EコマースAI向け拡張 + FDW + pgvector

-- ステップ1: コア拡張をインストール
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE EXTENSION IF NOT EXISTS pgvector;

-- ステップ2: ベクトル検索付き商品カタログ
CREATE TABLE products_ai (
    id          UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
    name        TEXT NOT NULL,
    category    TEXT,
    price       NUMERIC(10,2),
    attrs       JSONB DEFAULT '{}',
    embedding   vector(1536)
);

-- ステップ3: あいまい名前検索用GINインデックス
CREATE INDEX idx_products_name_trgm ON products_ai
    USING GIN (name gin_trgm_ops);

-- ステップ4: ベクトル類似度用HNSWインデックス
CREATE INDEX idx_products_embedding ON products_ai
    USING hnsw (embedding vector_cosine_ops)
    WITH (m = 16, ef_construction = 64);

-- ステップ5: FDW経由でレガシーデータベースに接続
CREATE EXTENSION IF NOT EXISTS postgres_fdw;
CREATE SERVER legacy_db FOREIGN DATA WRAPPER postgres_fdw
    OPTIONS (host '192.168.1.50', port '5432', dbname 'legacy_shop');
CREATE USER MAPPING FOR current_user SERVER legacy_db
    OPTIONS (user 'migrate_user', password 'secure_pass');
IMPORT FOREIGN SCHEMA public LIMIT TO (old_products, old_users)
    FROM SERVER legacy_db INTO legacy;

-- ステップ6: データを移行・変換
INSERT INTO products_ai (name, category, price, attrs)
SELECT name, category, price,
       jsonb_build_object('weight_kg', weight, 'color', color)
FROM legacy.old_products
WHERE active = true;

-- ステップ7: あいまい検索+ベクトル検索の組み合わせ
SELECT p.name, p.price,
       similarity(p.name, 'wireless earbuds') AS text_score,
       p.embedding <=> '[0.015,-0.030,0.050]'::vector AS vec_dist
FROM products_ai p
WHERE p.name % 'wireless earbuds'
ORDER BY vec_dist ASC
LIMIT 5;

❓ よくある質問

Q CREATE EXTENSIONにスーパーユーザーは必要ですか?
A ほとんどの拡張はCの動的ライブラリをロードするためスーパーユーザーが必要です。PG 14+では GRANT CREATE ON DATABASE + 信頼された拡張メカニズムにより一部の拡張が許可されます。
Q pgvectorのベクトル列がサポートする最大次元数は?
A pgvectorは最大16,000次元(0.7.0+)をサポートしますが、モデルの出力に基づいて選択してください(OpenAI 1536、Cohere 1024など)。次元数が高いほどインデックスが遅くなります。
Q postgres_fdwのクエリパフォーマンスは?
A FDWにはネットワークオーバーヘッドがあり、単純なクエリで約2-5msのレイテンシが追加されます。use_remote_estimate=on を設定するとリモートがコスト見積もりを生成し、プラン品質が向上します。一括移行にはFDWよりCOPYを優先してください。
Q HNSWとIVFFlatのどちらを選ぶべき?
A 10万行未満ならインデックス不要、10万〜100万行ならIVFFlat(構築が速い)、10万行以上で低レイテンシが必要ならHNSW(クエリが最速)を選んでください。HNSWは構築が遅いですが、クエリはIVFFlatよりはるかに高速です。
Q pg_trgmのGINインデックスはINSERTを遅くしますか?
A はい。トライグラムインデックスは多くのトークンを分割するため、書き込みオーバーヘッドは通常のB-treeの約3-5倍です。読み取り重視で書き込みが少ない検索シナリオに最適です。
Q FDWはリモートテーブルに書き込めますか?
A postgres_fdwはリモートテーブルへのINSERT/UPDATE/DELETEをサポートします。file_fdwは読み取り専用です。リモートテーブルへの書き込みには��散トランザクションのリスクがあるため、読み取��専用クエリまたは一括移行にとどめてください。
Q 拡張のアップグレードはテーブルをロックしますか?
A 一般的にALTER EXTENSION UPDATEにはACCESS EXCLUSIVEロックが必要で、所要時間は拡張の内容に依存します。オフピーク時に実行し、事前にステージング▶検証してください。

📖 まとめ


📝 練習問題

  1. ⭐ uuid-osspとpgcrypto拡張をインストールし、UUIDを主キーとし、crypt() でパスワードハッシュを保存する api_tokens テーブルを作成し、パスワードを検証するクエリを書いてください。

  2. ⭐⭐ postgres_fdwを設定してリモートPGデータベースに接続し(Dockerでシミュレート)、リモートの products テーブルをインポートし、ローカルのordersテーブルとリモートのproductsテーブルのクロスデータベースJOINクエリを書いてください。

  3. ⭐⭐⭐ デュアルエンジン商品検索を設計してください:pg_trgmがあいまいテキスト検索を処理し、pgvectorが意味的ベクトル検索を処理します。テキストスコアとベクトル距離を融合してランキングする複合検索関数 search_products(keyword TEXT, query_vec vector, limit_count INT) を作成し、重み付けによる結果の違いをテストしてください。

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%