10日で覚えるSystem DesignDay 3: データベース設計とスケーリング

Day 3: データベース設計とスケーリング

今日学ぶこと

  • SQL vs NoSQLの使い分け
  • データベースインデックスの仕組みと設計
  • レプリケーション戦略(Leader-Follower、Multi-Leader)
  • シャーディング/パーティショニング戦略
  • ACIDとBASEの違い
  • 面接でのデータモデリング手法

SQL vs NoSQL

flowchart TB
    subgraph SQL["SQL(リレーショナルDB)"]
        S1["MySQL"]
        S2["PostgreSQL"]
        S3["Oracle"]
    end
    subgraph NoSQL["NoSQL"]
        subgraph KV["Key-Value"]
            N1["Redis"]
            N2["DynamoDB"]
        end
        subgraph Doc["Document"]
            N3["MongoDB"]
            N4["CouchDB"]
        end
        subgraph Col["Wide-Column"]
            N5["Cassandra"]
            N6["HBase"]
        end
        subgraph Graph["Graph"]
            N7["Neo4j"]
            N8["Amazon Neptune"]
        end
    end
    style SQL fill:#3b82f6,color:#fff
    style KV fill:#8b5cf6,color:#fff
    style Doc fill:#22c55e,color:#fff
    style Col fill:#f59e0b,color:#fff
    style Graph fill:#ef4444,color:#fff
比較項目 SQL NoSQL
データモデル テーブル(行と列) Key-Value、Document、Wide-Column、Graph
スキーマ 固定スキーマ 柔軟(スキーマレス)
スケーリング 主に垂直 水平スケーリングに強い
トランザクション ACID保証 多くはBASE(結果整合性)
結合(JOIN) 強力 基本的に非対応
適するケース 複雑なクエリ、データの整合性が重要 大量データ、柔軟なスキーマ、高スループット

いつSQLを選ぶか?いつNoSQLを選ぶか?

flowchart TB
    Start["データベース選択"] --> Q1{"データ間の関係が\n複雑か?"}
    Q1 -->|"はい"| SQL["SQL を選択"]
    Q1 -->|"いいえ"| Q2{"ACID\nトランザクションが\n必須か?"}
    Q2 -->|"はい"| SQL
    Q2 -->|"いいえ"| Q3{"水平スケーリングが\n必要か?"}
    Q3 -->|"はい"| NoSQL["NoSQL を選択"]
    Q3 -->|"いいえ"| Q4{"スキーマが\n頻繁に変わるか?"}
    Q4 -->|"はい"| NoSQL
    Q4 -->|"いいえ"| SQL
    style Start fill:#3b82f6,color:#fff
    style SQL fill:#8b5cf6,color:#fff
    style NoSQL fill:#22c55e,color:#fff

データベースインデックス

インデックスは、データ検索を高速化するためのデータ構造です。

B-Treeインデックス

最も一般的なインデックス構造です。

// B-Tree index example for user table
CREATE INDEX idx_users_email ON users(email);

// Without index: Full table scan - O(n)
SELECT * FROM users WHERE email = 'user@example.com';

// With index: B-Tree lookup - O(log n)
// 100万件 → 約20回の比較で検索完了

インデックスのトレードオフ

メリット デメリット
SELECT(読み取り)が高速化 INSERT/UPDATE/DELETEが遅くなる
ソートが高速化 ストレージ容量を消費する
ユニーク制約の実現 インデックスの選択を誤ると効果なし

複合インデックス

-- Composite index: leftmost prefix rule applies
CREATE INDEX idx_orders_user_date ON orders(user_id, created_at);

-- Uses index (user_id is the leftmost column)
SELECT * FROM orders WHERE user_id = 123;

-- Uses index (both columns match)
SELECT * FROM orders WHERE user_id = 123 AND created_at > '2024-01-01';

-- Does NOT use index (created_at alone, missing leftmost column)
SELECT * FROM orders WHERE created_at > '2024-01-01';

