TT Lab
はじめる
学ぶ 学習パス コース

負荷テスト

200 が返ったことは仕事をした証拠ではない

TT Labで続きを見る

一言でいうと

負荷テストの数字は、何を測ったのかをテスト自身が検証するときにだけ、意味があります。ステータスコードだけを見るテストは、エラーページを1秒あたり3千件も出力しながら、「合格」と言います。

なぜ必要なのか

新しい決済APIの負荷テストの結果が、会議室に上がりました。1秒あたり3,012件、p95は8ミリ秒、エラー率0%。誰も異議を唱えず、その週にデプロイが出ていきました。そしてデプロイの30分後に、決済が止まりました。

原因は、テストの側にありました。負荷ジェネレーターが送ったリクエストには認証ヘッダーがなく、ゲートウェイは、認証のないリクエストに対して、200とともに{"status":"error","message":"upstream unavailable"}を返していました。古いフロントエンドが4xxを扱えないので、そうしてあったのです。負荷ジェネレーターはステータスコードだけを見ていたので、30万件すべてを成功として数えました。

数字が良かったのも、同じ理由です。エラー経路は、データベースもキューも通らないので、早く終わります。そのため、エラーが混ざるほど、平均とパーセンタイルが良くなります。テストが壊れたというサインが、「遅くなった」ではなく「速くなった」として来るのです。これが、負荷テストで最もよく踏む地雷です。

どう動くのか

負荷ジェネレーターが知っているのは、3つだけです。リクエストをいくつ送ったか、レスポンスのステータスコードが何だったか、レスポンスが来るまでにどれだけかかったか。heyのサマリーが見せるStatus code distributionは、まさにそれだけです。本文が何だったか、サーバーが実際に仕事をしたかは、ジェネレーターにはわかりません。

そのため、テストに検証レイヤーを自分で付ける必要があります。3つを一緒に見ます。

レイヤー 何を見るか 見逃すと起きること
ステータスコード 2xx以外のレスポンスがいくつあるか 502の嵐を「負荷に耐えた」と読んでしまいます
本文 レスポンスが実際に期待した形か 200に入ったエラーを成功として数えてしまいます
レスポンス数 サーバーが数えたリクエスト数と、ジェネレーターが数えたレスポンス数が同じか 途中で切れたリクエストが統計から消えてしまいます

k6は、このレイヤーをchecksという名前で標準機能に入れてあり、その結果をthresholdsにかけて、終了コードにできます。heyにはそうした機能がないので、人が自分で付ける必要があります。ツールを選ぶ基準が、ここに1つ生まれます。レスポンスを検証する場所があるか。

検証を付ければ終わりではありません。テストの条件が、運用を代表しているかも、別に問う必要があります。特によくずれるのが、3つあります。

接続の再利用。heyは、デフォルトでTCP接続を再利用します。-disable-keepaliveを渡すと、リクエストごとに新しい接続を開きます。同時実行数10で200件を送ると、前者は接続10個、後者は200個です。運用のクライアントが接続プールを使っているのに、テストだけが接続を毎回新しく開くと、テストは、サービスではなく、TCPハンドシェイクとファイアウォールのステートテーブルを測ることになります。

レスポンスのサイズ。テスト用のレスポンスが1KBなのに、運用のレスポンスが64KBなら、1秒あたりのリクエスト数は似た値が出ても、1秒あたりのバイト数は64倍違います。帯域幅やシリアライズがボトルネックのサービスでは、この違いがそのまま、誤った容量の見積もりになります。スループットは、1秒あたりのリクエスト数だけでなく、1秒あたりのバイト数でも見る必要があります。

キャッシュのキー。同じURLだけを繰り返し叩くと、最初のリクエスト以降は、すべてキャッシュヒットです。ヒット率99.5%のテスト結果で、ヒット率20%の運用の容量を決めると、キャッシュの裏のデータベースは、テストで一度も負荷を受けないまま、デプロイされます。

現場での姿

最もよくある形は、「テスト結果が運用より良い」です。そしてその原因は、ほとんどいつも、テストが運用より簡単なことをしていたからです。認証を飛ばしたか、同じデータだけを読んだか、レスポンスが小さかったか、エラー経路に逃げたか。

反対方向もあります。あるチームは、テスト結果が運用の3分の1しか出なかったので、サーバーを3倍に増やしました。あとで見ると、負荷ジェネレーターが、接続の再利用を切ったまま動いていました。増やしたサーバー代は、そのまま請求書に残りました。

3つ目の形は、もっと静かです。テストが何か月もうまく回っていたのに、ある日から結果が良くなる場合です。たいていその間に、対象側で何かが変わっています。認証方式が変わってテスト用のトークンが期限切れになったか、パスが移されてルーターがデフォルトの応答を返しているか、フィーチャーフラグが切れて、実際の計算が飛ばされたか。テストはそのままなのに、対象が別の仕事をし始めたのであり、検証レイヤーがなければ、この変化は「性能が改善した」として記録されます。そのため、検証は、一度付けて終わりではなく、テストと一緒に回り続ける必要があります。

そのため、負荷テストのレポートには、数字より先に書くべきことがあります。このテストが何を検証して、何を検証していないか。検証していないことが1つでもあれば、その数字は、上限でも下限でもありません。そしてその検証は、人がレポートを読んで確認するのではなく、スクリプトの終了コードとして残る必要があります。人は、良い数字を疑わないからです。

次のラボですること

ステータスコードは常に200なのに、3回に1回は本文がエラーの対象を起動して、heyのサマリーだけを見ると完璧に見えることを、まず確認します。そのあと、サーバーが残したアクセスログで、実際に何が出ていったかを数え、エラー応答が正常な応答より速いことを、中央値で確認します。接続の再利用を切ったり入れたりして、接続数が10個と200個に分かれるのを見て、レスポンスのサイズを1KBと64KBに変えて、1秒あたりのバイト数を比較し、固定キーとローテーションするキーで、キャッシュのヒット率が99.5%と0%に分かれるのを見ます。最後に、この3つを検査するチェックリストのスクリプトを作り、採点ツールが、自分で作った入力でそのスクリプトを4回回して、合格と失敗の両方を確認します。