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のどちらが適切か理由と共に答えてください。
- 銀行の口座振替システム
- IoTデバイスのセンサーデータ収集
- SNSのユーザープロフィール管理
中級レベル
問題: 月間1億件の注文を処理するECサイトのデータベースを設計してください。以下の点を考慮すること。
- シャーディング戦略とシャードキーの選択
- レプリケーション方式
- インデックス設計(主要なクエリパターン3つ以上)
チャレンジレベル
問題: グローバル展開するSNSプラットフォームのデータ層を設計してください。
- 3つの地域(日本・米国・欧州)にデータセンター
- ユーザーは自分の投稿を即座に確認できる必要がある(Read-your-writes一貫性)
- フォロワーのタイムラインは数秒の遅延は許容
- シャーディング、レプリケーション、キャッシュ戦略を含めること
参考リンク
- Designing Data-Intensive Applications (Martin Kleppmann)
- Database Internals (Alex Petrov)
- Use The Index, Luke
次回予告
Day 4: キャッシュとCDN では、データベースの負荷を軽減し、レスポンスを高速化するキャッシュ戦略を学びます。Cache-aside、Write-through、CDNアーキテクチャなど、パフォーマンス最適化の要を押さえましょう。