SRE兼バックエンドエンジニアの高良です。 今回は業務で実施した負荷テストのポイントについて書いていきます。
実施の経緯
サービスで利用しているRailsの大きめな設定変更に伴い、負荷テストを実施した上でリリース可否を判断する、という経緯でした。
自分はこれまで負荷テストは実施したことがなく、社内でも特に決まったやり方が無かったため、今回を機に今後社内で汎用的に実施できるよう、フローを整備することにしました。
選んだツール
いくつか負荷テストツールを比較した結果、grafana k6を利用することにしました。
主な選定理由については、基本的な機能が備わっている事に加え、社内で使い回す運用が容易なところです。
インストールはbrew経由で可能であり、テスト内容はjsのシンプルなファイルベースなので、リポジトリに置いておけばいつでも誰でも実行可能なのが嬉しいポイントです。
参考までに、シンプルな負荷テストのコードがこちらになります。
import http from 'k6/http'; import { check, sleep } from 'k6'; export const options = { vus: 10, // 常に10人の仮想ユーザー duration: '1m', // 1分間テストを継続 }; export default function () { const res = http.get('https://example.com'); check(res, { 'ステータスコードが200である': (r) => r.status === 200, }); sleep(1); // 各ユーザーは1秒に1回リクエストを送るイメージ }
テスト実施前の準備
各種計測指標の決定
k6で負荷を与えた結果、どれぐらいの値なら問題なしと判定するか、予めサーバー側とクライアント側で許容する指標を決めておきました。
クライアント側(k6)の指標
- エラーレスポンスが返らないこと
- レスポンスタイム(http_req_duration)のp95が大幅に悪化しないこと
サーバー側(Rails)の指標
- CPUとメモリのリソース消費量が、現在の稼働している本番サーバの空き容量内に収まること
こちらは計測のため、vmstatをベースとした、1秒ごとにCPUとメモリの利用状況を表示してくれる↓のようなコマンドを作成しました。
while true; do # タイムスタンプを取得 (例: 2023-10-27 15:30:05) timestamp=$(date '+%Y-%m-%d %H:%M:%S') # 実質空きメモリを直接取得(MB単位) real_free=$(free -m | awk '/buffers\/cache/ {print $4}') # CPU情報取得 read r us sy id wa <<< $(vmstat 1 2 | tail -1 | awk '{print $1,$13,$14,$15,$16}') # 表示 (タイムスタンプを追加) printf "%s | RunQueue=%s | CPU(us/sy/id/wa)=%s/%s/%s/%s%% | RealFreeMem=%sMB\n" \ "$timestamp" "$r" "$us" "$sy" "$id" "$wa" "$real_free" sleep 1 done
# 出力例 2025-05-09 17:04:16 | RunQueue=0 | CPU(us/sy/id/wa)=0/0/100/0% | RealFreeMem=1326MB 2025-05-09 17:04:18 | RunQueue=0 | CPU(us/sy/id/wa)=2/0/96/2% | RealFreeMem=1326MB 2025-05-09 17:04:20 | RunQueue=0 | CPU(us/sy/id/wa)=1/0/99/0% | RealFreeMem=1325MB
計測に関してはもっと便利なツールなどがあったかもしれませんが、各種メトリクスでどの値をどう見るべきか(CPUのuser/systemの違いや、バッファやキャッシュ分を加味した空きメモリ量など)を把握しておきたかったので、ChatGPTに壁打ちしてもらいながらあえて簡単なシェルを自作しました。
各種キャッシュの解除
対象環境ではVarnishというキャッシュサーバが最前段に配置されていました。(こちらもかなり古くなってきたのでFastlyに移行中です
Varnishのキャッシュでヒット扱いになってしまうと、肝心のRailsサーバにはリクエストが到達せず負荷を与えられないため、予め回避策が必要でした。
そこで今回は、特定ヘッダを含めたリクエストのみ、キャッシュの有無を判定せず全てpassするような変更を加えました。
下記がVCL(varnishの設定ファイル)のサンプルです。
sub vcl_recv { # UAが "Bypass-Varnish" の場合はキャッシュをバイパス if (req.http.User-Agent && req.http.User-Agent ~ "Bypass-Varnish") { return (pass); }
varnishを変更した上で、k6側でも↓のような該当ヘッダを追加するコードを加えました。
k6、設定がシンプルに書けるのでラクですね...
export default function () { const res = http.get(URL, { headers: { Authorization: AUTH_HEADER, 'User-Agent': 'Bypass-Varnish', }, });
負荷テスト実施後の対応
変更適用前と適用後の環境にそれぞれ実施し、どのくらい差が出るかを検証しました。
結果、無事サーバーの空きリソース内に収まることが分かったため、今回の負荷テストは完了となりました。
まとめ
現在弊社では、バス比較なびのフロントエンドNext.js化プロジェクトに加え、複数の案件が動いている状態です。
インフラチームでも、各種サービスのECS化などの大規模な刷新を継続的に行っているため、今後もこういった負荷テストは必要になるかと思います。
今回整備した負荷テストのフローを更に磨きつつ、リリース時にトラブルが発生しないような基盤の構築に今後も努めていきたいです。