面接でのポイント: 「クエリパターンに基づいてインデックスを設計する」と説明し、読み取り頻度が高いカラムにインデックスを張ることを提案しましょう。


レプリケーション

レプリケーションは、データを複数のノードにコピーして冗長性と読み取り性能を向上させる手法です。

Leader-Follower レプリケーション

flowchart TB
    Client["クライアント"]
    Client -->|"書き込み"| Leader["Leader\n(Primary)"]
    Leader -->|"レプリケーション"| F1["Follower 1\n(Replica)"]
    Leader -->|"レプリケーション"| F2["Follower 2\n(Replica)"]
    Client -->|"読み取り"| F1
    Client -->|"読み取り"| F2
    style Leader fill:#ef4444,color:#fff
    style F1 fill:#22c55e,color:#fff
    style F2 fill:#22c55e,color:#fff
方式 仕組み レイテンシ 一貫性
同期レプリケーション Followerの書き込み完了を待つ 高い 強い一貫性
非同期レプリケーション Leaderの書き込み完了で応答 低い 結果整合性
半同期 1台のFollowerのみ同期 中程度 バランス型

Multi-Leader レプリケーション

flowchart LR
    subgraph DC1["データセンター 東京"]
        L1["Leader 1"]
    end
    subgraph DC2["データセンター US"]
        L2["Leader 2"]
    end
    subgraph DC3["データセンター EU"]
        L3["Leader 3"]
    end
    L1 <-->|"双方向レプリケーション"| L2
    L2 <-->|"双方向レプリケーション"| L3
    L3 <-->|"双方向レプリケーション"| L1
    style DC1 fill:#3b82f6,color:#fff
    style DC2 fill:#8b5cf6,color:#fff
    style DC3 fill:#22c55e,color:#fff

Multi-Leaderの課題は書き込みコンフリクトです。解決策は以下の通りです:

解決策 仕組み
Last Write Wins (LWW) タイムスタンプが最新の書き込みが勝つ Cassandra
カスタムロジック アプリケーション側で解決 CRDTを使った実装
コンフリクト回避 同じデータは同じLeaderに書く ユーザーのホームリージョンを固定

シャーディング(パーティショニング)

データを複数のノードに分割して保存する手法です。

flowchart TB
    subgraph Sharding["シャーディング戦略"]
        subgraph Hash["ハッシュシャーディング"]
            HS["hash(user_id) % N"]
            HS --> HS1["Shard 0"]
            HS --> HS2["Shard 1"]
            HS --> HS3["Shard 2"]
        end
        subgraph Range["レンジシャーディング"]
            RS["user_id の範囲"]
            RS --> RS1["Shard 0\n1-1000"]
            RS --> RS2["Shard 1\n1001-2000"]
            RS --> RS3["Shard 2\n2001-3000"]
        end
        subgraph Geo["地理シャーディング"]
            GS["ユーザーの地域"]
            GS --> GS1["日本"]
            GS --> GS2["米国"]
            GS --> GS3["欧州"]
        end
    end
    style Hash fill:#3b82f6,color:#fff
    style Range fill:#8b5cf6,color:#fff
    style Geo fill:#22c55e,color:#fff
戦略 メリット デメリット 適するケース
ハッシュ データの均等分散 レンジクエリが困難 ユーザーデータ
レンジ レンジクエリが効率的 ホットスポットのリスク 時系列データ
地理 データローカリティ 地域間データアクセスが遅い グローバルサービス

シャーディングの課題

課題 説明 対策
JOINが困難 異なるシャード間のJOIN アプリケーション層で結合
リバランシング シャード追加時のデータ移行 Consistent Hashing
ホットスポット 特定シャードへの集中 シャードキーの選定を工夫
トランザクション 分散トランザクションが必要 Sagaパターン等

ACID vs BASE

