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

負荷テスト

毎秒3千件出たのに、その全部がエラーページだった

TT Labで続きを見る

目標

ステータスコードは常に200なのに、本文はエラーの対象を負荷テストしてみて、サマリーだけを見るテストが何を見逃すかを、自分で数えて確認します。接続の再利用・レスポンスのサイズ・キャッシュのキーを変えながら、テストの条件が運用を代表しているかを判断し、最後に、この結果を信じてよいかを判定するチェックリストのスクリプトを作り、終了コードでゲートを立てます。

なぜ重要なのか

負荷テストのレポートで最も危険な文は、「エラー率0%」です。負荷ジェネレーターが知っているのはステータスコードだけで、200の中に入ったエラーは、その目には見えません。しかも、エラー経路は仕事をしないので早く終わり、テストが壊れるほど、レイテンシの数字が良くなります。そのため、検証のない負荷テストは、間違った答えではなく、何の答えでもありません。検証を付けたあとも、質問がもう1つ残ります。このテストの条件は、運用を代表しているか。接続を毎回新しく開いたなら、TCPハンドシェイクを測ったのであり、レスポンスが運用の64分の1だったなら、シリアライズと帯域幅に一度も触れていないのであり、同じURLだけを叩いたなら、キャッシュがテストの代わりに合格したのです。この判断を、人の目に任せず、スクリプトの終了コードとして固定することが、このラボの最後のステップです。

