#0143（2025/05/19）エンジニアのための WebSocket 実践ガイド

## エンジニアのための WebSocket 実践ガイド

### 1. WebSocket 概要

WebSocket は **RFC 6455** で策定されたフルデュープレックス通信プロトコルです。最初に HTTP/1.1 の `Upgrade: websocket` 握手を経て、以降は独立した軽量フレームでやりとりするため、毎リクエストごとにヘッダを積む REST‐HTTP とくらべてレイテンシ・帯域の両面で効率的です。HTTP/2 環境でも **RFC 8441 (Extended CONNECT)** により 1 本の TCP で多重化でき、HTTP/3 では派生技術の WebTransport が UDP-ライクな通信を補完します。

WebSocket フレームは最小 2 byte のヘッダに `OPCODE`, `FIN`, `MASK` などを持ち、クライアント→サーバは必ずマスクが義務づけられる設計です。 `Ping/Pong`・`Close` など制御フレームが標準化されているため、TCP Keep-Alive では測れないアプリ層レベルの生存確認や Graceful Shutdown を自前で制御できます。

---

### 2. WebSocket が真価を発揮する場面

1. **リアルタイム双方向同期**

   * チャット、共同ドキュメント編集、オンラインホワイトボード。
2. **高頻度 Push**

   * 株価ティッカー、IoT センサストリーム、ゲーム状態のブロードキャスト。
3. **近似リアルタイムで大量接続**

   * ライブメトリクス監視、大規模スポーツ配信のコメントストリーム。

「サーバ → クライアント片方向」かつ接続数が限定的なら SSE(Server-Sent Events)、順序保証より低遅延を優先する UDP 系なら WebTransport/Datagram の検討がベターです。**要件が“低レイテンシ双方向”なら WebSocket 一択**と覚えてください。

---

### 3. FastAPI + Uvicorn で組むミニチャット

> Python 3.11／FastAPI 0.110 系で動作確認

```python
# app.py
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from collections import defaultdict
import asyncio, json, uuid

app = FastAPI(title="Realtime Chat over WebSocket")
rooms: dict[str, set[WebSocket]] = defaultdict(set)

async def broadcast(room: str, payload: dict):
    dead = set()
    msg = json.dumps(payload, ensure_ascii=False)
    for ws in rooms[room]:
        try:
            await ws.send_text(msg)
        except Exception:           # 送信失敗＝切断
            dead.add(ws)
    rooms[room] -= dead

@app.websocket("/ws/{room}")
async def ws_endpoint(ws: WebSocket, room: str):
    await ws.accept()
    rooms[room].add(ws)
    cid = uuid.uuid4().hex[:8]

    try:
        await broadcast(room, {"sys": f"🟢 {cid} joined"})
        while True:
            txt = await asyncio.wait_for(ws.receive_text(), timeout=30)
            await broadcast(room, {"user": cid, "msg": txt})
    except (WebSocketDisconnect, asyncio.TimeoutError):
        await broadcast(room, {"sys": f"🔴 {cid} left"})
    finally:
        rooms[room].discard(ws)
        if not rooms[room]:
            rooms.pop(room, None)
```

```bash
pip install fastapi "uvicorn[standard]"
uvicorn app:app --host 0.0.0.0 --port 8000
```

* **Ping/Pong** は `uvicorn --ws ping-interval 20 --ws ping-timeout 10` で自動化。
* 水平スケール時は `rooms` を Redis PUB/SUB や NATS に置換し、Pod 間でブロードキャストを共有。

---

### 4. HTTP(Web API) との対比

| 観点       | REST/GraphQL over HTTP   | WebSocket               |
| -------- | ------------------------ | ----------------------- |
| 接続モデル    | リクエスト → レスポンス／ステートレス     | 常時接続フルデュープレックス          |
| オーバーヘッド  | TLS + ヘッダを毎回             | 初回 Upgrade 後は数 byte     |
| 負荷分散     | コネクション短命で容易              | Sticky 必須 or 外部 Pub/Sub |
| キャッシュ    | CDN/SWR 等が有効             | 基本不可                    |
| セキュリティ機構 | CORS, CSRF, OAuth 充実     | Origin 検証と TLS のみ。再認証不可 |
| 代表ユースケース | CRUD API, バッチ, 公開エンドポイント | チャット, ゲーム同期, ティッカー      |

REST API が“短命 & ステートレス”でスケールを担保するのに対し、WebSocket は“長寿命 & 状態保持”でリアルタイムを達成します。

---

### 5. 実装・運用時の留意点

1. **接続ライフサイクル**

   * `ping_interval 15–30 s / ping_timeout 5–10 s` で死活を速断。
   * Close フレーム(1000,1001,1008) → 相手の Close 受信 → TCP FIN の順で Graceful。
2. **バックプレッシャ**

   * `asyncio.Queue` 送信バッファに上限を設け、溢れたらドロップ or 切断。
   * 1 接続あたりのメモリと FD を監視 (`ulimit -n`)。
3. **スケールアウト**

   * Sticky session が効かない LB では **Redis Stream/NATS** でメッセージをファンアウトし、サーバをステートレス化。
   * AWS ALB の Idle Timeout を Ping 間隔の 2–3 倍に設定。
4. **セキュリティ**

   * `wss://` 強制、握手時に Cookie or JWT を検証。
   * 接続中に権限が変わる可能性がある場合は **セッション寿命を短く**して再接続で更新。
   * `permessage-deflate` は CPU DoS を誘発するため `max_window_bits=15` など制限を入れる。
5. **クライアント再接続戦略**

   * 指数バックオフ + ジッター (`1s,2s,4s…max30s`)。
   * Close Code 1006/1011 は UI に「一時的障害」と表示し自動再試行。
6. **モニタリング**

   * `websocket_open_connections`、`ping_rtt_seconds`、`send_queue_size` を Prometheus で収集。
   * 異常時に Cut-off しても “サービス全体が詰まらない” 設計を徹底。

---

### 6. まとめ

WebSocket は「**低レイテンシ × 双方向 × ブラウザ互換**」を満たす唯一の標準プロトコルであり、HTTP エコシステムの枠を超えたリアルタイム機能を提供します。その一方で **接続維持によるリソース圧迫** や **既存 HTTP セキュリティモデルの不在** がトレードオフです。
上級エンジニアが WebSocket を採用する際は、

1. **要件が本当に双方向か？**
2. **水平スケールの出口（Pub/Sub）を用意したか？**
3. **Ping/Pong, Close, 再接続, Back-pressure をコードレベルで実装したか？**

をチェックリストに据え、プロトコルの特性と運用コストを天秤に掛けましょう。この記事が設計・実装・運用の各フェーズでの指針となれば幸いです。