postgresql-best-practices について
PostgreSQL を使う開発者向けに、スキーマ設計、クエリ最適化、セキュリティ、保守の実践方法をまとめています。ネイティブ型、インデックス、トランザクション、監視を扱います。
主な機能
- 01ネイティブ型とテーブル設計
- 02インデックス戦略と実行計画分析
- 03JSONB とウィンドウ関数の実践
- 04接続プール、トランザクション、ロック管理
- 05バックアップ、セキュリティ、性能監視
活用シーン
- 01制約を備えたリレーショナルスキーマの設計
- 02遅いクエリの診断とインデックス調整
- 03大規模テーブルのパーティション化と保守
- 04アクセス制御とバックアップ方針の確認
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ mindrally/ skills --skill postgresql-best-practicesインストール後、Claude Code で/postgresql-best-practicesを入力して読み込まれたか確認します。
詳細
PostgreSQL のベストプラクティス
基本原則
- PostgreSQL の高度な機能を活用し、堅牢なデータモデリングを実現します
- EXPLAIN ANALYZE と適切なインデックス戦略を使ってクエリを最適化します
- PostgreSQL のネイティブデータ型を適切に使用します
- 適切なコネクションプーリングとリソース管理を実装します
- PostgreSQL 固有のセキュリティのベストプラクティスに従います
スキーマ設計
データ型
- 適切なネイティブ型を使用します:
UUID、JSONB、ARRAY、INET、CIDR - タイムゾーンを考慮するアプリケーションでは、
TIMESTAMPよりTIMESTAMPTZを優先します - 長さの制限が不要な場合は、
VARCHARではなくTEXTを使用します - 正確な小数計算(金融データ)には
NUMERICを検討します - 自動採番 ID には
SERIALまたはBIGSERIALを、分散システムにはUUIDを使用します
CREATE TABLE orders (
order_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
customer_id UUID NOT NULL REFERENCES customers(customer_id),
order_data JSONB NOT NULL DEFAULT '{}',
tags TEXT[] DEFAULT '{}',
total_amount NUMERIC(12, 2) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
テーブル設計
- 必ず主キーを定義します
- 適切な ON DELETE/UPDATE アクションを指定した外部キーを使用します
- 適切な箇所に NOT NULL 制約を追加します
- データの検証には CHECK 制約を使用します
- 大規模なテーブルではパーティショニングを検討します
CREATE TABLE products (
product_id SERIAL PRIMARY KEY,
sku VARCHAR(50) NOT NULL UNIQUE,
name TEXT NOT NULL,
price NUMERIC(10, 2) NOT NULL CHECK (price >= 0),
status VARCHAR(20) NOT NULL DEFAULT 'active'
CHECK (status IN ('active', 'inactive', 'discontinued')),
metadata JSONB DEFAULT '{}'
);
パーティショニング
- 大規模なテーブル(数百万行)には宣言的パーティショニングを使用します
- 適切なパーティショニング戦略を選択します:RANGE、LIST、HASH
- パーティショニング後に、パーティション化テーブルにインデックスを作成します
CREATE TABLE events (
event_id BIGSERIAL,
event_type VARCHAR(50) NOT NULL,
payload JSONB,
created_at TIMESTAMPTZ NOT NULL
) PARTITION BY RANGE (created_at);
CREATE TABLE events_2024_q1 PARTITION OF events
FOR VALUES FROM ('2024-01-01') TO ('2024-04-01');
インデックス戦略
インデックスの種類
- 等価検索や範囲検索には B-tree インデックス(デフォルト)を使用します
- JSONB、配列、全文検索には GIN インデックスを使用します
- 幾何データや範囲型には GiST インデックスを使用します
- 大規模で自然に順序付けられたデータには BRIN インデックスを使用します
- フィルター付きクエリでは部分インデックスを検討します
-- B-tree index for common lookups
CREATE INDEX idx_orders_customer ON orders(customer_id);
-- GIN index for JSONB queries
CREATE INDEX idx_orders_data ON orders USING GIN (order_data);
-- Partial index for active records only
CREATE INDEX idx_active_products ON products(name) WHERE status = 'active';
-- Covering index to avoid table lookup
CREATE INDEX idx_orders_covering ON orders(customer_id)
INCLUDE (order_date, total_amount);
インデックスのメンテナンス
- 統計情報を更新するため、定期的に ANALYZE を実行します
- 肥大化したインデックスには REINDEX を使用します
pg_stat_user_indexesでインデックスの使用状況を監視します- 書き込み時のオーバーヘッドを減らすため、未使用のインデックスを削除します
-- Check index usage
SELECT schemaname, relname, indexrelname, idx_scan, idx_tup_read
FROM pg_stat_user_indexes
ORDER BY idx_scan ASC;
クエリの最適化
EXPLAIN ANALYZE
- 遅いクエリの実行計画を必ず分析します
- 大規模なテーブルでのシーケンシャルスキャンを確認します
- クエリの実行計画から不足しているインデックスを特定します
- 推定行数と実際の行数に大きな差がないか確認します
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT c.name, COUNT(o.order_id)
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
WHERE c.created_at > '2024-01-01'
GROUP BY c.customer_id, c.name;
共通テーブル式(CTE)
- 複雑なクエリを整理するために CTE を使用します
- 注意:古い PostgreSQL バージョンでは、CTE は最適化の障壁になります
- PostgreSQL 12 以降では、
MATERIALIZED/NOT MATERIALIZEDヒントを使用します
WITH recent_orders AS MATERIALIZED (
SELECT customer_id, COUNT(*) AS order_count, SUM(total) AS total_spent
FROM orders
WHERE order_date > CURRENT_DATE - INTERVAL '30 days'
GROUP BY customer_id
)
SELECT c.name, ro.order_count, ro.total_spent
FROM customers c
JOIN recent_orders ro ON c.customer_id = ro.customer_id
WHERE ro.total_spent > 1000;
ウィンドウ関数
- 分析クエリにはウィンドウ関数を使用します
- 複雑な計算には PARTITION BY と ORDER BY を活用します
SELECT
order_id,
customer_id,
total_amount,
SUM(total_amount) OVER (PARTITION BY customer_id ORDER BY order_date) AS running_total,
ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date DESC) AS order_rank
FROM orders;
JSONB のベストプラクティス
- パフォーマンスとインデックスの面で優れている JSONB を JSON より優先します
- クエリ対象の JSONB 列には GIN インデックスを作成します
- 効率的なクエリには包含演算子(@>、<@)を使用します
- 頻繁にクエリするフィールドは通常の列に抽出します
-- Efficient JSONB query with GIN index
SELECT * FROM products
WHERE metadata @> '{"category": "electronics"}';
-- Extract specific fields
SELECT
product_id,
metadata->>'brand' AS brand,
(metadata->>'rating')::numeric AS rating
FROM products
WHERE metadata ? 'rating';
コネクション管理
コネクションプーリング
- コネクションプーリングには PgBouncer または pgpool-II を使用します
- ワークロードに応じて適切なプールサイズを設定します
- 短時間のコネクションにはトランザクションプーリングモードを使用します
コネクション設定
-- Recommended session settings
SET statement_timeout = '30s';
SET lock_timeout = '10s';
SET idle_in_transaction_session_timeout = '60s';
トランザクションとロック
- 適切なトランザクション分離レベルを使用します
- ロック競合を減らすため、トランザクションを短く保ちます
- アプリケーションレベルのロックにはアドバイザリロックを使用します
- ロックの競合を監視し、解消します
-- Use advisory locks for application coordination
SELECT pg_advisory_lock(hashtext('resource_name'));
-- Do work
SELECT pg_advisory_unlock(hashtext('resource_name'));
-- Check for blocking queries
SELECT blocked_locks.pid AS blocked_pid,
blocking_locks.pid AS blocking_pid,
blocked_activity.query AS blocked_query
FROM pg_catalog.pg_locks blocked_locks
JOIN pg_catalog.pg_locks blocking_locks
ON blocking_locks.locktype = blocked_locks.locktype
AND blocking_locks.relation = blocked_locks.relation
AND blocking_locks.pid != blocked_locks.pid
JOIN pg_catalog.pg_stat_activity blocked_activity
ON blocked_activity.pid = blocked_locks.pid;
メンテナンス
Vacuum と Analyze
- autovacuum を有効にし、ワークロードに合わせて調整します
- 大量操作の後に手動で VACUUM ANALYZE を実行します
- テーブルの肥大化を監視します
-- Check table bloat
SELECT schemaname, relname,
n_live_tup, n_dead_tup,
round(n_dead_tup * 100.0 / nullif(n_live_tup + n_dead_tup, 0), 2) AS dead_pct
FROM pg_stat_user_tables
WHERE n_dead_tup > 1000
ORDER BY n_dead_tup DESC;
バックアップ戦略
- 論理バックアップには pg_dump を使用します
- 物理バックアップには pg_basebackup を使用します
- WAL アーカイブを使ってポイントインタイムリカバリ(PITR)を実装します
- バックアップからの復元を定期的にテストします
セキュリティ
- コネクションには SSL/TLS を使用します
- マルチテナントアプリケーションには行レベルセキュリティ(RLS)を実装します
- アクセス制御にはロールと GRANT/REVOKE を使用します
- pgAudit 拡張機能を使って機密性の高い操作を監査します
-- Enable row-level security
ALTER TABLE documents ENABLE ROW LEVEL SECURITY;
CREATE POLICY documents_tenant_policy ON documents
FOR ALL
USING (tenant_id = current_setting('app.tenant_id')::uuid);
-- Grant minimal privileges
GRANT SELECT, INSERT, UPDATE ON orders TO app_user;
GRANT USAGE ON SEQUENCE orders_order_id_seq TO app_user;
監視
- pg_stat_statements 拡張機能で監視します
- 遅いクエリを追跡し、定期的に最適化します
- レプリケーション遅延、コネクション数、ディスク使用量のアラートを設定します
- pg_stat_activity を使って実行中のクエリを監視します
-- Enable pg_stat_statements
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
-- Find slow queries
SELECT query, calls, mean_exec_time, total_exec_time
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 10;
コメント
Markdown 対応