ステップ

  1. /opt/lab/lt/lt-test-validity/app.pyを、DELAY=0.02 ERR_EVERY=3 SIZE=1024で127.0.0.1:8080に起動してください。アクセスログは/root/lt-test-validity/01-access.logに受けます。起動したあと、/resetを1回呼び出し、hey -n 300 -c 10 'http://127.0.0.1:8080/api/order?id=1'を実行して、元の出力を/root/lt-test-validity/01-summary.txtに保存してください。そして/root/lt-test-validity/01-claim.txtに、4行total=・status_200=・non_2xx=・rps=を、そのサマリーから読み取って書きます。rpsは、サマリーのRequests/secの値をそのまま書いてください。
  2. /root/lt-test-validity/01-access.logの6列目(kind)を数えて、/root/lt-test-validity/02-truth.tsvを作成してください。2行で、各行はタブで区切った2列ok <수>とerror <수>です(okが先、プレースホルダーは数です)。そして/root/lt-test-validity/02-note.txtに3行を書いてください。error_ratio=はエラー本文の割合をパーセンテージで小数第1位まで、hey_non2xx=はステップ1のサマリーの非2xxのレスポンス数、why=は、2つの数字がなぜこれほど違うのかを40文字以上で書きます。
  3. /root/lt-test-validity/01-access.logの8列目(dur_ms)で、正常なレスポンスとエラーのレスポンスの中央値をそれぞれ求めて、/root/lt-test-validity/03-durations.tsvに2行で書いてください。各行はタブで区切った2列ok <중앙값>・error <중앙값>で(プレースホルダーは中央値です)、ミリ秒を小数第1位まで書きます。中央値は、昇順にソートしたn個のうち、int(n/2)+1番目(1から数えて)の値で決めます。そして/root/lt-test-validity/03-note.txtに、faster=<ok 또는 error>とeffect=<이 성질이 시험 결과를 어느 쪽으로 밀어내는가, 40자 이상>の2行を書いてください(プレースホルダーは、okまたはerror、この性質がテスト結果をどちらへ押しやるか40文字以上です)。
  4. エラー注入を切った対象(ERR_EVERY=0 DELAY=0.02 SIZE=1024)で、同じ負荷を2回測ってください。1回はデフォルトで(/root/lt-test-validity/04-on.txt、アクセスログ/root/lt-test-validity/04-on.log)、もう1回は-disable-keepaliveを付けて(/root/lt-test-validity/04-off.txt、アクセスログ/root/lt-test-validity/04-off.log)測り、どちらもhey -n 200 -c 10 'http://127.0.0.1:8080/api/order?id=1'です。/root/lt-test-validity/04-keepalive.tsvに、2行を書いてください。on <연결 수> <rps>とoff <연결 수> <rps>で(プレースホルダーは接続数とrpsです)、rpsは小数第2位までです。接続数は、アクセスログの2列目に出てくる、異なる値の個数です。そして/root/lt-test-validity/04-note.txtに、use_run=<on 또는 off>・conn_ratio=<off 연결 수 ÷ on 연결 수, 소수 첫째 자리>・why=<50자 이상>の3行を書きます(プレースホルダーは、onまたはoff、offの接続数÷onの接続数で小数第1位、50文字以上です)。運用のクライアントは、接続プールを使うと仮定してください。
  5. 同じ負荷(hey -n 200 -c 10 'http://127.0.0.1:8080/api/order?id=1'、ERR_EVERY=0)を、レスポンスのサイズだけを変えて2回測ってください。SIZE=1024は/root/lt-test-validity/05-1k.txt、SIZE=65536は/root/lt-test-validity/05-64k.txtです。/root/lt-test-validity/05-size.tsvに、2行を書きます。各行はタブで区切った4列<이름> <요청당 바이트> <rps> <초당 바이트>で(プレースホルダーは、名前、リクエストあたりのバイト数、rps、1秒あたりのバイト数です)、名前は1kと64k、rpsは小数第2位まで、1秒あたりのバイト数は、リクエストあたりのバイト数 × rpsを四捨五入した整数です。リクエストあたりのバイト数とrpsは、サマリーのSize/requestとRequests/secから読みます。そして/root/lt-test-validity/05-note.txtに、bound_by=<latency 또는 bandwidth>・bytes_ratio=<64k 초당 바이트 ÷ 1k 초당 바이트, 소수 첫째 자리>・why=<50자 이상>の3行を書いてください(プレースホルダーは、latencyとbandwidthのどちらか、64kの1秒あたりのバイト数÷1kの1秒あたりのバイト数で小数第1位、50文字以上です)。
  6. キャッシュを有効にした対象(CACHE=1 ERR_EVERY=0 DELAY=0.02 SIZE=1024)で、2回測ってください。1回はキーが固定された'http://127.0.0.1:8080/api/order?id=1'(/root/lt-test-validity/06-fixed.txt、記録/root/lt-test-validity/06-fixed.log)、もう1回はキーがローテーションするhttp://127.0.0.1:8080/api/order/rotate(/root/lt-test-validity/06-rotate.txt、記録/root/lt-test-validity/06-rotate.log)で、どちらもhey -n 200 -c 10です。2回目はROTATE_KEYS=500で起動してください。/root/lt-test-validity/06-cache.tsvに、2行を書きます。各行はタブで区切った5列<이름> <적중> <빗나감> <적중률> <rps>で(プレースホルダーは、名前、ヒット、ミス、ヒット率、rpsです)、名前はfixedとrotate、ヒット率はパーセンテージで小数第1位まで、rpsは小数第2位までです。そして/root/lt-test-validity/06-note.txtに、representative=<fixed 또는 rotate>・inflation=<fixed rps ÷ rotate rps, 소수 첫째 자리>・why=<50자 이상>の3行を書いてください(プレースホルダーは、fixedまたはrotate、fixedのrps÷rotateのrpsで小数第1位、50文字以上です)。
  7. /root/lt-test-validity/validate.shを作成してください。bash validate.sh <hey 요약 파일> <접근 기록 파일>で呼び出すと(プレースホルダーは、heyのサマリーファイルと、アクセスログファイルです)、3行を出力します。status <OK|FAIL> <값>・body <OK|FAIL> <값>・volume <OK|FAIL> <값>で(プレースホルダーは値です)、名前が行の先頭に来ます。statusは、サマリーの非2xxのレスポンスが0のとき、bodyは、アクセスログのerror本文の割合が1%未満のとき、volumeは、アクセスログの行数がサマリーのレスポンス総数と同じとき、OKです。1つでもFAILなら終了コード1、すべてOKなら0で終了する必要があります。採点ツールは、自分で作った4セットの入力でこのスクリプトを回して、合格と失敗の両方を確認します。
  8. 作ったチェックリストで、ステップ1の結果を判定してください。bash validate.sh 01-summary.txt 01-access.logの出力を/root/lt-test-validity/08-gate-out.txtに保存し、/root/lt-test-validity/08-verdict.txtに、4行を書きます。gate_exit=<종료 코드>・failed_check=<FAIL 이 난 검사 이름>・trustworthy=<yes 또는 no>・fix=<이 시험을 다시 하려면 무엇을 고쳐야 하는가, 60자 이상>です(プレースホルダーは、終了コード、FAILになった検査の名前、yesまたはno、このテストをやり直すには何を直すべきかで60文字以上です)。

