PostgreSQL: PostgreSQL拡張エコシステム、外部データラッパー、ベクトル検索
最終更新:2026-08-26
1. 学習内容
- PostgreSQLの拡張メカニズム(CREATE EXTENSION)を理解する
- 一般的な拡張を習得する:uuid-ossp、pgcrypto、pg_trgm、pg_stat_statements
- クロスデータベースクエリのための外部データラッパー(FDW)を学ぶ
- postgres_fdwとfile_fdwの設定と使用を実践する
- pgvectorのベクトルストレージと類似度検索を習得する
- 拡張管理のベストプラクティスを理解する
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つのコマンドでインストール・アンインストールできます。
-- 利用可能な拡張を一覧表示
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 / .dll が shared_preload_libraries または dynamic_library_path にあること |
| コントロールファイル | extension_name.control が SHAREDIR/extension/ にあること |
| SQLスクリプト | extension_name--version.sql がオブジェクトを定義 |
| 権限 | 現在のデータベースへの CREATE 権限 + 一部の拡張にはスーパーユーザーが必要 |
4. 操作:一般的な拡張
▶ サンプル:uuid-osspでUUIDを生成
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:
CREATE TABLE
▶ サンプル:pgcryptoデータ暗号化
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:
CREATE TABLE
▶ サンプル:pg_trgmあいまい検索
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;
name | score
---------------+-------
iPhone 15 Pro | 0.42
iPhone 14 | 0.38
(2 rows)
▶ サンプル:pg_stat_statementsスロークエリ統計
-- 最初に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:
result
----------
42.50
(1 row)
▶ サンプル:PostGIS空間データ入門
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:
INSERT 0 1
5. 概念:外部データラッパー
(1) FDWアーキテクチャ
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接続の設定
-- ステップ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:
count
-------
5
(1 row)
▶ サンプル:ローカルとリモートのクロスデータベースJOIN
-- ローカル:新しい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:
result
----------
42.50
(1 row)
▶ サンプル:file_fdwで外部CSVを読み取り
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:
count
-------
5
(1 row)
▶ サンプル:FDWデータ移行の実践
-- レガシーからローカルにユーザーを移行
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:
INSERT 0 1
7. 概念:pgvectorベクトル検索
(1) ベクトル検索の仕組み
AIモデルはテキスト/画像を高次元ベクトル(エンベディング)にエンコードし、ベクトル間の距離が意味的な類似度を表します。pgvectorにより、PostgreSQLはネイティブにベクトルを保存・検索できます。
| 距離メトリック | 計算式 | ユースケース |
|---|---|---|
| L2距離(<=>) | ユークリッド距離 | 空間距離 |
| 内積(<#>) | ドット積 | 正規化ベクトル |
| コサイン距離(<=>) | 1 - cos(θ) | 意味的類似度 |
(2) pgvectorインデックス選択
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をインストールしてベクトル列を作成
-- 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:
CREATE TABLE
▶ サンプル:コサイン類似度による挿入と検索
-- エンベディング付き商品を挿入
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:
INSERT 0 1
▶ サンプル:HNSWインデックスを作成
-- 高速近似検索用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:
CREATE TABLE
▶ サンプル:IVFFlatインデックスとチューニング
-- 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:
CREATE TABLE
▶ サンプル:完全なAI商品レコメンデーションフロー
-- 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:
INSERT 0 1
9. 操作:拡張管理のベストプラクティス
(1) バージョン管理
-- 現在の拡張バージョンを確認
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. 総合的な例
-- フルセットアップ: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;
❓ よくある質問
GRANT CREATE ON DATABASE + 信頼された拡張メカニズムにより一部の拡張が許可されます。use_remote_estimate=on を設定するとリモートがコスト見積もりを生成し、プラン品質が向上します。一括移行にはFDWよりCOPYを優先してください。📖 まとめ
- PostgreSQLの拡張メカニズム(CREATE EXTENSION)はプラグイン形式の管理を実現
- uuid-ossp(UUID)、pgcrypto(暗号化)、pg_trgm(あいまい検索)、pg_stat_statements(スロークエリ分析)が最も一般的な4つの拡張
- 外部データラッパー(FDW)により、PGは外部データソースをローカルテーブルのようにクエリ可能
- postgres_fdwはクロスPGクエリと移行に適し、file_fdwはCSVログ分析に適する
- pgvectorはベクトルデータ型+類似度検索を提供し、HNSWインデックスが最速でクエリ可能
- 拡張管理ではバージョン、権限、セキュリティ監査、本番レビュープロセスに注意が必要
📝 練習問題
-
⭐ uuid-osspとpgcrypto拡張をインストールし、UUIDを主キーとし、
crypt()でパスワードハッシュを保存するapi_tokensテーブルを作成し、パスワードを検証するクエリを書いてください。 -
⭐⭐ postgres_fdwを設定してリモートPGデータベースに接続し(Dockerでシミュレート)、リモートの
productsテーブルをインポートし、ローカルのordersテーブルとリモートのproductsテーブルのクロスデータベースJOINクエリを書いてください。 -
⭐⭐⭐ デュアルエンジン商品検索を設計してください:pg_trgmがあいまいテキスト検索を処理し、pgvectorが意味的ベクトル検索を処理します。テキストスコアとベクトル距離を融合してランキングする複合検索関数
search_products(keyword TEXT, query_vec vector, limit_count INT)を作成し、重み付けによる結果の違いをテストしてください。