API負荷テストの「深淵」を覗く:スループット、レイテンシ、エラー率の真実
ネットワークエンジニアとして数々の死線を越えてきた諸君、APIの「性能」をどう定義しているだろうか?
「レスポンスが速い」「たくさん捌ける」といった曖昧な感覚で語るエンジニアは、障害が起きたときに必ず右往左往する。負荷テストとは、単にシステムをいじめて限界値を見つける儀式ではない。「どのコンポーネントが、どのような条件下で、なぜ音を上げるのか」というボトルネックの挙動を、データとして刻み込む作業のことだ。
今日は、REST APIの設計思想を理解した諸君に向けて、k6やJMeterを用いた負荷試験で真に見るべき指標と、その裏にある通信のリアルを紐解いていく。
—
1. 測定すべき「3つの聖なる指標」
負荷試験の結果レポートを眺める際、諸君がまず目を向けるべきは以下の3点だ。
スループット(Throughput / RPS)
単位時間あたりに処理されたリクエスト数。REST APIでは Requests Per Second (RPS) で表すのが一般的だ。重要なのは「RPSが伸びなくなった地点」ではなく、「RPSは維持できているが、レイテンシが跳ね上がった地点」を見極めることだ。
レイテンシ(Latency / Response Time)
単なる平均値(Average)に騙されてはいけない。負荷テストでは必ず「パーセンタイル値(p95, p99)」を見ること。平均値では隠れてしまう「稀に発生する激重リクエスト」の存在こそが、ユーザー体験を殺し、キューを溢れさせる真犯人だ。
エラー率(Error Rate)
単に 5xx が出たか否かではない。429 Too Many Requests が発生する直前の挙動、あるいはコネクションタイムアウトによる 503 Service Unavailable の発生タイミング。これらは、インフラの限界を告げるシグナルだ。
—
2. 実践:k6で「限界の予兆」を掴む
k6はGo言語ベースで書かれており、非常に軽量かつ強力だ。まずは、ごく一般的なエンドポイントに対する負荷スクリプトを見てほしい。
import http from 'k6/http';
import { check, sleep } from 'k6';
// 負荷試験の設定
export const options = {
stages: [
{ duration: '30s', target: 20 }, // 30秒かけて20ユーザーまで増やす
{ duration: '1m', target: 20 }, // 1分間維持(定常状態の観察)
{ duration: '10s', target: 0 }, // クールダウン
],
};
export default function () {
const url = 'https://api.example.com/v1/resources';
const params = {
headers: { 'Content-Type': 'application/json' },
};
const res = http.get(url, params);
// ステータスコードの検証(ここがエラー率の監視対象)
check(res, {
'status is 200': (r) => r.status === 200,
});
sleep(1); // リアルなユーザーの待機時間をシミュレート
}
このスクリプトを走らせた後、注目すべきは http_req_duration という指標だ。これが p95 でどれくらい増大したかを記録し続ける。
—
3. ボトルネック特定のための「現場の分析手法」
テスト中にレイテンシが急増したら、すぐに以下の「ネットワークの四天王」を疑え。
1. アプリケーション層のロック: DBのデッドロックや、処理スレッドの枯渇。
2. コネクションプール: TCP の最大接続数を超過し、SYN パケットの再送(Retransmission)が発生していないか?
3. DNS解決: 負荷がかかるとリゾルバが詰まるケースがある。curl -w を使って、名前解決の時間を計測せよ。
4. I/O待ち: ログの書き出しや、外部API呼び出しのタイムアウト設定。
トラブルシューティングのためのCLI定石
「なぜ遅い?」と思ったら、まずは curl での個別確認からだ。
# -w オプションで各フェーズの時間をミリ秒で計測する
curl -o /dev/null -s -w \
"DNS lookup: %{time_namelookup}s\n\
Connect: %{time_connect}s\n\
TTFB: %{time_starttransfer}s\n\
Total: %{time_total}s\n" \
"https://api.example.com/v1/resources"
このコマンドで Connect が異常に長いならネットワークかTCPのスタック、TTFB(Time To First Byte)が長いならアプリケーション側のビジネスロジックに問題がある可能性が高い。
—
4. 最後に:エンジニアとしての心構え
負荷テストの目的は「数字を出すこと」ではない。「システムがどのように壊れるか」という故障のパターンを事前に学習することだ。
もし諸君が設計したAPIが、RPSの増加とともにメモリを食いつぶすのであれば、それはデータ構造に無駄がある。もしコネクションが溜まるのであれば、それは Keep-Alive の設定を見直すべき合図だ。
RFC 7231 (HTTP/1.1 Semantics) を読み返し、一つひとつのHTTPヘッダーがどのような挙動を意図しているかを再確認してほしい。プロトコルの深淵を知る者だけが、負荷テストという名の「戦場」を冷静にコントロールできるのだ。
さあ、次は君の環境で k6 を回し、そのログから何が見えるか。答えは必ず、パケットの往復の中にある。
コメント