flowchart LR
    subgraph ACID["ACID(SQL)"]
        A1["Atomicity\n原子性"]
        A2["Consistency\n一貫性"]
        A3["Isolation\n分離性"]
        A4["Durability\n永続性"]
    end
    subgraph BASE["BASE(NoSQL)"]
        B1["Basically Available\n基本的に利用可能"]
        B2["Soft State\n柔軟な状態"]
        B3["Eventually Consistent\n結果整合性"]
    end
    style ACID fill:#8b5cf6,color:#fff
    style BASE fill:#f59e0b,color:#fff
特性 ACID BASE
一貫性 常に整合性を保つ 結果的に整合する
可用性 一貫性のため可用性が下がる場合あり 高可用性を優先
パフォーマンス トランザクションのオーバーヘッド 高スループット
スケーリング 分散トランザクションは困難 水平スケーリングが容易
適するケース 銀行、ECの決済 SNS、ログ、分析

データモデリング(面接向け)

面接でのデータモデリングは、以下のステップで進めます。

ステップ1: エンティティの洗い出し

ユースケース: ECサイト
エンティティ: User, Product, Order, OrderItem, Review, Cart

ステップ2: リレーションの定義

User 1:N Order      (1人のユーザーが複数の注文)
Order 1:N OrderItem (1つの注文に複数の商品)
Product 1:N Review  (1つの商品に複数のレビュー)
User 1:1 Cart       (1人のユーザーに1つのカート)

ステップ3: アクセスパターンの分析

クエリ 頻度 DB選択
ユーザーの注文履歴を取得 SQL(JOINが必要)
商品検索(フルテキスト) 非常に高 Elasticsearch
ユーザーセッション管理 非常に高 Redis(Key-Value)
商品カタログ MongoDB(柔軟なスキーマ)

面接でのポイント: 「一つのDBで全てを解決する必要はない」と伝え、ユースケースに応じた**ポリグロットパーシステンス(多言語永続化)**を提案しましょう。


まとめ

今日のポイント

トピック キーポイント
SQL vs NoSQL JOINが多い→SQL、大規模・柔軟→NoSQL
インデックス 読み取りを高速化するが書き込みは遅くなる
レプリケーション 読み取り分散と障害耐性、同期vs非同期のトレードオフ
シャーディング ハッシュ・レンジ・地理の3戦略、シャードキーの選択が重要
ACID vs BASE 整合性重視→ACID、可用性重視→BASE
データモデリング エンティティ→リレーション→アクセスパターンの順で設計

DB選択チートシート

ユースケース 推奨DB
ユーザー情報・注文(トランザクション) PostgreSQL / MySQL
セッション・キャッシュ Redis
商品カタログ(柔軟なスキーマ) MongoDB
時系列データ(ログ・メトリクス) InfluxDB / TimescaleDB
全文検索 Elasticsearch
ソーシャルグラフ Neo4j
大規模分析 Cassandra / BigQuery

練習問題

基礎レベル

問題: 以下のユースケースに対して、SQLとNoSQLのどちらが適切か理由と共に答えてください。

  1. 銀行の口座振替システム
  2. IoTデバイスのセンサーデータ収集
  3. SNSのユーザープロフィール管理

中級レベル

問題: 月間1億件の注文を処理するECサイトのデータベースを設計してください。以下の点を考慮すること。

  • シャーディング戦略とシャードキーの選択
  • レプリケーション方式
  • インデックス設計(主要なクエリパターン3つ以上)

チャレンジレベル

問題: グローバル展開するSNSプラットフォームのデータ層を設計してください。

  • 3つの地域(日本・米国・欧州)にデータセンター
  • ユーザーは自分の投稿を即座に確認できる必要がある(Read-your-writes一貫性)
  • フォロワーのタイムラインは数秒の遅延は許容
  • シャーディング、レプリケーション、キャッシュ戦略を含めること

参考リンク


次回予告

Day 4: キャッシュとCDN では、データベースの負荷を軽減し、レスポンスを高速化するキャッシュ戦略を学びます。Cache-aside、Write-through、CDNアーキテクチャなど、パフォーマンス最適化の要を押さえましょう。