mcpField
P
postgresql-best-practices

postgresql-best-practices

github.com/mindrallyデータ・データベース2871.5k 回インストール3 週間前に更新
リポジトリ

postgresql-best-practices について

PostgreSQL を使う開発者向けに、スキーマ設計、クエリ最適化、セキュリティ、保守の実践方法をまとめています。ネイティブ型、インデックス、トランザクション、監視を扱います。

主な機能

  1. 01ネイティブ型とテーブル設計
  2. 02インデックス戦略と実行計画分析
  3. 03JSONB とウィンドウ関数の実践
  4. 04接続プール、トランザクション、ロック管理
  5. 05バックアップ、セキュリティ、性能監視

活用シーン

  1. 01制約を備えたリレーショナルスキーマの設計
  2. 02遅いクエリの診断とインデックス調整
  3. 03大規模テーブルのパーティション化と保守
  4. 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;

コメント