ストーリー
高橋アーキテクトが仕様書を渡した。
ミッション概要
| ミッション | テーマ | 難易度 |
|---|---|---|
| Mission 1 | 基本的なロードテスト | 初級 |
| Mission 2 | 段階的負荷テスト | 中級 |
| Mission 3 | マルチシナリオテスト | 中級 |
| Mission 4 | 結果分析レポート | 上級 |
Mission 1: 基本的なロードテスト(10分)
商品一覧APIに対する基本的なロードテストを書いてください。
要件
- エンドポイント:
GET /api/products - VU数: 10
- テスト時間: 30秒
- 成功基準: ステータス200、レスポンスタイム500ms以下
- 思考時間: 1秒
解答例
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
vus: 10,
duration: '30s',
thresholds: {
http_req_duration: ['p(95)<500'],
http_req_failed: ['rate<0.01'],
},
};
export default function () {
const res = http.get('http://localhost:3000/api/products');
check(res, {
'status is 200': (r) => r.status === 200,
'response time < 500ms': (r) => r.timings.duration < 500,
'body contains products': (r) => {
const body = JSON.parse(r.body);
return Array.isArray(body.products) && body.products.length > 0;
},
});
sleep(1);
}
Mission 2: 段階的負荷テスト(15分)
ストレステストのパターンで、段階的に負荷を上げていくテストを書いてください。
要件
- ステージ1: 2分で20VUまで増加
- ステージ2: 3分で20VUを維持
- ステージ3: 2分で50VUまで増加
- ステージ4: 3分で50VUを維持
- ステージ5: 2分で100VUまで増加
- ステージ6: 3分で100VUを維持
- ステージ7: 2分で0VUまで減少
- 成功基準: p95 < 300ms、エラー率 < 1%
解答例
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
stages: [
{ duration: '2m', target: 20 },
{ duration: '3m', target: 20 },
{ duration: '2m', target: 50 },
{ duration: '3m', target: 50 },
{ duration: '2m', target: 100 },
{ duration: '3m', target: 100 },
{ duration: '2m', target: 0 },
],
thresholds: {
http_req_duration: ['p(95)<300'],
http_req_failed: ['rate<0.01'],
},
};
const BASE_URL = 'http://localhost:3000';
export default function () {
// ランダムにエンドポイントを選択(加重あり)
const rand = Math.random();
if (rand < 0.5) {
// 50%: 商品一覧
const res = http.get(`${BASE_URL}/api/products`);
check(res, { 'products 200': (r) => r.status === 200 });
} else if (rand < 0.8) {
// 30%: 商品詳細
const id = Math.floor(Math.random() * 1000) + 1;
const res = http.get(`${BASE_URL}/api/products/${id}`);
check(res, { 'product detail 200': (r) => r.status === 200 });
} else {
// 20%: 検索
const queries = ['laptop', 'phone', 'tablet', 'headphones', 'camera'];
const q = queries[Math.floor(Math.random() * queries.length)];
const res = http.get(`${BASE_URL}/api/search?q=${q}`);
check(res, { 'search 200': (r) => r.status === 200 });
}
sleep(Math.random() * 3 + 1); // 1-4秒の思考時間
}
Mission 3: マルチシナリオテスト(20分)
閲覧ユーザーと購入ユーザーの2つのシナリオを並行実行するテストを書いてください。
要件
- シナリオA(閲覧): 30VU、一覧 → 詳細 → 一覧を繰り返し
- シナリオB(購入): 10VU、詳細 → カート追加 → 注文のフロー
- 各シナリオに適切な思考時間を設定
- グループ機能でシナリオを分類
解答例
import http from 'k6/http';
import { check, group, sleep } from 'k6';
const BASE_URL = 'http://localhost:3000';
export const options = {
scenarios: {
browsing: {
executor: 'constant-vus',
vus: 30,
duration: '5m',
exec: 'browsingFlow',
},
purchasing: {
executor: 'constant-vus',
vus: 10,
duration: '5m',
exec: 'purchaseFlow',
},
},
thresholds: {
http_req_duration: ['p(95)<300', 'p(99)<500'],
http_req_failed: ['rate<0.01'],
'group_duration{group:::Browse Products}': ['p(95)<400'],
'group_duration{group:::Purchase Flow}': ['p(95)<600'],
},
};
export function browsingFlow() {
group('Browse Products', function () {
// 商品一覧を閲覧
const listRes = http.get(`${BASE_URL}/api/products`);
check(listRes, {
'list 200': (r) => r.status === 200,
});
sleep(2 + Math.random() * 3); // 2-5秒閲覧
// 商品詳細を閲覧
const productId = Math.floor(Math.random() * 100) + 1;
const detailRes = http.get(`${BASE_URL}/api/products/${productId}`);
check(detailRes, {
'detail 200': (r) => r.status === 200,
});
sleep(3 + Math.random() * 5); // 3-8秒閲覧
// カテゴリ一覧
const catRes = http.get(`${BASE_URL}/api/categories`);
check(catRes, {
'categories 200': (r) => r.status === 200,
});
sleep(1);
});
}
export function purchaseFlow() {
group('Purchase Flow', function () {
const productId = Math.floor(Math.random() * 100) + 1;
// 商品詳細を閲覧
const detailRes = http.get(`${BASE_URL}/api/products/${productId}`);
check(detailRes, { 'detail 200': (r) => r.status === 200 });
sleep(3);
// カートに追加
const cartRes = http.post(
`${BASE_URL}/api/cart`,
JSON.stringify({ productId: productId, quantity: 1 }),
{ headers: { 'Content-Type': 'application/json' } }
);
check(cartRes, { 'cart 200': (r) => r.status === 200 || r.status === 201 });
sleep(2);
// カート確認
const cartViewRes = http.get(`${BASE_URL}/api/cart`);
check(cartViewRes, { 'cart view 200': (r) => r.status === 200 });
sleep(2);
// 注文確定
const orderRes = http.post(
`${BASE_URL}/api/orders`,
JSON.stringify({ paymentMethod: 'credit_card' }),
{ headers: { 'Content-Type': 'application/json' } }
);
check(orderRes, {
'order created': (r) => r.status === 200 || r.status === 201,
});
sleep(1);
});
}
Mission 4: 結果分析レポート(15分)
以下のテスト結果を分析し、レポートを作成してください。
テスト結果データ
テスト種類: ロードテスト(100VU、10分間)
http_req_duration......: avg=125ms min=8ms med=78ms max=8234ms
p(90)=210ms p(95)=345ms p(99)=1823ms
http_req_failed........: 0.45% ✗ 135 ✓ 29865
http_reqs..............: 30000 50.0/s
エンドポイント別:
GET /api/products p95=120ms errors=0.1%
GET /api/products/:id p95=180ms errors=0.2%
GET /api/search p95=890ms errors=1.2%
POST /api/cart p95=250ms errors=0.3%
POST /api/orders p95=420ms errors=0.8%
リソース使用率:
CPU: avg=65%, max=92%
Memory: avg=70%, max=85%
DB connections: avg=18/20, max=20/20(飽和)
作成するレポート項目
- 全体評価(合格/不合格)
- ボトルネックの特定
- 改善提案(優先順位付き)
解答例
=== 負荷テスト結果レポート ===
## 1. 全体評価: 不合格
SLO判定:
- p95 = 345ms → 目標300ms: 未達
- p99 = 1823ms → 目標500ms: 未達(大幅超過)
- エラー率 = 0.45% → 目標0.1%: 未達
- スループット = 50 RPS → 想定より低い
## 2. ボトルネック分析
ボトルネック1: 検索API(最優先)
- p95 = 890ms(全体を大きく押し上げている)
- エラー率 = 1.2%(他エンドポイントの6-12倍)
- 原因推定: 検索クエリの最適化不足、インデックス未設定
ボトルネック2: DBコネクションプール
- 最大20/20で飽和している
- これが注文API(p95=420ms)の遅延要因と推定
- 注文APIのエラー率0.8%もコネクション枯渇が原因の可能性
ボトルネック3: テールレイテンシ
- p99/p95 = 1823/345 = 5.3倍(危険水準)
- max=8234msは明らかに異常
- GC停止やコネクション待ちが原因の可能性
## 3. 改善提案(優先順位順)
1. [緊急] 検索APIの最適化
- 検索カラムにインデックスを追加
- 検索結果のキャッシュ導入(TTL: 5分)
- 期待効果: 検索p95を300ms以下に改善
2. [高] DBコネクションプールの拡張
- 現在max=20 → max=50に拡張
- コネクションの再利用設定を確認
- 期待効果: 注文APIのエラー率と遅延を改善
3. [中] テールレイテンシの調査
- GCログの確認(Node.jsのヒープサイズ設定確認)
- スロークエリログの確認
- 期待効果: p99を500ms以下に改善
改善後に再テストを実施し、SLO達成を確認すること。
達成度チェック
| ミッション | テーマ | 完了 |
|---|---|---|
| Mission 1 | 基本的なロードテスト | [ ] |
| Mission 2 | 段階的負荷テスト | [ ] |
| Mission 3 | マルチシナリオテスト | [ ] |
| Mission 4 | 結果分析レポート | [ ] |
チェックリスト
- k6で基本的なロードテストを書ける
- 段階的な負荷パターンを設定できる
- 複数シナリオを並行実行するテストを書ける
- テスト結果を分析してレポートを作成できる
次のステップへ
お疲れさまでした。負荷テストの実践力が身についたはずです。
次のセクションでは、Step 3の理解度チェックです。
推定所要時間: 60分