前回の続きになります。
はじめに
前回の記事では、GitHub Actions を使った CI/CD パイプラインを構築し、Next.js Todo アプリを ECS Fargate に自動デプロイできるようになりました。
今回は、デプロイしたアプリケーションが実際にどの程度の負荷に耐えられるかを、k6 を使った負荷テストで定量的に検証していきます。
負荷テストの全体像
| フェーズ | 処理内容 | 検証ポイント |
|---|---|---|
| 認証準備 | CSRF トークン取得 | NextAuth.js のセキュリティ機能 |
| ログイン | 認証情報送信・セッション確立 | 認証処理の性能とセッション管理 |
| API 呼び出し | 保護されたリソースへのアクセス | 認証済みユーザーの API 性能 |
| 評価 | レスポンス時間・エラー率測定 | システム全体のパフォーマンス |
使用技術とツール
- 負荷テストツール: k6
- テスト対象: NextAuth.js 認証 + 保護された API
- 測定項目: レスポンス時間、エラー率、スループット
- インフラ: AWS ECS Fargate + ALB + RDS
なぜ k6 を選んだのか
k6 の特徴
- JavaScript ベース: 開発者にとって親しみやすい言語
- 軽量: 高いパフォーマンスで大量の仮想ユーザーを生成可能
- 豊富な機能: HTTP/WebSocket/gRPC など多様なプロトコルに対応
- CI/CD 統合: GitHub Actions などとの連携が容易
認証付き SPA の負荷テストの課題
一般的な Web アプリケーションと異なり、NextAuth.js を使用した SPA では以下の課題があります:
- CSRF トークンの取得: セキュリティのため各リクエストで CSRF トークンが必要
- セッション Cookie の管理: 認証後のセッション状態を適切に維持
- 認証フローの複雑さ: 複数ステップの認証処理を正確に模倣
実装手順
1. テストユーザーの準備
負荷テスト用のテストユーザーを事前にデータベースに作成しておきます。
今回は以下のユーザーを作成しました。
-
メールアドレス:test@example.com -
パスワード:testtest
2. k6 負荷テストスクリプトの作成
load-test.jsを作成:
import http from "k6/http";
import { check, sleep } from "k6";
// ↓↓↓ この行を、ALBのDNS名に書き換えてください ↓↓↓
const ALB_URL = "http://<xxx....>.ap-northeast-1.elb.amazonaws.com";
const TEST_USER_EMAIL = "test@example.com";
const TEST_USER_PASSWORD = "testtest";
export const options = {
stages: [
{ duration: "30s", target: 5 },
{ duration: "1m", target: 5 },
{ duration: "10s", target: 0 },
],
thresholds: {
http_req_failed: ["rate<0.01"],
http_req_duration: ["p(95)<5000"],
},
};
export default function () {
// CSRFトークンを取得
let res = http.get(`${ALB_URL}/api/auth/csrf`);
const csrfToken = res.json("csrfToken");
check(res, {
"csrf token fetched": (r) => r.status === 200 && csrfToken != null,
});
sleep(1);
// フォームデータとして送信
const formData = {
email: TEST_USER_EMAIL,
password: TEST_USER_PASSWORD,
csrfToken: csrfToken,
callbackUrl: `${ALB_URL}/`,
json: "true",
};
// ログインリクエスト
res = http.post(`${ALB_URL}/api/auth/callback/credentials`, formData, {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
});
// レスポンスチェック
check(res, {
"login successful": (r) => r.status === 200,
"session cookie received": (r) =>
Object.keys(r.cookies).length > 0 &&
(r.cookies["next-auth.session-token"] != null ||
r.cookies["__Secure-next-auth.session-token"] != null),
});
sleep(1);
// ログインが必要なページへのアクセス
const protectedRes = http.get(`${ALB_URL}/api/todos`, {
cookies: res.cookies,
});
check(protectedRes, {
"Protected resource accessible": (r) => r.status === 200,
});
sleep(1);
}
sleep(1)は、実際に人間が操作するときの挙動を再現するために入れました。
これがなければ、すぐに次のチェックに進んでしまうからです。
3. 負荷テストスクリプトの詳細解説
3.1 CSRF トークンの取得
let res = http.get(`${ALB_URL}/api/auth/csrf`);
const csrfToken = res.json("csrfToken");
NextAuth.js はセキュリティのため、各認証リクエストで CSRF トークンの確認を行います。まず/api/auth/csrfエンドポイントから CSRF トークンを取得します。
3.2 認証情報の送信
const formData = {
email: TEST_USER_EMAIL,
password: TEST_USER_PASSWORD,
csrfToken: csrfToken,
callbackUrl: `${ALB_URL}/`,
json: "true",
};
NextAuth.js の Credentials Provider は、フォームデータとして認証情報を受け取ります。CSRF トークンも含めて送信することで、実際のブラウザでの認証フローを模倣しています。
3.3 セッション Cookie の確認
check(res, {
"session cookie received": (r) =>
Object.keys(r.cookies).length > 0 &&
(r.cookies["next-auth.session-token"] != null ||
r.cookies["__Secure-next-auth.session-token"] != null),
});
認証が成功すると、NextAuth.js はセッション Cookie を発行します。HTTPS 環境では__Secure-プレフィックス付きの Cookie が使用されるため、両方をチェックしています。
4. パフォーマンス閾値の設定
export const options = {
stages: [
{ duration: "30s", target: 5 },
{ duration: "1m", target: 5 },
{ duration: "10s", target: 0 },
],
thresholds: {
http_req_failed: ["rate<0.01"], // エラー率1%未満
http_req_duration: ["p(95)<5000"], // 95%のリクエストが5秒未満
},
};
ステージ設定の考え方
- ランプアップ(30 秒): 徐々に負荷を上げることで、システムへの急激な負荷を避ける
- 定常状態(1 分): 一定の負荷を維持し、システムの安定性を確認
- ランプダウン(10 秒): 負荷を下げ、システムの回復を確認
閾値設定の根拠
- エラー率 1%未満: 高い可用性を要求(99%以上の成功率)
- 95 パーセンタイル 5 秒未満: ユーザー体験を重視した現実的な目標値
負荷テストの実行と結果分析
1. 負荷テストの実行
# k6のインストール(macOS)
brew install k6
# 負荷テストの実行
k6 run load-test.js
2. 実行結果の例
execution: local
script: load-test.js
output: -
scenarios: (100.00%) 1 scenario, 5 max VUs, 2m10s max duration (incl. graceful stop):
* default: Up to 5 looping VUs for 1m40s over 3 stages (gracefulRampDown: 30s, gracefulStop: 30s)
█ THRESHOLDS
http_req_duration
✓ 'p(95)<5000' p(95)=396.2ms
http_req_failed
✓ 'rate<0.01' rate=0.00%
█ TOTAL RESULTS
checks_total.......................: 484 4.73745/s
checks_succeeded...................: 100.00% 484 out of 484
checks_failed......................: 0.00% 0 out of 484
✓ csrf token fetched
✓ login successful
✓ session cookie received
✓ Protected resource accessible
HTTP
http_req_duration.......................................................: avg=137.11ms min=17.94ms med=36.93ms max=938.9ms p(90)=324.73ms p(95)=396.2ms
{ expected_response:true }............................................: avg=137.11ms min=17.94ms med=36.93ms max=938.9ms p(90)=324.73ms p(95)=396.2ms
http_req_failed.........................................................: 0.00% 0 out of 363
http_reqs...............................................................: 363 3.553087/s
EXECUTION
iteration_duration......................................................: avg=3.41s min=3.28s med=3.36s max=4.2s p(90)=3.59s p(95)=3.68s
iterations..............................................................: 121 1.184362/s
vus.....................................................................: 1 min=1 max=5
vus_max.................................................................: 5 min=5 max=5
NETWORK
data_received...........................................................: 218 kB 2.1 kB/s
data_sent...............................................................: 201 kB 2.0 kB/s
running (1m42.2s), 0/5 VUs, 121 complete and 0 interrupted iterations
default ✓ [======================================] 0/5 VUs 1m40s
3. 結果の読み方
| メトリクス | 値 | 意味 |
|---|---|---|
checks |
100.00% | 全てのチェックが成功 |
http_req_duration |
p(95)=396.2ms | 95%のリクエストが 396.2ms 以内 |
http_req_failed |
0.00% | エラー率 0%(目標 1%未満を達成) |
http_reqs |
3.553087/s | 秒間 3.55 リクエストの処理 |
iterations |
1.184362/s | 秒間 1.18 回のシナリオ実行 |
負荷テスト結果のグラフ可視化
コマンドラインでの数値だけでは分かりにくいパフォーマンスの変化を、InfluxDB と Grafana を使ってグラフで可視化してみましょう。
1. InfluxDB と Grafana のセットアップ
Docker で簡単に環境を構築できます:
# InfluxDB (v2.x ではなく v1.8 を使用)
docker run -d --name influx -p 8086:8086 influxdb:1.8
# Grafana
docker run -d --name grafana -p 3000:3000 grafana/grafana
なぜ InfluxDB v1.8 を使用するのか
k6 の InfluxDB 出力機能は、現在 InfluxDB v1.x のプロトコルに最適化されています。v2.x では設定が複雑になるため、v1.8 を使用します。
2. k6 でデータを InfluxDB に送信
負荷テスト実行時に、結果を InfluxDB に送信します:
k6 run --out influxdb=http://localhost:8086/k6 load-test.js
このコマンドにより、k6 の実行結果がリアルタイムで InfluxDB に保存されます。
3. Grafana の設定
3.1 Grafana にアクセス
http://localhost:3000 にブラウザでアクセスします。
- ユーザー名: admin
- パスワード: admin
初回ログイン時に新しいパスワードの設定を求められます。
3.2 データソースの追加
- 左メニューから「Connections」→「Data sources」を選択
- 「Add data source」をクリック
- 「InfluxDB」を選択
- 以下の設定を入力:
-
URL:
http://host.docker.internal:8086 -
Database:
k6 - User: 空白
- Password: 空白
-
URL:
3.3 ダッシュボードのインポート
- 左メニューから「Dashboards」を選択
- 「New」→「Import」をクリック
- 「Grafana.com dashboard ID」に「2587」を入力
- 「Load」をクリック
- データソースで先ほど設定した InfluxDB を選択
- 「Import」をクリック
4. グラフで確認できるメトリクス
上記の手順でグラフ化を試してみましたが、正直なところ詳細な分析まではできませんでした。調べながら環境構築を進め、上記のようにグラフが表示されるところまでは確認できました。
グラフからは以下のような情報が読み取れることが分かります:
- レスポンス時間の推移: 時間経過とともにパフォーマンスがどう変化するか
- スループット: 秒あたりのリクエスト処理数の推移
- エラー率: 失敗したリクエストの割合
- チェック結果: 認証フローの各ステップの成功率
コマンドラインでの数値だけでは分かりにくいパフォーマンスの変化が、グラフで視覚的に確認できるのは大きなメリットです。
5. グラフ化でつまづいたこと
今回は環境構築とグラフ表示までしかできませんでしたが、以下のような問題に遭遇しました:
- 設定の複雑さ: InfluxDB と Grafana の連携設定が思ったよりも細かい
- ダッシュボードのカスタマイズ: デフォルトのダッシュボードでは物足りない部分がある
- データの理解: グラフに表示されるメトリクスの意味を正しく理解するのに時間がかかる
それでも、コマンドラインでの数値だけでは分からなかったパフォーマンスの変化を視覚的に確認できたのは大きな成果でした。
まとめと次回予告
今回構築した負荷テストの特徴
- 実践的なシナリオ: NextAuth.js 認証フローを含む実際のユーザー行動を模倣
- 定量的な評価: レスポンス時間とエラー率による客観的な性能測定
- リアルタイム可視化: InfluxDB + Grafana によるグラフでのパフォーマンス監視
- 継続的な品質保証: 閾値設定による自動的な品質チェック
実用的な学び
- k6 による認証付き Web アプリケーションの負荷テスト手法
- NextAuth.js の認証フローを模倣するテクニック
- Docker での監視環境構築の基礎
- 負荷テスト結果のグラフ化による視覚的な確認方法
技術的な成果
- 品質保証の自動化: 手動テストでは困難な大量ユーザーでの動作確認
- ボトルネックの特定: グラフでの視覚的な性能限界の把握
- 運用準備: 本番環境での予期しない負荷に対する準備
- データドリブンな改善: 定量的なデータに基づいたシステム最適化
次回予告
次回は、CloudWatch を使った運用監視で、アプリケーションとインフラの状態を可視化し、異常発生時に迅速に対応できる体制を構築します。
次回で扱う内容(予定)
- CloudWatch Logs によるアプリケーションログの集中管理
- CloudWatch Metrics とダッシュボードによる主要メトリクスの可視化
- CloudWatch Alarms による異常検知と通知設定
- ECS Auto Scaling との連携による自動スケーリング
- 運用効率を向上させる監視ベストプラクティス
ここまでご覧いただきありがとうございました。

