キーポイント
- Conducting load tests against APIs and websites can both validate performance after a long stretch of development and get useful feedback from an application in order to increase its scaling capabilities and performance.
- Engineers should avoid creating “the cathedral” of load testing and end up with little time to improve performance overall. Write the simplest possible test and iterate from there.
- Gatling can be used to conduct stress tests, soak tests, and capacity tests. It is also possible to write Give-When-Then style tests.
- When analyzing results, engineers must examine percentile results, rather than focusing on averages.
- It is important to establish the goals, constraints, and conditions of any load test. Always identify and verify any assumptions, e.g. a user’s default device, network speed, the type of server an application will be running on production.
ソフトウェアエンジニアリングのログブックを開いて、書き始めます。「研究開発チームのログ。 私たちのアプリケーションでこのような失敗を予見することは率直に言ってできませんでした。私にはわかりません。私たちはそれをすべて持っていました。上出来でした ! テストはグリーンで、指標は良好で、ユーザは満足していました、にもかかわらず、トラフィックが大量に到着したとき失敗しました」
深呼吸をするために一息つき、続けます。「私たちは準備ができていると思いました。人気のあるサイトを運営するコツがあると思いました。どうしましたか ? 程遠い。TechCrunchのリンクはそれほど多くのトラフィックをもたらさないと当初は思っていました。また、テレビCMが放送された後の負荷の急増にも対応できると考えていました。アプリケーションが起動直後に応答を停止したとき、このすべてのテストのポイントは何でしたか。また、投入したサーバーの数に関係なく、これを修正することはできず、可能な限り状況を救おうとしました」
長い休止の後、「もう遅いです、時間が経ちます。伝えたいことはたくさん残っています、私が知りたいことはたくさんあり、私が決して作りたくなかった多くの間違い」 止められない、「それを機能させるために私たちにできることがあれば...」
そして、明晰さがあなたに反撃します。あなたは疑問に思います: そもそもなぜ負荷テストを行うのだろう ? 長い開発期間の後にパフォーマンスを検証するためですか、それともアプリケーションから有用なフィードバックを取得してスケーリング機能とパフォーマンスを向上させるためですか ? どちらの場合も検証が行われますが、後者の場合、フィードバックの取得がプロセスの中心になります。
なるほど、これは実際には負荷テスト自体のことではありません。目標は、アプリケーションが稼働したときにクラッシュしないようにすることに重点を置いています。負荷テストの「伽藍」を作成する必要はなく、全体的なパフォーマンスを向上させるための時間はほとんどありません。多くの場合、改善に取り組むことは、すべてではないにしても、ほとんどの時間を費やしたい場所です。負荷テストは、目的を達成するための手段にすぎず、他には何もありません。
締め切りが明日であるために恐れていて、迅速な勝利を探しているなら、ようこそ、これがあなたのための記事です。
可能な限り単純なシミュレーションを書く
この記事では、Gatlingを使用するので、少し精密にScalaコードを記述します。コードは怖く見えるかもしれませんが、そうではないと請け負います。実際、Scalaは今のところ脇に置いておくことができ、Gatlingを独自の言語と考えることができます:
import io.gatling.core.Predef._
import io.gatling.http.Predef._
class SimplestPossibleSimulation extends Simulation {
val baseHttpProtocol =
http.baseUrl("https://computer-database.gatling.io")
val scn = scenario("simplest")
.exec(
http("Home")
.get("/")
)
setUp(
scn.inject(atOnceUsers(1))
).protocols(baseHttpProtocol)
}
これを小さな部分に分解してみましょう。Gatlingはそれ自体がJava仮想マシン上で実行されるScalaを使用して構築されているため、予想通り、いくつかの類似点があります。一番目は、コードが常にパッケージのインポートで始まることです:
import io.gatling.core.Predef._
import io.gatling.http.Predef._
これらにはすべてのGatling言語定義が含まれており、必要に応じて、他のインポートとともにこれらを常に使用します。次に、シミュレーションをカプセル化するのに十分なコードを作成します:
class SimplestPossibleSimulation extends Simulation {}
SimplestPossibleSimulation
はシミュレーションの名前です。Simulation
はGatlingによって定義された構造であり、“extend”し、すべてのシミュレーションコードを三個の部分に含めます。 (1) 使用するプロトコルにプロトコルに必要なパラメータを定義する必要があります:
val baseHttpProtocol =
http.baseUrl("https://computer-database.gatling.io")
(2) シナリオ自体のコード:
val scn = scenario("simplest")
.exec(
http("Home")
.get("/")
)
これまでシミュレーションについてのみ説明しましたが、シナリオという用語を使用したことに注意してください。それらはしばしば混同されますが、それらは両方とも本当に異なる意味を持っています:
- シナリオは、アプリケーションのタイプに応じて、1人のユーザがアプリケーション上で、ページからページへ、エンドポイントからエンドポイントへなどをナビゲートする過程を説明します。
- シミュレーションとは、シナリオに割り当てられた母集団 (管理者やユーザなど) を使用した完全なテストの定義です。
- シミュレーションを起動することは、実行すると呼ばれます
この用語は、最後のセクション (3) シミュレーションの設定を理解するのに役立ちます:
setUp(
scn.inject(atOnceUsers(1))
).protocols(baseHttpProtocol)
シミュレーションはおおよそシナリオのコレクションであるため、それら自身にインジェクションプロファイルで構成されたすべてのシナリオの合計です (ここでは無視します) 。シナリオで実行されるすべての要求に基づくプロトコルは、setUpの後にチェーンされている場合は共有するか、scn1とscn2が複数ある場合はシナリオごとに定義することができます:
setUp(
scn1.inject(atOnceUsers(1)).protocols(baseHttpProtocol1),
scn2.inject(atOnceUsers(1)).protocols(baseHttpProtocol2)
)
インジェクションプロファイルとプロトコルで構成されたscn1などのように、すべてがどのようにチェーンされ、コンマで区切られているかに注意してください。
Gatlingシミュレーションを実行すると、内部で構成されたすべてのシナリオが同時に直ちに起動します。
シミュレーションの実行
Gatlingシミュレーションを実行する1つの方法は、Gatlingオープンソースバンドルを使用することです。これは、シミュレーションファイルを内部にドロップするフォルダとそれらを実行するスクリプトを備えた自己完結型のプロジェクトです。
警告 ! すべてのインストールには、適切なJavaインストール (JDK 8以上) が必要です。詳細については、Gatlingインストールのドキュメントを参照してください。 |
バンドルを解凍した後、user-files/simulations
フォルダ内にSimplestPossibleSimulation.scala
という名前のファイルを作成します。次に、コマンドラインからバンドルフォルダのルートに次のように入力します:
./bin/gatling.sh
または、Windowsの場合:
.\bin\gatling.bat
これにより、前述のフォルダ内のシミュレーションがスキャンされ、どのシミュレーションを実行するかを尋ねるプロンプトが表示されます。バンドルにはいくつかの例が含まれているため、それらから選択できます。
これはGatlingシミュレーションを実行する最も簡単な方法ですが、ソースリポジトリではうまく機能しません。推奨される方法は、MavenやSBTなどのビルドツールを使用することです。このソリューションを試みたい場合は、gitを使用してデモリポジトリのクローンを作成できます:
または、この記事をフォローしたい場合は、そのために作成された次のリポジトリのクローンを作成できます:
テストを実行した後、出力の最後にURLがあり、それを開いて障害に気付きます。
そして:
自分たちで作成しなかった「Home Redirect 1」という名前のリクエストがあることに気付くでしょう。リダイレクトを自動的に追跡し、すべての異なるクエリから応答時間を分離しているのではないかと思われるかもしれません。しかし、それはおもしろみに欠けるように見えます、それは正しいでしょう。
インジェクションプロファイルの構成
シナリオを構成するときに振り返えると、次のことを行いました:
scn.inject(atOnceUsers(1))
これはデバッグ目的には最適ですが、負荷テストに関してはそれほどスリリングではありません。重要なのは、インジェクションプロファイルを作成する正しい方法も間違った方法もありませんが、まず最初に行うことです。
インジェクションプロファイルは、指定された順序で実行される一連のルールであり、ユーザが独自のシナリオを開始する速度を記述します。作成する必要のある正確なプロファイルがわからない場合でも、ほとんどの場合、予想される動作については理解できます。これは、通常、特定の時点で多くの利用ユーザを予想しているか、ビジネスの成長に伴ってより多くのユーザが来ることを期待しているためです。
考慮すべき質問は次のとおりです。ユーザがすべて同時に到着することを期待していますか ? これは、フラッシュセールを提供する予定がある場合、またはWebサイトがまもなくテレビに表示される場合に当てはまります。そして、ユーザが従うパターンについての考えはありますか ? ユーザが特定の時間に到着したり、1日を通して分散したり、勤務時間内にのみ表示されたりする可能性があります。この知識により、一種のテストと呼ばれる迎え角が得られます。そのうちの3つを紹介します。
ストレステスト (過負荷テスト)
負荷テストについて考えるとき、よく「ストレステスト」について考えます。これは、単一のタイプのテストにすぎないことがわかります。「フラッシュセールス」は根本的な意味です。
アイデアは単純です。多くのユーザー、可能な限り短い時間です。atOnceUsers
はそのために最適ですが、いくつかの注意点があります:
scn.inject(
atOnceUsers(10000)
)
ハードウェアに過度の負担をかけずに、同時に起動できるユーザーの数を言うことは困難です。CPU、メモリ、帯域幅、Linuxカーネルが開くことができる接続の数、マシンで使用可能なソケットの数など、考えられる制限はたくさんあります。値を高くしすぎると無意味な結果になってしまうため、ハードウェアに負担がかかりやすいですか ?
これは、テストを実行するために複数のマシンが必要になる可能性があります。例を挙げると、Linuxカーネルは、適切に最適化されていれば、毎秒5kの接続を簡単に開くことができますが、10kでは多すぎます。
1分間に1万人のユーザーを分割することは、時間の制約が非常に厳しいため、依然としてストレステストと見なされます:
scn.inject(
rampUsers(10000) during 1.minute
)
ソークテスト (耐久テスト)
期間が長くなりすぎると、ユーザジャーニーはより親しみやすくなります。「1日あたりのユーザー数」、「1週間あたりのユーザー数」などを考えてください。ただし、ユーザが1日にどのように到着するかを模倣することは、耐久テストの目的ではなく、結果にすぎません。
耐久テストを行う場合、テストしたいのは、長期間にわたるシステムの動作です。CPUはどのように動作しますか ? メモリリークを確認できますか ? ディスクはどのように動作しますか ? ネットワークは ?
これを行う方法は、長期間にわたって到着するユーザをモデル化することです:
scn.inject(
rampUsers(10000000) during 10.hours // ~277 users per sec
)
これは、「1日あたりのユーザー数」を実行しているように感じられます。それでも、分析のモデル化が目標である場合は、実行と期間の短縮を強化して、1秒あたりのユーザー数を計算することで結果を速く得られるでしょう:
scn.inject(
constantUsersPerSec(300) during 10.minutes
)
これについて言えば、constantUserPerSec
よりもrampUsers
は好みの問題です。後者は、到着したユーザーの総数の概要を簡単に示し、前者はスループットに関するものです。ただし、スループットについて考えると、「ランプアップ」、つまり最終目的地に徐々に到達することが容易になります。
scn.inject(
rampUsersPerSec(1) to 300 during 10.minutes,
constantUsersPerSec(300) during 2.hours
)
キャパシティテスト (限界値テスト)
最後に、システムが処理できるスループットをテストするだけで済みます。その場合、限界値テストが進むべき道です。前のテストの方法を組み合わせて、任意の時間からスループットを平準化し、負荷を増やし、再び平準化し、すべてがダウンしてリミットが発生するまで続行するというアイデアです。次のような:
scn.inject(
constantUsersPerSec(10) during 5.minutes,
rampUsersPerSec(10) to 20 during 30.seconds,
constantUsersPerSec(20) during 5.minutes,
rampUsersPerSec(20) to 30 during 30.seconds,
...
)
これを20回行うのは少し面倒かもしれませんが … Gatlingのベースはコードであるため、前のインジェクションプロファイルを生成するループを作成するか、限界値テスト専用のDSLを使用できます:
scn.inject(
incrementUsersPerSec(10)
.times(20)
.eachLevelLasting(5.minutes)
.separatedByRampsLasting(30.seconds) // optional
.startingFrom(10) // users per sec too!
)
これをグラフィカルにモデル化する方法は次のとおりです:
以上です !
ロードテスト (負荷テスト) はどこから始めますか ?
初めての負荷テストの場合は、ターゲットユーザの動作をすでに知っているかどうかに関係なく、限界値テストから始める必要があります。過負荷テストは便利ですが、メトリックの分析は、このような負荷の下では本当に注意が必要です。すべてが同時に失敗しているので、それはタスクを困難にし、不可能にさえします。限界値テストは、最初の分析にとってより快適な、ゆっくりと失敗するまでの贅沢を提供します。
開始するには、限界値テストを実行して、アプリケーションをできるだけ早くクラッシュさせます。すべてがスムーズに実行されているように見える場合にのみ、シナリオを複雑にする必要があります。
次に、メトリックを確認する必要があります:
これらの結果のすべてはどういう意味ですか ?
上のグラフは、応答時間のパーセンタイルを示しています。負荷テストを行う場合、平均を使用してグローバル応答時間を分析したくなる可能性があり、エラーが発生しやすくなります。平均が実行中に何が起こったかの概要を簡単に示すことができる場合、それはあなたが実際に見たいすべてのものをラグの下に隠します。これは、パーセンタイルが役立つところです。
このように考えてください。平均応答時間がミリ秒単位の場合、ユーザベースの1%で、最悪の場合のエクスペリエンスはどのように感じられますか ? 良くも悪くも ? 0.1%のユーザにとってどのように感じますか ? そして、ゼロにどんどん近づいていきます。ユーザとリクエストの数が多いほど、極端な行動を研究するためにゼロに近づく必要があります。たとえば、300万人のユーザがアプリケーションで1つのリクエストを実行し、そのうちの0.01%がタイムアウトした場合、アプリケーションにアクセスできなかったユーザは3万人になります。
パーセンタイルがよく使用されます。これは、これを逆に考えることに対応します。ユーザの1%の最悪のケースは、「99%のユーザーにとって最高のエクスペリエンスはどのように感じられるか」、0.1%は99.9%などになります。計算をモデル化する方法は、すべての応答時間を昇順でソートして点でマークします:
- 応答時間の0%が最小で、最小値をマークします
- 100%は最大値です
- 50%は中央値です
ここから、ユーザ数に応じて9を追加することで、99%から可能な限り100%に近づきます。
前のグラフでは、99パーセンタイルが63ミリ秒でしたが、それは良いですか ? 最大65であるため、そう思われます。でも、10ミリ秒だったらもっといいのでしょうか ?
ほとんどの場合、指標は状況に応じたものであり、それ自体では意味がありません。大まかに言えば、ローカルホストでの10ミリ秒の応答時間は達成ではなく、光の速度の制約のためにパリからオーストラリアへは不可能です。「実際に実行されたのはどのような条件でしたか ? 」と尋ねる必要があります。これは、実行が実際にそれほど良かったかどうかを推測するのに大いに役立ちます。これらの条件は次のとおりです:
- アプリケーションを実行しているサーバの種類は何ですか ?
- それはどこにありますか ?
- アプリケーションは何をしていますか ?
- ネットワークの下にありますか ?
- TLSはありますか ?
- シナリオは何をしていますか ?
- アプリケーションがどのように動作することを期待しますか ?
- すべてが同じデータセンタの同じクラウドプロバイダで実行されていますか ?
- 予想されるレイテンシーの種類はありますか ? モバイル (3G) 、長距離を考えてください。
- その他
これが最も重要な部分です。テストの実行条件を知っていれば、疑問に思うことができます。すべてをあなたのコンピュータ上で実行して、ローカルでテストできます(そしてそうすべきです)。結局のところ、プロダクション環境でどのように実行されるかを推測することはできませんが、回帰テストを行うことができます。複数の実行間でメトリックを比較するだけで、パフォーマンスが向上したか低下したかがわかります。
そのために本格的なテスト環境は必要ありません。エンドユーザがモバイルユーザであることがわかっている場合は、単一のデータセンターにあるすべてのマシンでテストすると、災害につながる可能性があります。ただし、100%現実的である必要もありません。それが意味することと、テスト条件から推測できることを覚えておいてください。データセンタは巨大なローカルネットワークであり、結果をモバイルネットワークと比較することはできません。実際、観測された結果は実際よりもはるかに優れています。
ニーズの指定
テストが実行される条件を知っているだけでなく、テストが成功または失敗する理由を事前に決定する必要があります。そのために、次のように基準を定義します:
- 250ms未満の平均応答時間
- 2000人のアクティブユーザ
- 1%未満の失敗したリクエスト
ただし、問題があります。これらの3つの受け入れ基準のうち、負荷がかかっているシステムを説明するのに実際に役立つのは1つだけですが、どれに、何に置き換えることができるかを推測できますか ?
(1) 「250ms未満の平均応答時間」: 前のセクションから当然のことながら、平均は悪くはありませんが、ユーザーの行動を説明するだけでは不十分であり、常にパーセンタイルを使用する必要があります:
250ms未満の平均応答時間- 250ms未満が99パーセンタイル
- 最大1000ms未満
(2) 「2000人のアクティブユーザ」: これはもっとトリッキーです。今日では、「アクティブユーザ」などを示す分析が殺到しているため、この測定値を使用して受け入れ基準を定義したくなるはずです。しかし、それは問題です。アクティブユーザの数を直接モデル化する唯一の方法は、クローズドモデルを作成することです。ここでは、ユーザのキューができあがります。ここに問題があります。考えてみてください。アプリケーションの速度が低下し、ユーザがキューにいる場合、ユーザはキューに積み上げられ始めますが、「アクティブ」でリクエストを実行するのはごく少量 (アプリケーションで許可されている最大量) だけです。アクティブユーザの数は同じままですが、ユーザは店にいるときと同じように外で待っています。
実世界では、Webサイトがダウンした場合、人々は何かが発生するまでページを更新し続けます。これは、人々が諦めてあなたがそれらを失うまで、Webサイトのパフォーマンスをさらに悪化させます。ユースケースでない限り、アクティブユーザをモデル化するべきではありません。代わりに、アクティブユーザの数をターゲットにする必要があります。それは難しいかもしれませんが、実行可能です。それは、応答時間、一時停止などを含むシナリオの期間、ネットワーク、インジェクションプロファイルなどに依存します。
これは代わりに測定できるものです:
2000人のアクティブユーザ- 1秒あたり100〜200人の新規ユーザ
- 1秒あたり100を超えるリクエスト
(3) 「1%未満の失敗したリクエスト」は、実際、3つの間の負荷がかかっているシステムを適切に表す唯一の基準でした。ただし、経験則として解釈されるべきではありません。ユースケースによっては、1%が高すぎる場合があります。eコマースサイトについて考えてみてください。一部のページがあちこちで失敗することを許可するかもしれませんが、購入直前のコンバージョンファネルの最後で失敗すると、ビジネスモデルにとって致命的です。この特定のリクエストには、失敗基準が0%で、残りはより高いパーセンテージであるか、基準がまったくない場合があります。
これらすべてが、Given-When-Thenモデルに基づく仕様と、これまでに学んだすべての負荷テスト全般についての考え方につながります:
- Given: インジェクションプロファイル
- When: シナリオ
- Then: 受け入れ基準
前の例を使用すると、次のようになります:
- Given: 毎秒200ユーザーの負荷
- When: ユーザーがホームページにアクセスする
- Then:
- 少なくとも100リクエスト/秒を受け取る
- 250ミリ秒未満で99パーセンタイル
- そして、1%未満の失敗したリクエスト
最後に、Given-When-Thenモデルは、Gatlingシミュレーションコードとして完全に統合できます:
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._
class FullySpecifiedSimulation extends Simulation {
val baseHttpProtocol =
http.baseUrl("https://computer-database.gatling.io")
// When
val scn = scenario("simplest")
.exec(
http("Home")
.get("/")
)
// Given
setUp(
scn.inject(
rampUsersPerSec(1) to 200 during 1.minute,
constantUsersPerSec(200) during 9.minutes
)
).protocols(baseHttpProtocol) // Then
.assertions(
global.requestsPerSec.gte(100),
global.responseTime.percentile(99).lt(250),
global.failedRequests.percent.lte(1)
)
}
シナリオ (When) の部分は (まだ) 変更されていませんが、同じ場所にインジェクションプロファイル (Given) が必要であり、Thenとしてアサーションと呼ばれる新しい部分が必要です。アサーションが行うことは、すべてのシミュレーション内からメトリックを計算することであり、要件を下回ったり上回ったりすると、「ビルド」に失敗します。たとえば、Mavenプロジェクトを使用すると、次のように表示されます:
[INFO] ------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------
[INFO] Total time: 07:07 min
[INFO] Finished at: 2020-08-27T20:52:52+02:00
[INFO] ------------------------------------------------------------
それ以外の場合は、BUILD FAILUREです。これは、Gatling Jenkinsプラグインを提供しているJenkinsなどのCIツールを使用する場合に便利です。これを使用して、シミュレーションの実行をCIに直接構成し、受け入れ基準が失敗した場合にその量を通知することができます。
前の例では、開始点としてキーワード global を使用しましたが、失敗のない単一のリクエストを持ち、他のすべてを無視するのと同じくらい正確である可能性があることに注意してください:
.assertions(
details("Checkout").failedRequests.percent.is(0)
)
その他の例は、アサーションのドキュメントにあります。
テストプロトコルの定義
ネットワークなどの特定の変数がパフォーマンス低下の原因であると思われる場合は、仮定をテストするための適切なテストプロトコルが必要になります。各変数は、「目撃者 (witness)」に対してテストする必要があります。「何か」が原因であると思われる場合は、それをテストして、テストしてから比較してください。ベースラインを作成し、小さなバリエーションを作成してから、これに対してテストします。
それは、常にあなたの仮定をテストすることに要約されます、
追加のツールは、Gatlingでは不可能な必需品をキャッチするのに役立ちます。Gatlingによって提供されるすべての指標はユーザの視点からのものであるため、スペクトルの反対側にシステムとアプリケーションの監視が装備されていることを確認する必要があります。システム監視は、Gatling側でも非常に便利です。CPU、メモリ、ネットワーク接続とその状態、ネットワークの問題などの情報を使用した大規模なテストで、マシン上で使用しているリソースが多すぎるかどうかを確認するのに役立ちます。
PrometheusとNode Exporterを組み合わせたものなど、人気のあるメトリック収集ソリューションは数多くあります。これらは、適切なダッシュボードと組み合わせてGrafanaと一緒に使用できます。常に実行してメトリックを収集します。テストの時間枠でテストを実行するときに確認します。
このようなツールを装備すると、テストプロトコルは次のように要約されます:
- システムとアプリケーションの監視が有効になっていることを確認します
- 負荷テストを実行します (限界値など)
- リミットを分析して推測する
- チューニング
- 再試行
超えて
ログブックを閉じると、結局、それほど成功は遠くなかったことがわかります。本格的な負荷テストの伽藍を構築するのではなく、一度にひとつの小さな一歩を踏み出し、知識が重要であることを理解することにします。必要な情報は入手するのが早ければ早いほどよいでしょう。
これからは、すべてが正常に機能しているように見えるときに、シナリオを複雑にして、ユーザが実際にアプリケーションでアクションを実行する方法にますます近づけることです。最も役立つリソースは、Gatling OSSのドキュメントと次のとおりです:
- Quickstart、最初から多くのコードを記述せずに、このユーザージャーニーを記録する方法を学ぶことができます。
- Advanced Tutorial、より深く進むためのチュートリアル
- Cheat-Sheet、すべてのGatling言語のキーワードとその使用法の一覧
著者について
Guillaume Corré氏は、パリの世界最大のスタートアップキャンパスであるStation Fに拠点を置く、Gatling Corpでソフトウェアエンジニア、コンサルタント、テックリードとして働いています。スイスアーミーナイフは本質的に、データの最適化、アプリケーションの負荷テストに最適な開発者ツールであるGatlingを使用した本番環境のクラッシュなどの簡単なことを楽しんでおり、アプリケーションやWebサイトが成功の妨げになるのを防ぎ、ゴーライブやブラックフライデーのような重大な状況に直面するのを助けます。