参考

サマリーだけを見ると、このテストは完璧である

/opt/lab/lt/lt-test-validity/app.pyを、DELAY=0.02 ERR_EVERY=3 SIZE=1024で127.0.0.1:8080に起動してください。アクセスログは/root/lt-test-validity/01-access.logに受けます。起動したあと、/resetを1回呼び出し、hey -n 300 -c 10 'http://127.0.0.1:8080/api/order?id=1'を実行して、元の出力を/root/lt-test-validity/01-summary.txtに保存してください。そして/root/lt-test-validity/01-claim.txtに、4行total=・status_200=・non_2xx=・rps=を、そのサマリーから読み取って書きます。rpsは、サマリーのRequests/secの値をそのまま書いてください。

サマリーのStatus code distributionの下の行は、[<코드>]<탭><수> responsesの形です(プレースホルダーは、コード、タブ、数です)。awkで角括弧を消せば、最初の列がコード、2番目の列が数になります。サーバーが起動するまで待つには、/statsにcurlを繰り返し送ってください。このステップで出る数字は、あとで「間違っていた」と明らかになる数字です。

サーバーが実際に何を返したかを数える

/root/lt-test-validity/01-access.logの6列目(kind)を数えて、/root/lt-test-validity/02-truth.tsvを作成してください。2行で、各行はタブで区切った2列ok <수>とerror <수>です(okが先、プレースホルダーは数です)。そして/root/lt-test-validity/02-note.txtに3行を書いてください。error_ratio=はエラー本文の割合をパーセンテージで小数第1位まで、hey_non2xx=はステップ1のサマリーの非2xxのレスポンス数、why=は、2つの数字がなぜこれほど違うのかを40文字以上で書きます。

awk -F'\t' '$6=="ok"' 01-access.log | wc -lで数えます。アクセスログの行数は、heyが数えたレスポンス数と同じである必要があります。違えば、途中で切れたリクエストがあるという意味です。ステータスコードは契約の一部にすぎず、本文が残りです。

エラーのほうが速いので、数字が良く見える

/root/lt-test-validity/01-access.logの8列目(dur_ms)で、正常なレスポンスとエラーのレスポンスの中央値をそれぞれ求めて、/root/lt-test-validity/03-durations.tsvに2行で書いてください。各行はタブで区切った2列ok <중앙값>・error <중앙값>で(プレースホルダーは中央値です)、ミリ秒を小数第1位まで書きます。中央値は、昇順にソートしたn個のうち、int(n/2)+1番目(1から数えて)の値で決めます。そして/root/lt-test-validity/03-note.txtに、faster=<ok 또는 error>とeffect=<이 성질이 시험 결과를 어느 쪽으로 밀어내는가, 40자 이상>の2行を書いてください(プレースホルダーは、okまたはerror、この性質がテスト結果をどちらへ押しやるか40文字以上です)。

