System Design: Cloudflare Serverless Chat App
Cloudflare Workers, Durable Objects, KV, D1を活用したグローバルリアルタイムチャットアプリのシステム設計。
グローバルスケールで低遅延なリアルタイムチャットアプリケーションを構築することは、System Designにおける古典的かつ高度な課題です。従来のAWS Lambda等のサーバーレス環境では、ステートレスな性質上、長時間接続を維持するWebSocketの実装が困難でした。
Cloudflare Edge Platform(Workers、Durable Objects、Cloudflare KV、D1)を組み合わせることで、完全サーバーレスで堅牢なチャット基盤を構築できます。
1. 課題:サーバーレスとWebSocketの相性#
従来のサーバーレス構成での課題:
- WebSocketの接続状態維持: 常時接続(Long-lived Connection)が必要。
- メッセージのルーティング: 世界中のエッジノード間に存在するユーザーへのメッセージ配信。
- データ永続化と低遅延: 過去ログの高速な保存・取得。
2. システム構成概要 (Architecture)#
- Cloudflare Workers: グローバルAPI GatewayとしてWebSocketハンドシェイクを処理。
- Durable Objects (DO): エッジ上で動くSingleton Actor。チャットルームごとのWebSocket接続管理とメッセージ配信を担当。
- Cloudflare D1: 分散SQLiteデータベース。メッセージ履歴やユーザー情報を保存。
- Cloudflare KV: 超低遅延なKey-Valueストア。セッションキャッシュ等に使用。
+--------------+ WebSocket +---------------------+
| User Client | <------------------> | Cloudflare Workers |
+--------------+ +---------------------+
|
Forward / Stub Routing
v
+---------------------+
| Durable Object | (Chat Room State)
| (Room 101 Instance)|
+---------------------+
/ \
v v
+----------------+ +----------------+
| Cloudflare D1 | | Cloudflare KV |
| (Message History)| | (User Session) |
+----------------+ +----------------+text3. 実装例 (Code Implementation)#
3.1. Durable Object: ルーム状態の管理#
src/room.ts
export class ChatRoom implements DurableObject {
private state: DurableObjectState;
private sessions: Set<WebSocket> = new Set();
constructor(state: DurableObjectState) {
this.state = state;
}
async fetch(request: Request): Promise<Response> {
const pair = new WebSocketPair();
const [client, server] = Object.values(pair);
this.handleSession(server);
return new Response(null, { status: 101, webSocket: client });
}
private handleSession(ws: WebSocket) {
ws.accept();
this.sessions.add(ws);
ws.addEventListener('message', async (msg) => {
try {
const data = JSON.parse(msg.data as string);
// 同一ルーム内の全クライアントへブロードキャスト // [!code focus]
this.broadcast(JSON.stringify({
user: data.user,
text: data.text,
timestamp: new Date().toISOString()
}));
} catch (err) {
ws.send(JSON.stringify({ error: 'Invalid payload' }));
}
});
ws.addEventListener('close', () => {
this.sessions.delete(ws);
});
}
private broadcast(message: string) {
for (const session of this.sessions) {
session.send(message);
}
}
}typescript4. 利点と制限 (Trade-offs)#
[!TIP] メリット:
- Zero Cold Starts: V8 Isolatesによる超高速起動。
- Global Scalability: ユーザーに最も近いエッジで接続処理。
[!WARNING] 注意点:
- 単一DOインスタンスで数十万人の大規模配信を行う場合は、DOの階層構造(Broadcast Tree)の設計が必要になります。