Grafana Cloud k6でボトルネックを特定して改善してみた

Grafana Cloud k6でボトルネックを特定して改善してみた

Clock Icon2025.03.29

こんにちは!製造ビジネステクノロジー部の小林です。

前回、Grafana Cloud k6で簡単な負荷テストを行いました。今回は、実際にボトルネックを特定して改善していく過程をご紹介します。ダッシュボードでの新しい分析画面もご紹介するのでぜひ最後までご覧ください。

はじめに

この記事では「Grafana k6」と「Grafana Cloud k6」という2つのサービスが登場します。
まずは両者の違いを簡単に確認しておきましょう。

Grafana k6

  • オープンソース
    • 完全に無料で利用可能なオープンソースの負荷テストツール
  • ローカル実行
    • 主にローカル環境やCI/CDパイプラインで実行
  • 自己管理
    • インフラストラクチャやテスト結果の保存・分析は自分で管理
  • コマンド
    • k6 run script.jsのように実行

https://grafana.com/docs/k6/latest/

Grafana Cloud k6

  • SaaS
  • Grafana Labsが提供するクラウドベースの有料サービス(無料プランあり)
  • クラウド実行
    • 複数のリージョンから分散負荷テストが可能
  • マネージド
    • インフラストラクチャやテスト結果の保存・分析はGrafanaが管理
  • コマンド
    • k6 loginしてからk6 cloud script.jsのように実行

https://grafana.com/docs/k6/latest/results-output/real-time/cloud/

検証環境

$ sw_vers
ProductName:       macOS Sonoma 14.7.4
ProductVersion:    14.7.4
BuildVersion:		23H420

テスト実行環境:EC2 AmazonLinux2023

構成図

EC2 → API Gateway → Lambda → RDS Proxy → Auroraの経路でテストを行います。
archi (1)

やってみる

前回はダッシュボードから負荷テストを行いましたが、今回はEC2からCLIで実行してみます。

テストスクリプトの作成

まずは必要なテストスクリプトを作成します。今回のテストでは、API Gatewayのエンドポイントに対して段階的に負荷を増加させていきます。

  • テストの内容

    • テスト対象: 指定されたAPIエンドポイント
    • テスト方法: GETリクエストを送信し、レスポンスを検証
  • 負荷パターン

    • 段階的に負荷を増加させる
  1. 10秒間: 10人の仮想ユーザー
  2. 10秒間: 30人の仮想ユーザー
  3. 10秒間: 50人の仮想ユーザー
  4. 10秒間: 70人の仮想ユーザー
  5. 10秒間: 80人の仮想ユーザー
  6. 10秒間: 100人の仮想ユーザー

合計60秒間のテストを実施します。

ターミナルで以下のコマンドを入力し、スクリプトを作成します。

sudo vi rds-proxy-load.get.js

スクリプト

rds-proxy-load-get.js
import http from 'k6/http';
import { check, sleep } from 'k6';

// テスト対象のAPIエンドポイント
const API_ENDPOINT = https://4qhgkooonl.execute-api.ap-northeast-1.amazonaws.com/prod/rdsProxy;

export let options = {
  // 段階的な負荷
  stages: [
    { duration: '10s', target: 10 },
    { duration: '10s', target: 30 },
    { duration: '10s', target: 50 },
    { duration: '10s', target: 70 },
    { duration: '10s', target: 80 },
    { duration: '10s', target: 100 },
  ],
  // レポート設定
  summaryTrendStats: ['avg', 'min', 'med', 'max', 'p(90)', 'p(95)', 'p(99)'],
};

export default function() {
  // GETリクエスト実行
  const response = http.get(`${API_ENDPOINT}`);

  // 成功/失敗の記録
  const success = check(response, {
    'status is 200': (r) => r.status === 200,
    'response has data': (r) => r.json().length > 0,
  });

  sleep(0.5);
}

スクリプトが作成できたら、GrafanaのMy Accountにログインし、「Learn → CLI instructions」を選択します。
スクリーンショット 2025-03-28 21.24.07

「Login to your account」のコマンドをコピーします。
スクリーンショット 2025-03-29 16.31.48

コピペした内容をターミナルで実行します。

k6 cloud login --token <トークン>

# 期待値
Logged in successfully, token saved in /home/ssm-user/.config/k6/config.json

テストの実行

ログインが完了したら、以下のコマンドでテストを実施します。

k6 cloud rds-proxy-load-get.js

実行すると以下のような出力が表示されます。
スクリーンショット 2025-03-28 22.05.07

テストが完了しました。
スクリーンショット 2025-03-28 22.08.27

テスト結果の分析

Grafana Cloudのコンソールを確認すると、作成したテストスクリプトの名前で新しいプロジェクトが作成されています。棒グラフの部分を選択しましょう。
スクリーンショット 2025-03-28 22.11.52

