目次
- システム設計とは
- スケーラビリティ
- ロードバランシング
- キャッシング戦略
- CDN
- データベース
- CAP定理
- メッセージキュー
- API設計
- モニタリングとロギング
- 実践例: URL短縮サービス設計
- 実践例: チャットシステム設計
システム設計とは
システム設計(System Design)とは、大規模ソフトウェアシステムのアーキテクチャを構築するプロセスである。単にコードを書くことを超えて、数百万のユーザーを処理できるスケーラブルで信頼性の高いシステムの構築方法を扱う。
なぜ重要か
- 実務: 実際のサービスは単一サーバーでは運用できない。トラフィック増加、障害対応、データ管理など多様な課題を解決する必要がある。
- 面接: FAANGをはじめとする大手テック企業のほとんどでシステム設計面接が実施される。
- キャリア成長: シニアエンジニアへの成長にはアーキテクチャレベルの思考力が不可欠である。
面接でのアプローチ
システム設計面接に正解はない。重要なのは思考プロセスである。
- 要件の明確化: 機能要件と非機能要件を整理する
- 規模の見積もり: ユーザー数、QPS、ストレージ容量を計算する
- ハイレベル設計: コアコンポーネントを配置する
- 詳細設計: ボトルネックを特定し解決する
- トレードオフの議論: すべての決定には長所と短所がある
スケーラビリティ
スケーラビリティとは、増加する負荷を処理するシステムの能力である。
垂直スケーリング (Scale Up)
単一サーバーにより強力なCPU、より多くのRAM、より大容量のディスクを追加する方式。
メリット:
- 実装がシンプル
- データ一貫性の維持が容易
- ネットワーク呼び出しがないためレイテンシが低い
デメリット:
- ハードウェアに物理的な限界がある
- 単一障害点(Single Point of Failure)になる
- コストが指数関数的に増加する
水平スケーリング (Scale Out)
複数のサーバーを追加して負荷を分散する方式。
メリット:
- 理論的に無制限のスケーリングが可能
- フォールトトレランスを確保できる
- コスト効率が良い
デメリット:
- 分散システムの複雑性が増加する
- データ一貫性の維持が困難
- ロードバランシングが必要
ステートレス設計
水平スケーリングの鍵はステートレス設計である。サーバーが状態を保持しなければ、どのサーバーでも同じリクエストを処理できる。
ステートフルサーバー:
ユーザーA -> サーバー1 (セッション保存)
ユーザーA -> サーバー2 (セッションなし -> エラー!)
ステートレスサーバー:
ユーザーA -> サーバー1 (外部ストアからセッション取得)
ユーザーA -> サーバー2 (外部ストアからセッション取得 -> 正常)
セッションやキャッシュなどの状態はRedisやMemcachedのような外部ストアに保管する。
ロードバランシング
ロードバランサーは、受信トラフィックを複数のサーバーに分散させる役割を果たす。
L4 vs L7 ロードバランシング
L4 (トランスポート層):
- TCP/UDPレベルで動作する
- IPとポート情報のみでルーティングする
- 高速で効率的
- 例: AWS NLB
L7 (アプリケーション層):
- HTTP/HTTPSレベルで動作する
- URLパス、ヘッダー、クッキー等に基づいてルーティングできる
- より細かい制御が可能
- 例: AWS ALB、Nginx
ロードバランシングアルゴリズム
ラウンドロビン (Round Robin): リクエストを順番に各サーバーへ配分する。実装がシンプルだが、サーバー性能の差を考慮しない。
加重ラウンドロビン (Weighted Round Robin): サーバーごとに重みを付与し、性能の高いサーバーにより多くのリクエストを配分する。
IPハッシュ (IP Hash): クライアントのIPをハッシュ化して常に同じサーバーにルーティングする。セッション維持が必要な場合に有用だが、サーバーの追加・削除時に再分配が必要。
最小接続数 (Least Connections): 現在の接続数が最も少ないサーバーにリクエストを送る。リクエスト処理時間が異なる場合に効果的。
ラウンドロビンの例:
リクエスト1 -> サーバーA
リクエスト2 -> サーバーB
リクエスト3 -> サーバーC
リクエスト4 -> サーバーA (循環)
リクエスト5 -> サーバーB (循環)
ヘルスチェック
ロードバランサーは定期的にサーバーの状態を確認する。応答がない、または異常応答を返すサーバーはロードバランシング対象から除外する。
ロードバランサー:
/health -> サーバーA: 200 OK (正常)
/health -> サーバーB: 503 Error (異常 -> 除外)
/health -> サーバーC: 200 OK (正常)
以降のトラフィックはサーバーAとCにのみ配分
キャッシング戦略
キャッシングとは、頻繁にアクセスされるデータを高速なストレージに一時保存してレスポンスタイムを短縮する技法である。
Cache Aside (Lazy Loading)
最も広く使われるパターン。
- アプリケーションがまずキャッシュを確認する
- キャッシュヒットならキャッシュから返す
- キャッシュミスならDBから取得後、キャッシュに保存して返す
読み取りフロー:
クライアント -> キャッシュ確認
ヒット -> キャッシュから返却
ミス -> DB検索 -> キャッシュ保存 -> 返却
メリット: 実際にリクエストされたデータのみキャッシュする。キャッシュ障害時にDBから直接取得可能。 デメリット: 最初のリクエストは必ずキャッシュミスが発生する。データ不整合の可能性がある。
Write Through
データ書き込み時にキャッシュとDBの両方に同時に記録するパターン。
書き込みフロー:
クライアント -> キャッシュ更新 -> DB更新 -> レスポンス
メリット: キャッシュとDBの一貫性が保証される。 デメリット: 書き込みレイテンシが増加する。使用されないデータもキャッシュされる。
Write Behind (Write Back)
データをまずキャッシュに書き込み、一定時間後に非同期でDBに反映するパターン。
書き込みフロー:
クライアント -> キャッシュ更新 -> レスポンス (即時)
バックグラウンド: キャッシュ -> DB同期 (遅延)
メリット: 書き込み性能が非常に高い。DBの負荷を削減できる。 デメリット: キャッシュ障害時にデータ喪失の可能性がある。
TTLとキャッシュ無効化
TTL (Time To Live): キャッシュエントリの有効期限を設定する。短すぎるとキャッシュ効率が低下し、長すぎるとデータ不整合が発生する。
無効化戦略:
- 時間ベース: TTL期限切れ時に自動削除
- イベントベース: データ変更時に即座に無効化
- バージョンベース: キーにバージョン情報を含め、新バージョン生成時に自動的に新データをキャッシュ
TTLの例:
SET user:123 "Kim" EX 3600 (1時間後に期限切れ)
SET product:456 "Laptop" EX 86400 (24時間後に期限切れ)
キャッシュスタンピード (Thundering Herd)
人気のあるキャッシュエントリが期限切れになった際、大量のリクエストが同時にDBに殺到する現象。
解決策:
- ロック: 1つのリクエストのみDBにアクセスし、他は待機
- 確率的早期リフレッシュ: TTL期限切れ前に確率的にキャッシュを更新
- TTLのずらし: キャッシュエントリごとに少しずつ異なるTTLを設定
CDN
CDN (Content Delivery Network) は、世界中に分散されたエッジサーバーを通じて、ユーザーに最も近い場所からコンテンツを提供するシステムである。
動作原理
ユーザー(東京) -> 東京エッジサーバー (キャッシュヒット) -> 即時レスポンス
ユーザー(東京) -> 東京エッジサーバー (キャッシュミス) -> オリジンサーバー -> エッジにキャッシュ -> レスポンス
静的アセットの配信
CDNは主に静的コンテンツの配信に使用される。
- 画像、動画、オーディオ
- CSS、JavaScriptファイル
- フォントファイル
- HTMLページ (静的サイト)
キャッシュヒット率の最適化
キャッシュヒット率 (Cache Hit Ratio) が高いほどCDNの効率が向上する。
最適化手法:
- 適切なCache-Controlヘッダーの設定
- ファイルバージョニング (例:
style.v2.cssやコンテンツハッシュの使用) - 変更頻度の低いコンテンツに長いTTLを設定
- クエリ文字列の正規化
Pull CDN vs Push CDN
Pull CDN: 最初のリクエスト時にオリジンからコンテンツを取得してキャッシュする。トラフィックの多いサイトに適する。
Push CDN: コンテンツを事前にCDNにアップロードする。コンテンツの変更が少ない場合に適する。
データベース
SQL vs NoSQL
SQL (リレーショナルデータベース):
- 構造化データ、スキーマベース
- ACIDトランザクション保証
- JOINによる複雑なクエリ対応
- 例: PostgreSQL、MySQL、Oracle
NoSQL (非リレーショナルデータベース):
- 柔軟なスキーマ
- 水平スケーリングが容易
- 高い読み書きスループット
- 例: MongoDB、Cassandra、DynamoDB、Redis
選択基準:
| 基準 | SQL | NoSQL |
|---|---|---|
| データ構造 | 構造化 | 柔軟 |
| スケーリング | 主に垂直 | 主に水平 |
| 一貫性 | 強い一貫性 | 結果整合性 (通常) |
| トランザクション | ACID | BASE |
| クエリ | 複雑なJOIN可能 | シンプルなキーバリュー最適化 |
シャーディング
データを複数のデータベースインスタンスに分散格納する技法。
ハッシュベースシャーディング: キーをハッシュ化してシャードを決定する。均等分配が可能だが、シャード追加・削除時の再分配コストが大きい。
user_id % 4 = 0 -> シャードA
user_id % 4 = 1 -> シャードB
user_id % 4 = 2 -> シャードC
user_id % 4 = 3 -> シャードD
レンジベースシャーディング: キーの範囲に応じてシャードを決定する。範囲クエリに有利だが、ホットスポットが発生する可能性がある。
コンシステントハッシング: ハッシュリングを使用して、ノード追加・削除時に最小限のキーのみ再配置する。分散キャッシュや分散DBで広く使用される。
レプリケーション
データを複数のノードに複製して可用性と読み取り性能を向上させる技法。
プライマリ・レプリカ (マスター・スレーブ):
- 書き込みはプライマリで、読み取りはレプリカで処理する
- プライマリ障害時にレプリカの1つを昇格させる
マルチプライマリ (マスター・マスター):
- 複数のノードで書き込みが可能
- コンフリクト解決戦略が必要
プライマリ・レプリカ構成:
書き込み -> [プライマリ]
|
+------+------+
| |
[レプリカ1] [レプリカ2]
| |
読み取り 読み取り
パーティショニング
データを論理的に分割する技法。
水平パーティショニング: 行単位で分割。シャーディングと類似。 垂直パーティショニング: 列単位で分割。頻繁にアクセスされる列とそうでない列を分離する。
CAP定理
分散システムにおいて、以下の3つの性質を同時にすべて満たすことはできないという定理。
3つの性質
Consistency (一貫性): すべてのノードが同一のデータを保持する。読み取りリクエストは常に最新のデータを返す。
Availability (可用性): すべてのリクエストに対してレスポンスを返す。一部のノードが障害状態でもシステムは正常応答する。
Partition Tolerance (分断耐性): ネットワーク分断が発生してもシステムが継続的に動作する。
実際の選択
ネットワーク分断 (Partition) は分散システムでは不可避であるため、実質的にはCPまたはAPのいずれかを選択する必要がある。
CPシステム (一貫性 + 分断耐性):
- ネットワーク分断時に一貫性のため一部のリクエストを拒否する可能性がある
- 例: HBase、MongoDB (デフォルト設定)、Zookeeper
APシステム (可用性 + 分断耐性):
- ネットワーク分断時に可用性のため古いデータを返す可能性がある
- 例: Cassandra、DynamoDB、CouchDB
CPシステム (分断発生時):
ノードA: 最新データ = "v2"
ノードB: 古いデータ = "v1" -> リクエスト拒否 (一貫性優先)
APシステム (分断発生時):
ノードA: 最新データ = "v2"
ノードB: 古いデータ = "v1" -> "v1"を返す (可用性優先)
PACELC
CAP定理の拡張で、分断がない場合(E: Else)のレイテンシ(L)と一貫性(C)間のトレードオフも考慮する。
- PA/EL: 分断時は可用性、通常時は低レイテンシ (例: DynamoDB)
- PC/EC: 分断時は一貫性、通常時も一貫性 (例: HBase)
- PA/EC: 分断時は可用性、通常時は一貫性 (例: MongoDB)
メッセージキュー
メッセージキューは非同期通信を可能にするミドルウェアである。プロデューサーとコンシューマーを分離し、システムの結合度を下げてスケーラビリティを向上させる。
なぜ必要か
同期通信の問題点:
- サービスAがサービスBを直接呼び出すと、Bがダウンした場合Aも影響を受ける
- リクエスト急増時に下流サービスが過負荷になる
- 処理時間の長いタスクが全体のレスポンスを遅延させる
同期 (問題):
注文API -> 決済サービス -> 在庫サービス -> 通知サービス -> レスポンス
(全体のレイテンシ = 各サービスのレイテンシの合計)
非同期 (解決):
注文API -> キューにイベント発行 -> 即座にレスポンス
決済サービス <- キューから消費
在庫サービス <- キューから消費
通知サービス <- キューから消費
(各サービスが独立に処理)
Apache Kafka
分散イベントストリーミングプラットフォーム。
主な特徴:
- 高スループット (秒間数百万メッセージ)
- メッセージの永続化 (ディスクに保存)
- コンシューマーグループによる並列処理
- トピックベースのパーティショニング
Kafkaアーキテクチャ:
Producer -> Topic (Partition 0, 1, 2) -> Consumer Group
Consumer A (P0)
Consumer B (P1)
Consumer C (P2)
ユースケース: ログ収集、イベントソーシング、リアルタイムストリーム処理、マイクロサービス間通信
RabbitMQ
AMQPプロトコルを使用する伝統的なメッセージブローカー。
主な特徴:
- 多様なルーティングパターン (Direct、Topic、Fanout、Headers)
- メッセージACKメカニズム
- 優先度キュー対応
- 柔軟なルーティング
ユースケース: タスクキュー、RPCパターン、複雑なルーティングが必要な場合
Amazon SQS
AWSのフルマネージドメッセージキューサービス。
主な特徴:
- サーバーレス、フルマネージド
- 自動スケーリング
- スタンダードキュー(最低1回配信)とFIFOキュー(正確に1回配信)のサポート
- Dead Letter Queueサポート
Kafka vs RabbitMQ vs SQS 比較
| 特性 | Kafka | RabbitMQ | SQS |
|---|---|---|---|
| スループット | 非常に高い | 中程度 | 高い |
| メッセージ順序 | パーティション内保証 | 保証可能 | FIFOのみ保証 |
| メッセージ保持 | 設定期間保持 | 消費後削除 | 最大14日 |
| 運用複雑度 | 高い | 中程度 | 低い (マネージド) |
| 適した用途 | イベントストリーミング | タスクキュー | サーバーレスワークロード |
イベントドリブンアーキテクチャ
メッセージキューを活用したイベントドリブンアーキテクチャは、マイクロサービス環境における核心パターンである。
イベントソーシング: 状態変更をイベントとして記録する。現在の状態はイベントを再生して再構築する。
CQRS: コマンド(書き込み)とクエリ(読み取り)を分離してそれぞれ最適化する。
イベントソーシングの例:
イベント1: 注文作成 (OrderCreated)
イベント2: 決済完了 (PaymentCompleted)
イベント3: 配送開始 (ShippingStarted)
イベント4: 配送完了 (DeliveryCompleted)
現在の状態 = イベント 1 + 2 + 3 + 4 の適用結果
API設計
REST vs GraphQL vs gRPC
REST:
- HTTPメソッド (GET、POST、PUT、DELETE) ベース
- リソース指向設計
- 最も広く採用されている
- オーバーフェッチング/アンダーフェッチングの問題
GET /api/users/123
POST /api/orders
PUT /api/users/123
DELETE /api/orders/456
GraphQL:
- クライアントが必要なデータを正確にリクエスト
- 単一エンドポイント
- スキーマベースの型システム
- オーバーフェッチング/アンダーフェッチングの解決
query {
user(id: "123") {
name
email
orders {
id
total
}
}
}
gRPC:
- Protocol Buffersベースのシリアライゼーション
- HTTP/2使用 (双方向ストリーミング)
- 高パフォーマンス
- マイクロサービス間通信に最適
service UserService {
rpc GetUser (GetUserRequest) returns (UserResponse);
rpc ListUsers (ListUsersRequest) returns (stream UserResponse);
}
API比較
| 特性 | REST | GraphQL | gRPC |
|---|---|---|---|
| プロトコル | HTTP/1.1 | HTTP/1.1 | HTTP/2 |
| データ形式 | JSON | JSON | Protocol Buffers |
| 型システム | なし (OpenAPIで補完) | 内蔵 | 内蔵 |
| ストリーミング | 制限的 | Subscription | 双方向ストリーミング |
| 適した用途 | 公開API | フロントエンドBFF | マイクロサービス |
APIバージョニング
API変更時に後方互換性を維持する戦略。
- URLパスバージョニング:
/api/v1/users,/api/v2/users - ヘッダーバージョニング:
Accept: application/vnd.api.v2+json - クエリパラメータ:
/api/users?version=2
レートリミティング
APIの乱用を防止し、システムを保護するためにリクエスト回数を制限する。
アルゴリズム:
- トークンバケット (Token Bucket): 一定速度でトークンを補充し、リクエスト時にトークンを消費する。バーストトラフィックを許容する。
- スライディングウィンドウ: 時間ウィンドウ内でリクエスト数をカウントする。境界問題が少ない。
- 固定ウィンドウ: 固定された時間間隔でカウンターをリセットする。実装がシンプル。
トークンバケット:
バケット容量: 10
補充速度: 1個/秒
時刻0: 10トークン (リクエスト5個 -> 5個残り)
時刻5: 10トークン (5個補充、上限10)
モニタリングとロギング
メトリクス
システムの健全性を数値で把握する。
主要メトリクス:
- レイテンシ: p50、p95、p99パーセンタイルのレスポンスタイム
- スループット: QPS (Queries Per Second)
- エラーレート: 4xx、5xxレスポンスの割合
- リソース使用率: CPU、メモリ、ディスク、ネットワーク
ツール: Prometheus、Grafana、Datadog、CloudWatch
アラーティング
メトリクスが閾値を超えた際に担当者へ通知を送る。
良いアラートの条件:
- アクション可能であること (受信者が行動できること)
- 誤検知 (False Positive) を最小化すること
- 重要度を区別すること (Critical、Warning、Info)
分散トレーシング
マイクロサービス環境でリクエストの全経路を追跡する。
ユーザーリクエスト (trace-id: abc123)
-> APIゲートウェイ (span-1, 5ms)
-> 認証サービス (span-2, 10ms)
-> 注文サービス (span-3, 50ms)
-> 決済サービス (span-4, 200ms)
-> 在庫サービス (span-5, 30ms)
-> 通知サービス (span-6, 15ms)
合計所要時間: 310ms
ツール: Jaeger、Zipkin、AWS X-Ray、OpenTelemetry
ロギング戦略
構造化ログ: JSON形式でログを記録すると、検索と分析が容易になる。
{
"timestamp": "2026-04-12T10:30:00Z",
"level": "ERROR",
"service": "order-service",
"trace_id": "abc123",
"message": "Payment failed",
"user_id": "user-789",
"order_id": "order-456",
"error_code": "INSUFFICIENT_FUNDS"
}
ELKスタック (Elasticsearch + Logstash + Kibana) または Loki + Grafana の組み合わせでログを収集、保存、可視化する。
実践例: URL短縮サービス設計
面接で頻出のシステム設計問題。
要件
機能要件:
- 長いURLを短いURLに変換
- 短いURLでアクセス時に元のURLへリダイレクト
- カスタム短縮URL対応
- URL有効期限機能
非機能要件:
- 高可用性
- 低レイテンシ (リダイレクトは高速である必要がある)
- 短縮URLは推測不可能であること
規模見積もり
- 書き込み: 1日1億件、QPSは約1,160
- 読み取り: 書き込みの10倍 = 1日10億件、QPSは約11,600
- ストレージ: 5年間で約1,800億件、レコードあたり500バイト = 約90TB
ハイレベル設計
クライアント -> ロードバランサー -> APIサーバー -> DB
|
キャッシュ (Redis)
短縮キー生成
方法1: ハッシュ + トランケート
- MD5やSHA-256でハッシュ生成後、先頭7文字を使用
- Base62エンコーディング (a-z、A-Z、0-9) = 62の7乗 = 約3.5兆通り
方法2: カウンターベース
- 自動増分IDをBase62でエンコードする
- 分散環境ではZookeeperやSnowflake IDを活用
方法3: 事前生成
- キーを事前に生成してDBに保存する
- キー生成サービス (Key Generation Service, KGS) がキーを管理する
詳細設計
リダイレクトフロー:
1. クライアントが short.url/abc123 をリクエスト
2. ロードバランサーがAPIサーバーへ転送
3. APIサーバーがRedisキャッシュを確認
4. キャッシュヒット -> 301 Redirect レスポンス
5. キャッシュミス -> DB検索 -> キャッシュ保存 -> 301 Redirect
301 vs 302 リダイレクト:
- 301 (Permanent): ブラウザがキャッシュする。サーバー負荷軽減。分析データ収集が困難。
- 302 (Temporary): 毎回サーバーを経由する。クリック分析が可能。サーバー負荷増加。
実践例: チャットシステム設計
要件
機能要件:
- 1対1チャットとグループチャット
- メッセージの送受信
- オンライン/オフラインステータス表示
- 既読確認
- ファイル/画像送信
- メッセージ検索
非機能要件:
- リアルタイムメッセージ配信 (低レイテンシ)
- メッセージ順序保証
- メッセージの永続化 (データ喪失なし)
- 高可用性
通信プロトコル
HTTPポーリング: クライアントが定期的にサーバーへ新着メッセージを確認する。実装がシンプルだが非効率的。
ロングポーリング: サーバーが新着メッセージがあるまで接続を維持する。ポーリングより効率的だが、タイムアウト管理が必要。
WebSocket: 双方向リアルタイム通信が可能。初回HTTPハンドシェイク後、持続的接続を維持する。リアルタイムチャットに最適。
HTTPポーリング:
クライアント -> サーバー: 新着メッセージ? (毎1秒)
サーバー -> クライアント: なし
クライアント -> サーバー: 新着メッセージ? (毎1秒)
サーバー -> クライアント: あり! (メッセージ配信)
WebSocket:
クライアント <-> サーバー: 常時接続維持
サーバー -> クライアント: 新着メッセージを即座にpush
ハイレベル設計
送信者 -> WebSocketサーバー -> メッセージキュー -> WebSocketサーバー -> 受信者
| |
メッセージDB プレゼンスサービス
|
メッセージ検索 (Elasticsearch)
メッセージストアの選択
チャットメッセージの特性:
- 順次書き込み (append only)
- 最近のメッセージの読み取りが大部分
- ランダム読み取りは検索時のみ発生
- データ量が非常に多い
適切なDB:
- Cassandra: 高い書き込みスループット、時間ベースパーティショニングに最適
- HBase: 大規模順次書き込みに最適化
メッセージID設計
順序保証のため、IDは時間順にソート可能である必要がある。
Snowflake ID方式:
| タイムスタンプ (41ビット) | データセンター (5ビット) | マシン (5ビット) | シーケンス (12ビット) |
- 時間順にソート可能
- 分散環境で衝突なし
- マシンあたり秒間4,096 ID生成可能
プレゼンス管理
ハートビート方式: クライアントが定期的に(例: 5秒間隔)サーバーへハートビートを送信する。一定時間ハートビートがなければオフラインと判定する。
ユーザーA: ハートビート送信 (5秒間隔)
サーバー: 最終ハートビート時刻を記録
30秒間ハートビートなし -> ステータスを「オフライン」に変更
グループチャット
グループチャットでは、メッセージをグループ内の全メンバーに配信する必要がある。
小規模グループ (数十人):
- 各メンバーのメッセージキューに直接コピーする
大規模グループ (数千人):
- ファンアウト方式は非効率的
- 受信者がメッセージをプル (Pull) する方式がより適切
まとめ
システム設計の核心はトレードオフを理解することである。完璧なシステムは存在せず、常に要件に合った最適な選択をする必要がある。
核心原則のまとめ:
- シンプルに始め、必要に応じて複雑性を追加する
- スケーラビリティは最初から考慮するが、過度なエンジニアリングは避ける
- 障害は必ず発生する。フォールトトレランスを設計に組み込む
- モニタリングなしではシステムを運用できない
- データは最も重要な資産である。バックアップとレプリケーションを必ず実装する
この記事で扱った概念はシステム設計の基礎に該当する。実際のプロジェクトでこれらの概念を適用し、多様なシステム設計事例を学習しながらスキルを磨いていただきたい。
현재 단락 (1/436)
1. [システム設計とは](#システム設計とは)