awk -F'\t' '$6=="ok"{print $8}' 01-access.log | sort -nでソートしたあと、行番号で選びます。エラー経路は、データベースもキューも通らないので、早く終わります。そのため、エラーが混ざるほど、平均とパーセンタイルが良くなり、テストが壊れたというサインが、「遅くなった」ではなく「速くなった」として来ます。

接続を再利用するかが、テストの半分を変える

エラー注入を切った対象(ERR_EVERY=0 DELAY=0.02 SIZE=1024)で、同じ負荷を2回測ってください。1回はデフォルトで(/root/lt-test-validity/04-on.txt、アクセスログ/root/lt-test-validity/04-on.log)、もう1回は-disable-keepaliveを付けて(/root/lt-test-validity/04-off.txt、アクセスログ/root/lt-test-validity/04-off.log)測り、どちらもhey -n 200 -c 10 'http://127.0.0.1:8080/api/order?id=1'です。/root/lt-test-validity/04-keepalive.tsvに、2行を書いてください。on <연결 수> <rps>とoff <연결 수> <rps>で(プレースホルダーは接続数とrpsです)、rpsは小数第2位までです。接続数は、アクセスログの2列目に出てくる、異なる値の個数です。そして/root/lt-test-validity/04-note.txtに、use_run=<on 또는 off>・conn_ratio=<off 연결 수 ÷ on 연결 수, 소수 첫째 자리>・why=<50자 이상>の3行を書きます(プレースホルダーは、onまたはoff、offの接続数÷onの接続数で小数第1位、50文字以上です)。運用のクライアントは、接続プールを使うと仮定してください。

アクセスログの2列目は、そのリクエストを運んだTCP接続の番号です。cut -f2 04-on.log | sort -u | wc -lで数えます。2回目の実行の前に、対象を起動し直さないと、アクセスログが混ざります。heyのオプション名は-disable-compressionではなく-disable-keepaliveです。2つは、別のものを切ります。

レスポンスを64倍にすると、何が変わるか

同じ負荷(hey -n 200 -c 10 'http://127.0.0.1:8080/api/order?id=1'、ERR_EVERY=0)を、レスポンスのサイズだけを変えて2回測ってください。SIZE=1024は/root/lt-test-validity/05-1k.txt、SIZE=65536は/root/lt-test-validity/05-64k.txtです。/root/lt-test-validity/05-size.tsvに、2行を書きます。各行はタブで区切った4列<이름> <요청당 바이트> <rps> <초당 바이트>で(プレースホルダーは、名前、リクエストあたりのバイト数、rps、1秒あたりのバイト数です)、名前は1kと64k、rpsは小数第2位まで、1秒あたりのバイト数は、リクエストあたりのバイト数 × rpsを四捨五入した整数です。リクエストあたりのバイト数とrpsは、サマリーのSize/requestとRequests/secから読みます。そして/root/lt-test-validity/05-note.txtに、bound_by=<latency 또는 bandwidth>・bytes_ratio=<64k 초당 바이트 ÷ 1k 초당 바이트, 소수 첫째 자리>・why=<50자 이상>の3行を書いてください(プレースホルダーは、latencyとbandwidthのどちらか、64kの1秒あたりのバイト数÷1kの1秒あたりのバイト数で小数第1位、50文字以上です)。

1秒あたりのリクエスト数がほぼそのままなのに、1秒あたりのバイト数だけが数十倍に跳ねるなら、この対象は、帯域幅ではなく、待ち時間に縛られているという意味です。スループットを、1秒あたりのリクエスト数だけで見ると、この違いが見えません。運用のレスポンスが64KBなのに、1KBでテストしたなら、そのテストは、シリアライズと帯域幅に一度も触れていません。

同じURLだけを叩くと、キャッシュがテストの代わりに合格する

