ストーリー
田
田中VPoE
デプロイ戦略がわかったところで、次はサービングインフラの設計だ。LLMアプリケーションを安定して提供するためのインフラ構成を考えよう
あなた
今はECS上でアプリケーションを動かしていますが、LLM APIへのプロキシ部分が不安定で、タイムアウトが頻発しています
あ
田
田中VPoE
LLMのAPIコールは従来のREST APIとは特性が違う。レスポンス時間が数秒〜数十秒と長く、ストリーミングレスポンスもある。それに合わせたインフラ設計が必要だ
サービングインフラの特性
LLM APIコールの特性
| 特性 | 従来のREST API | LLM APIコール |
|---|
| レスポンス時間 | 数十ms〜数百ms | 数秒〜数十秒 |
| レスポンスサイズ | 予測可能 | トークン数により大きく変動 |
| 接続方式 | リクエスト/レスポンス | ストリーミング(SSE)が一般的 |
| タイムアウト | 5〜30秒 | 30秒〜120秒 |
| リトライ | 短時間で可能 | コスト増加のため慎重に |
| エラーパターン | HTTPステータスコード | レート制限、コンテンツフィルタ、トークン上限超過 |
インフラ構成パターン
基本構成
本番サービングインフラ:
┌────────────────────────────────────────────────────┐
│ VPC │
│ │
│ ┌──────────┐ ┌──────────────┐ ┌──────────┐ │
│ │ ALB │ → │ ECS Service │ → │ Redis │ │
│ │ │ │ (LiteLLM │ │ (Cache) │ │
│ │ SSL終端 │ │ Proxy) │ │ │ │
│ │ ヘルスチェック│ │ │ └──────────┘ │
│ └──────────┘ │ Min: 2 │ │
│ │ Max: 10 │ ┌──────────┐ │
│ └──────┬───────┘ │ RDS │ │
│ │ │ (Langfuse │ │
│ │ │ メタデータ) │ │
│ │ └──────────┘ │
└─────────────────────────┼──────────────────────────┘
│
┌─────────┼─────────┐
▼ ▼ ▼
OpenAI API Anthropic Self-hosted
API Model
コンポーネント別設計
| コンポーネント | 技術選択 | 設計ポイント |
|---|
| ロードバランサ | ALB | WebSocket/SSE対応、長時間接続のタイムアウト設定 |
| Gateway | ECS Fargate + LiteLLM | ステートレス設計、水平スケーリング |
| キャッシュ | ElastiCache (Redis) | セマンティックキャッシュ、TTL管理 |
| メタデータDB | RDS PostgreSQL | ログ・設定の永続化 |
| シークレット管理 | AWS Secrets Manager | APIキーのローテーション |
接続管理
タイムアウト設計
LLM APIの長い応答時間に対応するため、各層のタイムアウトを適切に設定します。
タイムアウトチェーン:
クライアント (120s) → ALB (90s) → Gateway (60s) → LLM API (30s)
各層のタイムアウト:
┌──────────┐ 120s ┌──────┐ 90s ┌─────────┐ 60s ┌─────────┐
│ Client │ ────→ │ ALB │ ───→ │ Gateway │ ───→ │ LLM API │
└──────────┘ └──────┘ └─────────┘ └─────────┘
ポイント:
- 外側ほど長いタイムアウトを設定(内側の処理完了+バッファ)
- LLM APIの30sは1リクエストあたりの制限
- ストリーミング時はfirst byte timeout と total timeoutを分離
| 層 | タイムアウト | 説明 |
|---|
| クライアント | 120秒 | ユーザーの最大待機時間 |
| ALB | 90秒 | アイドルタイムアウト |
| Gateway | 60秒 | バックエンドへの接続タイムアウト |
| LLM API | 30秒 | 単一リクエストのタイムアウト |
| ストリーミング first byte | 10秒 | 最初のトークンが届くまでの制限 |
コネクションプーリング
# コネクションプール設計例
import httpx
# LLM APIへの接続プール
llm_client = httpx.AsyncClient(
limits=httpx.Limits(
max_connections=100, # 最大同時接続数
max_keepalive_connections=20, # キープアライブ接続数
keepalive_expiry=30, # キープアライブ有効期間(秒)
),
timeout=httpx.Timeout(
connect=5.0, # 接続タイムアウト
read=60.0, # 読み取りタイムアウト
write=10.0, # 書き込みタイムアウト
pool=10.0, # プール取得タイムアウト
),
)
ストリーミング対応
SSE(Server-Sent Events)の設計
| 項目 | 設計ポイント |
|---|
| ALB設定 | HTTP/2対応を有効化、アイドルタイムアウトを延長 |
| バッファリング | プロキシバッファリングを無効化(X-Accel-Buffering: no) |
| 再接続 | クライアント側でSSE再接続ロジックを実装 |
| 部分応答保存 | ストリーミング途中の断絶に備え、受信済みトークンを保存 |
ストリーミングの流れ:
Client ←── SSE ──── Gateway ←── SSE ──── LLM API
│ │
│ Token 1 │ Token 1
│ Token 2 │ Token 2
│ Token 3 │ Token 3
│ ... │ ...
│ [DONE] │ [DONE]
│ │
│ ├── ログ記録(完全なレスポンスを構築)
│ └── メトリクス記録(TTFT, トークン数, レイテンシ)
ヘルスチェックとレディネス
ヘルスチェック設計
| チェック種別 | 確認内容 | 間隔 |
|---|
| Liveness | Gatewayプロセスが生存しているか | 10秒 |
| Readiness | LLM APIへの接続が可能か | 30秒 |
| Deep Health | Redis接続、DB接続、プロバイダ到達性 | 60秒 |
ヘルスチェック階層:
/health/live → プロセス生存確認(軽量)
/health/ready → サービス準備完了確認
/health/deep → 全依存関係の確認(重量)
├── Redis接続
├── DB接続
├── OpenAI API疎通
└── Anthropic API疎通
障害分離設計
バルクヘッドパターン
サービスごとにリソースを分離し、1つのサービスの障害が他に波及しないようにします。
バルクヘッドパターン:
┌─────────────────────────────────────┐
│ Gateway │
│ │
│ ┌───────────┐ ┌───────────┐ │
│ │ CS-AI │ │ FAQ Bot │ │
│ │ Pool │ │ Pool │ │
│ │ (50 conn) │ │ (30 conn) │ │
│ └───────────┘ └───────────┘ │
│ ┌───────────┐ │
│ │ CodeReview│ 各プールが独立 │
│ │ Pool │ → 1サービスの過負荷が│
│ │ (20 conn) │ 他に波及しない │
│ └───────────┘ │
└─────────────────────────────────────┘
まとめ
| ポイント | 内容 |
|---|
| LLM API特性 | レスポンス時間が長く、ストリーミングが一般的。タイムアウトと接続管理が重要 |
| タイムアウト | 外側ほど長いタイムアウトチェーンを設計し、各層で適切な制限を設ける |
| ストリーミング | ALBのHTTP/2対応、バッファリング無効化、部分応答保存が必要 |
| 障害分離 | バルクヘッドパターンでサービス間の影響を遮断する |
チェックリスト
推定読了時間: 30分