OLAP Anti-Patterns
一部のコンテンツは LLM によって生成されており、まだ手動で検証されていません。
OLAP(Online Analytical Processing)システムの一般的 なアンチパターンとベストプラクティス。
スキーマ設計
| アンチパターン | 問題 | ベストプラクティス |
|---|---|---|
| 過度 な正規化 | 分析 クエリに大量 のJOINが必要 、性能 が悪 い | スターまたはスノーフレークスキーマを使用 (適度 な非正規化 ) |
| 過度 なスノーフレークモデル | ディメンションテーブルが細 かく分割 、クエリの複雑 さが増加 | 正規化 とクエリ性能 のバランス、ディメンションのフラット化 を検討 |
スター vs スノーフレークモデル
スタースキーマ(Star Schema):
┌─────────────┐
│ dim_time │
└──────┬──────┘
│
┌───────────┐ │ ┌────────────┐
│dim_product├──┼──┤dim_customer│
└───────────┘ │ └────────────┘
│
┌──────┴──────┐
│ fact_sales │
└─────────────┘ディメンションテーブルがファクトテーブルに直接 接続
クエリが簡単 、JOINが少 ない
大半 の分析 シナリオに適合
スノーフレークスキーマ(Snowflake Schema):
┌──────────────┐
│ dim_category │
└──────┬───────┘
│
┌──────┴───────┐ ┌─────────────┐
│ dim_product ├────┤ fact_sales │
└──────────────┘ └─────────────┘ディメンションテーブルがさらに正規化
データの冗長性 を削減
より多 くのJOINが必要
ETL/クエリ最適化
| アンチパターン | 問題 | ベストプラクティス |
|---|---|---|
| 全量 再構築 | 毎回 ETLでテーブル全体 を再構築 、時間 とリソースを消費 | 増分 ロードを使用 |
| ビューへの過度 な依存 | 多層 入れ子 ビュー、実行 計画 の最適化 が困難 | マテリアライズドビューまたは事前 計算 テーブル |
| クエリ効率 が悪 い | パーティション、インデックス、データ分布 特性 を活用 していない | パーティションプルーニング、列 指向 ストレージの利点 を活用 |
| 過度 なアドホッククエリ | ユーザーが複雑 なクエリを直接 実行 、システムに影響 | 事前 構築 レポートまたはBIツール層 を提供 |
| クエリ内 の集計 ロジック | 毎回 クエリで集計 を再計算 | 事前 計算 した集計 をサマリー テーブルに保存 |
増分ロード戦略
アンチパターン(全量 再構築 ):
TRUNCATE TABLE fact_sales;
INSERT INTO fact_sales SELECT * FROM staging_sales;ベストプラクティス(増分 ロード):
-- タイムスタンプまたはCDCで変更を識別
MERGE INTO fact_sales AS target
USING (
SELECT * FROM staging_sales
WHERE updated_at > @last_load_time
) AS source
ON target.id = source.id
WHEN MATCHED THEN UPDATE SET ...
WHEN NOT MATCHED THEN INSERT ...;増分 ロードの種類 : | 種類 | 適用
| シナリオ | |
|---|---|
| Append Only | 新規 |
| データのみ(ログなど) | |
| Upsert (MERGE) | 新規 |
| と更新 | |
| あり | |
| CDC | 削除 |
| の追跡 | |
| が必要 | |
| SCD Type 2 | 履歴 |
| 変更 | |
| の保持 | |
| が必要 | |
マテリアライズドビューの使用
アンチパターン(多層 入れ子 ビュー):
CREATE VIEW v1 AS SELECT ... FROM base_table;
CREATE VIEW v2 AS SELECT ... FROM v1 WHERE ...;
CREATE VIEW v3 AS SELECT ... FROM v2 JOIN ...;
-- v3をクエリする際、オプティマイザが良い実行計画を生成しにくいベストプラクティス(マテリアライズドビュー):
CREATE MATERIALIZED VIEW mv_daily_sales AS
SELECT
date_trunc('day', sale_date) AS day,
product_id,
SUM(amount) AS total_amount,
COUNT(*) AS transaction_count
FROM fact_sales
GROUP BY 1, 2;
-- 定期的にリフレッシュ
REFRESH MATERIALIZED VIEW mv_daily_sales;パーティション戦略
一般的 なパーティション方式 :
| パーティション種類 | 適用 シナリオ | 例
| Range | 時系列 | |
| データ | 月 | |
| /年 | ||
| でパーティション | ||
| List | 離散 | |
| 値 | ||
| 地域 | ||
| /カテゴリ | ||
| Hash | 均等 | |
| 分散 | ||
| ID hash |
パーティションプルーニングの例 :
-- パーティションテーブルの作成
CREATE TABLE fact_sales (
id BIGINT,
sale_date DATE,
amount DECIMAL(10,2)
) PARTITION BY RANGE (sale_date);
-- クエリ時に自動プルーニング
SELECT SUM(amount)
FROM fact_sales
WHERE sale_date BETWEEN '2024-01-01' AND '2024-01-31';
-- 2024-01パーティションのみスキャン事前計算集計テーブル
アンチパターン(毎回 リアルタイム計算 ):
-- ダッシュボードが毎回ロード時に実行
SELECT
region,
product_category,
SUM(amount) AS total_sales
FROM fact_sales
WHERE sale_date >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY region, product_category;ベストプラクティス(事前 計算 ):
-- 日次ETLタスクで事前計算
CREATE TABLE agg_daily_sales AS
SELECT
sale_date,
region,
product_category,
SUM(amount) AS total_sales,
COUNT(*) AS num_transactions
FROM fact_sales
GROUP BY sale_date, region, product_category;
-- ダッシュボードが事前計算テーブルをクエリ
SELECT region, product_category, SUM(total_sales)
FROM agg_daily_sales
WHERE sale_date >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY region, product_category;