キャッシュを有効にした対象(CACHE=1 ERR_EVERY=0 DELAY=0.02 SIZE=1024)で、2回測ってください。1回はキーが固定された'http://127.0.0.1:8080/api/order?id=1'(/root/lt-test-validity/06-fixed.txt、記録/root/lt-test-validity/06-fixed.log)、もう1回はキーがローテーションするhttp://127.0.0.1:8080/api/order/rotate(/root/lt-test-validity/06-rotate.txt、記録/root/lt-test-validity/06-rotate.log)で、どちらもhey -n 200 -c 10です。2回目はROTATE_KEYS=500で起動してください。/root/lt-test-validity/06-cache.tsvに、2行を書きます。各行はタブで区切った5列<이름> <적중> <빗나감> <적중률> <rps>で(プレースホルダーは、名前、ヒット、ミス、ヒット率、rpsです)、名前はfixedとrotate、ヒット率はパーセンテージで小数第1位まで、rpsは小数第2位までです。そして/root/lt-test-validity/06-note.txtに、representative=<fixed 또는 rotate>・inflation=<fixed rps ÷ rotate rps, 소수 첫째 자리>・why=<50자 이상>の3行を書いてください(プレースホルダーは、fixedまたはrotate、fixedのrps÷rotateのrpsで小数第1位、50文字以上です)。

アクセスログの7列目が、hitまたはmissです。/api/order/rotateは、リクエストごとに違うキーを使うので、キーの種類数がリクエスト数より多ければ、ヒットが1つもありません。運用のヒット率を知らないまま、固定キーでテストすると、キャッシュの裏のデータベースは、一度も負荷を受けないまま、デプロイされます。

信じてよい結果かを判定するチェックリストを作る

/root/lt-test-validity/validate.shを作成してください。bash validate.sh <hey 요약 파일> <접근 기록 파일>で呼び出すと(プレースホルダーは、heyのサマリーファイルと、アクセスログファイルです)、3行を出力します。status <OK|FAIL> <값>・body <OK|FAIL> <값>・volume <OK|FAIL> <값>で(プレースホルダーは値です)、名前が行の先頭に来ます。statusは、サマリーの非2xxのレスポンスが0のとき、bodyは、アクセスログのerror本文の割合が1%未満のとき、volumeは、アクセスログの行数がサマリーのレスポンス総数と同じとき、OKです。1つでもFAILなら終了コード1、すべてOKなら0で終了する必要があります。採点ツールは、自分で作った4セットの入力でこのスクリプトを回して、合格と失敗の両方を確認します。

3つの検査をそれぞれ実行したあと、最後に1回だけ終了コードを出す必要があります。途中でexitすると、残りの検査の行が出力されません。小数の比較は、awk 'BEGIN{exit !(r < 1.0)}'のように、awkの終了コードで行います。名前を行の先頭に置く理由は、人ではなくパイプラインが読むからです。

ステップ1のあの素晴らしい結果を、ゲートに通してみる

作ったチェックリストで、ステップ1の結果を判定してください。bash validate.sh 01-summary.txt 01-access.logの出力を/root/lt-test-validity/08-gate-out.txtに保存し、/root/lt-test-validity/08-verdict.txtに、4行を書きます。gate_exit=<종료 코드>・failed_check=<FAIL 이 난 검사 이름>・trustworthy=<yes 또는 no>・fix=<이 시험을 다시 하려면 무엇을 고쳐야 하는가, 60자 이상>です(プレースホルダーは、終了コード、FAILになった検査の名前、yesまたはno、このテストをやり直すには何を直すべきかで60文字以上です)。

ステップ1のサマリーだけを見ると、非2xxは0で、レスポンス数も合っています。それでもゲートが止めるなら、どの検査が止めたかが、このラボの結論です。fixには、「何を検証するようにテストを直すか」を書いてください。ツールを変えることも、レスポンスを検査するレイヤーを付けることも、答えになりえます。