ストーリー
高橋アーキテクトがJaegerでトレースを開いた。
ボトルネックの3つのパターン
パターン1: 単一の遅いSpan
[API Gateway] ━━━━━━━━━━━━━━━━━━━━ 2500ms
├─[Auth] ━━ 20ms
├─[Order] ━━━━━━━━━━━━━━━━━━ 2300ms
│ ├─[DB] ━━ 10ms
│ └─[Payment] ━━━━━━━━━━━━━━━━ 2200ms ← ボトルネック!
│ └─[Stripe API] ━━━━━━━━━━━━━━ 2150ms
└─[Notify] ━━ 15ms
原因: 外部APIの遅延、重いDBクエリ、計算量の多い処理 対策: タイムアウト設定、キャッシュ、非同期処理への変更
パターン2: 多数のシーケンシャル呼び出し
[Order Service] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1800ms
├─[DB: get user] ━━━ 50ms
├─[DB: get order] ━━━ 50ms 順番に実行
├─[DB: get items] ━━━ 50ms されている!
├─[DB: get inventory] ━━━ 50ms
├─[DB: get pricing] ━━━ 50ms
├─[DB: get shipping] ━━━ 50ms
├─[DB: get tax] ━━━ 50ms
├─[Validate] ━━━ 50ms
├─[Calculate total] ━━━ 50ms
└─[DB: insert order] ━━━ 50ms
合計: 50ms x 10 = 500ms(直列実行)
原因: 並列実行可能な処理を直列に実行している 対策: Promise.allで並列実行
// Before: 直列実行(遅い)
const user = await getUser(userId);
const order = await getOrder(orderId);
const items = await getItems(orderId);
const inventory = await getInventory(items);
// After: 並列実行(速い)
const [user, order, items] = await Promise.all([
getUser(userId),
getOrder(orderId),
getItems(orderId),
]);
const inventory = await getInventory(items); // itemsに依存するので後
パターン3: N+1問題
[Order Service] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1200ms
├─[DB: SELECT orders] ━━ 20ms 1回のクエリ
├─[DB: SELECT items WHERE order_id=1] ━━ 15ms ┐
├─[DB: SELECT items WHERE order_id=2] ━━ 15ms │
├─[DB: SELECT items WHERE order_id=3] ━━ 15ms │ N回のクエリ
├─[DB: SELECT items WHERE order_id=4] ━━ 15ms │
│ ... (50回繰り返し) │
└─[DB: SELECT items WHERE order_id=50] ━━ 15ms ┘
合計: 20 + 15 x 50 = 770ms
原因: ループ内で個別にDBクエリを発行 対策: JOINまたはIN句でバッチ取得
// Before: N+1問題
const orders = await db.query('SELECT * FROM orders LIMIT 50');
for (const order of orders) {
order.items = await db.query('SELECT * FROM items WHERE order_id = $1', [order.id]);
}
// After: バッチ取得
const orders = await db.query('SELECT * FROM orders LIMIT 50');
const orderIds = orders.map(o => o.id);
const items = await db.query('SELECT * FROM items WHERE order_id = ANY($1)', [orderIds]);
// メモリ上でマッピング
トレースによるボトルネック分析フロー
// 体系的な分析フロー
interface BottleneckAnalysis {
step1: {
action: '遅いトレースをJaegerで検索';
query: 'Duration > 2s, Service = order-service';
};
step2: {
action: 'ウォーターフォールで最も長いSpanを特定';
check: 'Total Durationの50%以上を占めるSpanはどれか';
};
step3: {
action: 'ボトルネックのパターンを判定';
patterns: [
'単一の遅いSpan → 外部API/重いクエリ',
'多数の直列Span → 並列化可能',
'同種の繰り返しSpan → N+1問題',
];
};
step4: {
action: 'Span属性で詳細を確認';
attributes: ['db.statement', 'http.url', 'http.status_code'];
};
step5: {
action: '対策を実施し、トレースで効果を検証';
};
}
対策パターンの一覧
| ボトルネック | 対策 | 効果 |
|---|---|---|
| 外部APIの遅延 | タイムアウト + サーキットブレーカー | 障害時の影響を限定 |
| 重いDBクエリ | インデックス追加、クエリ最適化 | クエリ時間短縮 |
| 直列処理 | Promise.allで並列化 | 待ち時間の削減 |
| N+1問題 | バッチ取得(JOIN/IN句) | クエリ数の削減 |
| キャッシュミス | Redis等のキャッシュ導入 | DB負荷の軽減 |
| 不要な処理 | 非同期化(キューイング) | レスポンスタイム短縮 |
// 対策例: 非同期化
// Before: 同期的に通知を送信(レスポンスに含まれる)
async function createOrder(data: OrderInput) {
const order = await saveOrder(data);
await chargePayment(order);
await sendNotification(order); // 通知は非同期でOK
return order;
}
// After: 通知をキューに投入(レスポンスから切り離し)
async function createOrder(data: OrderInput) {
const order = await saveOrder(data);
await chargePayment(order);
await messageQueue.publish('order.created', { orderId: order.id });
return order; // 通知を待たずにレスポンス
}
まとめ
| ポイント | 内容 |
|---|---|
| 3つのパターン | 単一遅延、直列実行、N+1問題 |
| 分析フロー | 遅いトレース検索 → 長いSpan特定 → パターン判定 → 対策 |
| 対策 | 並列化、バッチ取得、キャッシュ、非同期化 |
| 検証 | 対策後にトレースで効果を測定 |
チェックリスト
- 3つのボトルネックパターンを説明できる
- トレースのウォーターフォールからボトルネックを特定できる
- N+1問題をトレースで検出し、対策を提案できる
- 直列処理の並列化を実装できる
次のステップへ
次は演習「トレーシングを導入しよう」です。これまで学んだ知識を使って、実際にトレーシングを導入してみましょう。
推定読了時間: 40分