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;