グラフが表示されました。前回の記事ではこの画面で分析を行いました。
今回は違う画面で分析してみます。ハンバーガーメニューから「Explore」を選択します。
スクリーンショット 2025-03-29 18.28.07

この画面では、メトリクスのカスタマイズや追加・削除が自由に行えます。
スクリーンショット 2025-03-29 17.22.52

メトリクスの線グラフと、時間ごとの仮想ユーザー数などの複数メトリクスを表形式で確認できるため、より詳細な分析が可能です。
スクリーンショット 2025-03-29 18.29.59

ボトルネックの特定

グラフを分析すると、初動のhttp_req_durationが2.56秒と非常に高い値を示しており、その後は安定していることがわかります。最も遅い時間(初動値のスパイク部分)は18:22:03の2.56秒です。
スクリーンショット 2025-03-29 18.35.51

CloudWatchのログを確認すると、Lambdaがコールドスタートしており、これが初動時間の遅延の主な原因であることがわかります。
コールドスタートについては以下の記事に詳細が記載してあります。
https://dev.classmethod.jp/articles/devio-osaka-2024-lambda-coldstart/

実行時間の内訳
REPORTセクションから以下の内容が分かります。

  • 総実行時間: 1582.71 ms(約1.58秒)
  • 課金対象時間: 1583 ms
  • メモリ設定: 128 MB
  • 実際のメモリ使用量: 90 MB(設定の約70%)
  • 初期化時間: 368.66 ms

k6での時間は2.56秒となっていますがこれは測定範囲の違いによるものです。
k6はクライアントから見たエンドツーエンドの応答時間を測定しています。

例えばk6の2.56秒には以下の要素が含まれています。

  • ネットワークレイテンシ(クライアントからAWSまで)
  • API GatewayやALBなどのルーティング時間
  • Lambda関数の初期化時間(コールドスタートの場合)
  • Lambda関数の実行時間(CloudWatchに記録される1582.71ms)
  • レスポンスがクライアントに戻るまでのネットワーク時間

Lambdaのメトリクスを確認すると、以下のことがわかります。
スクリーンショット 2025-03-29 18.50.30
Durationメトリクスを見ると最大実行時間は1,583msですが、平均実行時間は約19.5msと非常に短いです。
最小実行時間が3.23msと短いことからも、ウォームスタート時の処理は効率的であることがわかります。

また、Lambda関数の同時実行制限には達しておらず、スロットル(実行制限)も発生していないため、
これは処理能力の制限ではなく、コールドスタートや他の要因が遅延の原因であることを示唆しています。

パフォーマンス改善

コールドスタート対策として、Provisioned Concurrency(プロビジョニングされた同時実行)設定を行います

プロビジョニングされた同時実行?

事前に指定した数のLambda実行環境を「温めておく」設定です。通常、Lambdaは使われていないと環境が停止しますが、この機能を使うと常に準備された状態の環境を維持できます。
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/provisioned-concurrency.html

コンソールから設定する

AWS Lambdaコンソールからプロビジョニングされた同時実行を設定します。
スクリーンショット 2025-03-29 19.18.15

修飾子のタイプ

  • エイリアス
    • Lambda関数の特定バージョンを指す名前付きポインタ(今回は「prod」)
  • バージョン
    • Lambda関数コードとその設定の特定の時点でのスナップショット

今回は「エイリアス」を選択し、プロビジョニングされた同時実行数を「100」に設定します。
スクリーンショット 2025-03-29 19.20.16

AWS CDKで設定するとこんな感じです

// Lambda関数のエイリアス
    this.rdsProxyGetAlias = new lambdaBase.Alias(this, 'RdsProxyGetAlias', {
      aliasName: 'prod',
      version: this.rdsProxyGet.currentVersion,
      provisionedConcurrentExecutions: 100 // プロビジョニングされた同時実行設定 
    });

改善結果の検証

再度テストを実施した結果は以下のとおりです。
初動のレスポンス時間が572msとなり、改善前と比較して約1.96秒の短縮に成功しました!
スクリーンショット 2025-03-29 20.36.23

Lambdaのログを確認すると、INIT_STARTがなくコールドスタートが発生していないことが確認できます。
スクリーンショット 2025-03-29 20.50.12

まとめ

今回はGrafana Cloud k6を使って負荷テストを実施し、Lambda関数のコールドスタートによるパフォーマンスボトルネックを特定・改善する方法をご紹介しました。

負荷テストの結果を視覚化し分析するGrafana Cloud k6の「Explore」機能は、時系列データとメトリクスを同時に確認できるので、ボトルネックの原因究明に大きく役立ちました。

Grafana Cloud k6にはまだ多くの機能があり、今後も様々な活用方法をご紹介していく予定です。
この記事がボトルネック解決の参考になれば幸いです。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.