はじめに
「ダッシュボードの集計クエリが3秒かかる」——PostgreSQLのマテリアライズドビューで重い集計を事前計算し、1ms未満で返す設計をClaude Codeに生成させる。
CLAUDE.mdにマテリアライズドビュー設計ルールを書く
## マテリアライズドビュー設計ルール
### 適用基準
- クエリが複数テーブルJOIN + GROUP BY + 集計関数を含む
- データが数秒〜数分遅れても許容できる(ダッシュボード・レポート系)
### 更新戦略
- REFRESH CONCURRENTLY: 読み取りロックなし(本番推奨)
- スケジュール: 5分ごと(BullMQまたはpg-cron)
- 更新後はアプリキャッシュも無効化
### インデックス設計
- マテリアライズドビューにもインデックスを張る
生成される実装(抜粋)
-- 日次売上集計マテリアライズドビュー
CREATE MATERIALIZED VIEW mv_daily_revenue AS
SELECT
u.tenant_id,
DATE_TRUNC('day', o.created_at AT TIME ZONE 'Asia/Tokyo') AS sale_date,
p.category,
COUNT(DISTINCT o.id) AS order_count,
COUNT(DISTINCT o.user_id) AS unique_buyers,
SUM(oi.unit_price * oi.quantity) AS revenue,
AVG(oi.unit_price * oi.quantity) AS avg_order_value
FROM orders o
JOIN users u ON u.id = o.user_id
JOIN order_items oi ON oi.order_id = o.id
JOIN products p ON p.id = oi.product_id
WHERE o.status NOT IN ('cancelled', 'refunded') AND o.deleted_at IS NULL
GROUP BY u.tenant_id, DATE_TRUNC('day', o.created_at AT TIME ZONE 'Asia/Tokyo'), p.category
WITH DATA;
CREATE INDEX idx_mv_daily_revenue_tenant_date ON mv_daily_revenue (tenant_id, sale_date DESC);
// BullMQスケジューラーで5分ごとに自動更新
export async function refreshView(viewName: string): Promise<void> {
await prisma.$executeRawUnsafe(`REFRESH MATERIALIZED VIEW CONCURRENTLY ${viewName}`);
await redis.del(`cache:view:${viewName}:*`); // キャッシュも無効化
}
await refreshQueue.add(
'refresh-daily-revenue',
{ viewName: 'mv_daily_revenue' },
{ repeat: { every: 5 * 60 * 1000 } }
);
まとめ
- CLAUDE.md に適用基準(JOIN+GROUP BY+集計)・REFRESH CONCURRENTLY(本番必須)・5分更新間隔を明記
- CONCURRENT REFRESH はUNIQUEインデックスが前提——ビューにもインデックスを張ることで更新後のSELECTも高速
- BullMQスケジューラー で優先度別の更新間隔——売上は5分・コホートは1時間など頻度を使い分けてDB負荷を分散
- Redisキャッシュ+ビュー更新で二重高速化 ——ビュー更新と同時にキャッシュも無効化して次アクセス時にフレッシュなデータを5分キャッシュ
パフォーマンス設計のレビューは **Code Review Pack(¥980)* の /code-review で確認できます。*
みょうが (@myougatheaxo) — ウーパールーパーのVTuber。
Top comments (0)