Day 5: メッセージキューと非同期処理
今日学ぶこと
- 同期処理と非同期処理の違い
- メッセージキューの基本概念(Producer、Consumer、Topic、Partition)
- Kafka vs RabbitMQ vs SQS の比較
- イベント駆動アーキテクチャ
- Pub/Subパターン
- メッセージ配信保証(Exactly-once、At-least-once、At-most-once)
同期 vs 非同期
flowchart LR
subgraph Sync["同期処理"]
C1["クライアント"] -->|"リクエスト"| S1["サービス A"]
S1 -->|"待機..."| S2["サービス B"]
S2 -->|"待機..."| S3["サービス C"]
S3 -->|"レスポンス"| S2
S2 -->|"レスポンス"| S1
S1 -->|"レスポンス"| C1
end
style Sync fill:#ef4444,color:#fff
flowchart LR
subgraph Async["非同期処理"]
C2["クライアント"] -->|"リクエスト"| SA["サービス A"]
SA -->|"即座に応答"| C2
SA -->|"メッセージ"| MQ["メッセージキュー"]
MQ -->|"非同期処理"| SB["サービス B"]
MQ -->|"非同期処理"| SC["サービス C"]
end
style Async fill:#22c55e,color:#fff
style MQ fill:#f59e0b,color:#fff
| 比較項目 |
同期処理 |
非同期処理 |
| レスポンス時間 |
全処理完了まで待つ |
即座に応答可能 |
| 結合度 |
密結合(直接通信) |
疎結合(キュー経由) |
| 障害の影響 |
連鎖障害のリスク |
障害が局所化 |
| スケーリング |
全サービスを同時にスケール |
個別にスケール可能 |
| 複雑さ |
シンプル |
メッセージ管理が必要 |
| 適するケース |
即座に結果が必要 |
バックグラウンド処理、重い処理 |
非同期処理が適するユースケース
- メール/通知の送信
- 画像・動画のエンコーディング
- データ分析・集計
- 注文処理(支払い確認後の後続処理)
- ログ収集
メッセージキューの基本概念
flowchart LR
subgraph Producers["プロデューサー"]
P1["Producer 1"]
P2["Producer 2"]
end
subgraph Queue["メッセージキュー"]
subgraph T1["Topic: orders"]
Part1["Partition 0"]
Part2["Partition 1"]
Part3["Partition 2"]
end
end
subgraph Consumers["コンシューマー"]
CG["Consumer Group"]
C1["Consumer 1"]
C2["Consumer 2"]
C3["Consumer 3"]
end
P1 --> Part1
P2 --> Part2
Part1 --> C1
Part2 --> C2
Part3 --> C3
style Queue fill:#3b82f6,color:#fff
style T1 fill:#8b5cf6,color:#fff
style Producers fill:#22c55e,color:#fff
style Consumers fill:#f59e0b,color:#fff
主要な用語
| 用語 |
説明 |
| Producer |
メッセージを送信する側 |
| Consumer |
メッセージを受信・処理する側 |
| Topic |
メッセージのカテゴリ(チャンネル) |
| Partition |
Topic内のデータ分割単位 |
| Consumer Group |
同じTopicを協力して処理するConsumerの集合 |
| Offset |
Partition内のメッセージの位置(Kafka固有) |
| Acknowledgment (ACK) |
メッセージ処理完了の通知 |
Kafka vs RabbitMQ vs SQS
flowchart TB
subgraph Kafka["Apache Kafka"]
K1["分散ログストリーム"]
K2["高スループット"]
K3["メッセージ永続化"]
end
subgraph RabbitMQ["RabbitMQ"]
R1["従来型メッセージブローカー"]
R2["柔軟なルーティング"]
R3["豊富なプロトコル"]
end
subgraph SQS["Amazon SQS"]
S1["フルマネージド"]
S2["無限スケール"]
S3["運用負荷ゼロ"]
end
style Kafka fill:#3b82f6,color:#fff
style RabbitMQ fill:#8b5cf6,color:#fff
style SQS fill:#f59e0b,color:#fff
| 比較項目 |
Kafka |
RabbitMQ |
SQS |
| アーキテクチャ |
分散ログ |
メッセージブローカー |
マネージドキュー |
| スループット |
非常に高い(100万msg/s) |
中程度(数万msg/s) |
高い(自動スケール) |
| メッセージ保持 |
設定期間保持(リプレイ可) |
消費後削除 |
最大14日間 |
| 順序保証 |
Partition内で保証 |
キュー内で保証 |
FIFO版で保証 |
| 配信保証 |
At-least-once / Exactly-once |
At-least-once |
At-least-once / Exactly-once(FIFO) |
| ルーティング |
Topic/Partitionベース |
Exchange/Bindingで柔軟 |
シンプルなキュー |
| 運用コスト |
高い(自己管理) |
中程度 |
低い(マネージド) |
| 適するケース |
ログ収集、イベントストリーミング |
タスクキュー、複雑なルーティング |
AWSネイティブ、シンプルなキュー |
選択のガイドライン
flowchart TB
Start["メッセージキューの選択"] --> Q1{"イベントストリーミング\nやログ収集?"}
Q1 -->|"はい"| Kafka["Kafka"]
Q1 -->|"いいえ"| Q2{"複雑なルーティング\nが必要?"}
Q2 -->|"はい"| RabbitMQ["RabbitMQ"]
Q2 -->|"いいえ"| Q3{"AWSを\n使っている?"}
Q3 -->|"はい"| SQS["SQS"]
Q3 -->|"いいえ"| Q4{"スループットが\n非常に高い?"}
Q4 -->|"はい"| Kafka
Q4 -->|"いいえ"| RabbitMQ
style Start fill:#3b82f6,color:#fff
style Kafka fill:#22c55e,color:#fff
style RabbitMQ fill:#8b5cf6,color:#fff
style SQS fill:#f59e0b,color:#fff
イベント駆動アーキテクチャ
イベント駆動アーキテクチャでは、サービス間の通信をイベント(事実の記録)で行います。
flowchart TB
subgraph OrderService["注文サービス"]
OS["注文作成"]
end
OS -->|"OrderCreated\nイベント"| EB["イベントバス\n(Kafka)"]
EB --> PS["決済サービス\n支払い処理"]
EB --> IS["在庫サービス\n在庫確保"]
EB --> NS["通知サービス\nメール送信"]
EB --> AS["分析サービス\nデータ記録"]
style EB fill:#f59e0b,color:#fff
style OrderService fill:#3b82f6,color:#fff
style PS fill:#8b5cf6,color:#fff
style IS fill:#22c55e,color:#fff
style NS fill:#ef4444,color:#fff
style AS fill:#22c55e,color:#fff
コマンド vs イベント
| 項目 |
コマンド |
イベント |
| 意味 |
「〜してください」(命令) |
「〜が起きました」(事実) |
| 例 |
SendEmail |
OrderCreated |
| 方向 |
1対1 |
1対多 |
| 結合度 |
送信者が受信者を知っている |
送信者は受信者を知らない |
Pub/Subパターン
Pub/Sub(Publish/Subscribe)は、メッセージの送信者と受信者を完全に分離するパターンです。
flowchart TB
subgraph Publishers["パブリッシャー"]
Pub1["ユーザーサービス"]
Pub2["注文サービス"]
end
subgraph Topics["トピック"]
T1["user-events"]
T2["order-events"]
end
subgraph Subscribers["サブスクライバー"]
Sub1["通知サービス"]
Sub2["分析サービス"]
Sub3["検索サービス"]
end
Pub1 --> T1
Pub2 --> T2
T1 --> Sub1
T1 --> Sub2
T2 --> Sub1
T2 --> Sub2
T2 --> Sub3
style Topics fill:#3b82f6,color:#fff
style Publishers fill:#22c55e,color:#fff
style Subscribers fill:#8b5cf6,color:#fff
Pub/Subのメリット
| メリット |
説明 |
| 疎結合 |
PublisherはSubscriberの存在を知らない |
| スケーラビリティ |
Subscriberを独立にスケール可能 |
| 拡張性 |
新しいSubscriberの追加が容易 |
| 障害分離 |
1つのSubscriberの障害が他に影響しない |
メッセージ配信保証
分散システムでのメッセージ配信には3つの保証レベルがあります。
flowchart TB
subgraph Delivery["メッセージ配信保証"]
AtMost["At-Most-Once\n最大1回\n(メッセージ損失の可能性)"]
AtLeast["At-Least-Once\n最低1回\n(重複の可能性)"]
Exactly["Exactly-Once\n正確に1回\n(理想的だが困難)"]
end
style AtMost fill:#22c55e,color:#fff
style AtLeast fill:#f59e0b,color:#fff
style Exactly fill:#ef4444,color:#fff
| 保証レベル |
仕組み |
リスク |
適するケース |
| At-Most-Once |
送信後ACKを待たない |
メッセージ損失 |
ログ、メトリクス |
| At-Least-Once |
処理完了までリトライ |
重複処理 |
通知、メール送信 |
| Exactly-Once |
べき等性 + トランザクション |
実装が複雑、性能低下 |
決済、在庫管理 |
Exactly-Onceの実現方法
真のExactly-Onceは分散システムでは非常に困難です。一般的にはAt-Least-Once + べき等性で実現します。
def process_order(message):
order_id = message["order_id"]
if db.exists(f"processed:{order_id}"):
return
db.execute_transaction([
("INSERT INTO orders ...", order_data),
("INSERT INTO processed VALUES (%s)", order_id),
])
べき等性(Idempotency)
| 操作 |
べき等か |
理由 |
x = 5 |
はい |
何回実行しても結果は同じ |
x = x + 1 |
いいえ |
実行回数で結果が変わる |
DELETE FROM orders WHERE id = 123 |
はい |
2回目以降は何も起きない |
INSERT INTO orders VALUES (...) |
いいえ |
重複レコードが生成される |
実践:注文処理システムの設計
flowchart TB
Client["クライアント"] -->|"注文リクエスト"| API["API Gateway"]
API --> OrderSvc["注文サービス"]
OrderSvc -->|"OrderCreated"| Kafka["Kafka"]
Kafka --> PaymentSvc["決済サービス"]
PaymentSvc -->|"PaymentCompleted"| Kafka
Kafka --> InventorySvc["在庫サービス"]
InventorySvc -->|"InventoryReserved"| Kafka
Kafka --> ShippingSvc["配送サービス"]
ShippingSvc -->|"ShippingScheduled"| Kafka
Kafka --> NotificationSvc["通知サービス"]
subgraph DLQ["Dead Letter Queue"]
Failed["処理失敗メッセージ"]
end
PaymentSvc -->|"リトライ上限超過"| DLQ
InventorySvc -->|"リトライ上限超過"| DLQ
style Kafka fill:#3b82f6,color:#fff
style OrderSvc fill:#8b5cf6,color:#fff
style PaymentSvc fill:#22c55e,color:#fff
style InventorySvc fill:#f59e0b,color:#fff
style ShippingSvc fill:#22c55e,color:#fff
style NotificationSvc fill:#8b5cf6,color:#fff
style DLQ fill:#ef4444,color:#fff
設計のポイント
| 要素 |
設計判断 |
理由 |
| メッセージキュー |
Kafka |
高スループット、イベントリプレイが可能 |
| 配信保証 |
At-Least-Once + べき等性 |
注文の損失を防ぎつつ重複を安全に処理 |
| Dead Letter Queue |
失敗メッセージの退避先 |
リトライ上限超過時の安全策 |
| Saga パターン |
分散トランザクション管理 |
決済失敗時に在庫を戻す補償処理 |
Sagaパターン(補償トランザクション)
正常フロー:
注文作成 → 決済処理 → 在庫確保 → 配送手配
補償フロー(在庫確保失敗時):
在庫確保失敗 → 決済キャンセル → 注文キャンセル
まとめ
今日のポイント
| トピック |
キーポイント |
| 同期 vs 非同期 |
即時応答不要ならば非同期で疎結合に |
| メッセージキュー |
Producer→Topic→Partition→Consumer の流れ |
| Kafka vs RabbitMQ vs SQS |
ストリーミング→Kafka、ルーティング→RabbitMQ、マネージド→SQS |
| イベント駆動 |
コマンド(命令)ではなくイベント(事実)で通信 |
| Pub/Sub |
Publisher と Subscriber を完全に分離 |
| 配信保証 |
At-Least-Once + べき等性が現実的な選択 |
面接での使い方
1. 「この処理は非同期で良いか?」を常に問いかける
2. 非同期処理にはメッセージキューを提案
3. 配信保証レベルを明示する
4. 失敗時のリトライとDLQを忘れずに
5. 分散トランザクションにはSagaパターン
練習問題
基礎レベル
問題: 以下の処理について、同期と非同期のどちらが適切か理由と共に答えてください。
- ユーザーログイン認証
- 注文確認後のメール送信
- 画像のサムネイル生成
- 商品価格の取得
中級レベル
問題: 以下の要件を満たすメッセージングシステムを設計してください。
- ECサイトの注文イベントを処理
- 日次100万件の注文
- 決済、在庫、通知の3つのサービスが注文イベントを処理
- 決済は必ず成功させる必要がある(損失不可)
- 通知は多少の遅延・損失は許容
- どのメッセージキューを選択するか、配信保証レベルは何か
チャレンジレベル
問題: Uberのようなライドシェアサービスの注文処理を設計してください。
- ユーザーが配車をリクエスト
- 近くのドライバーにマッチング
- ドライバーがリクエストを承認/拒否
- リアルタイムの位置情報更新
- 乗車完了後の決済処理
- 同期処理と非同期処理をどこで使い分けるか
- メッセージキューをどのように活用するか
- 障害時の対策(ドライバーアプリがクラッシュした場合等)
参考リンク
次回予告
Day 6: API設計とプロトコル では、REST、GraphQL、gRPCの使い分け、レート制限、APIバージョニングなど、システムのインターフェース設計を学びます。面接では必ず問われるAPIの設計方法を身につけましょう。