こんにちは。よっしーです(^^)
今日は、k6を利用した大規模なテスト実行についてご紹介します。
背景
Dockerで構築したWebアプリの開発環境において、k6を利用した負荷テストについて調査したときの内容を備忘として残しました。
開発環境のソースは下記のリポジトリにあります。
エラーハンドリングの耐障害性
エラーハンドリングは耐障害性を持つべきです。大規模なストレステストを実行する際、スクリプトはHTTPレスポンスについて何も前提しないべきです。一部のスクリプトの見落としとして、ハッピー パスだけを考慮してテストすることがあります。
たとえば、k6スクリプトでは、以下のようなハッピー パスのチェックがよく見られます:
import { check } from 'k6';
import http from 'k6/http';
const res = http.get('https://test.k6.io');
const checkRes = check(res, {
'Homepage body size is 11026 bytes': (r) => r.body.length === 11026,
});
このようなコードは、SUTが過負荷でなく、適切なレスポンスを返す場合には問題ありません。システムが障害を起こし始めると、このチェックは期待どおりに動作しなくなります。
問題は、このチェックが常にレスポンスに本文があると仮定していることです。しかし、サーバーが障害を起こしている場合、r.bodyが存在しない可能性があります。このような場合、チェック自体が期待通りに機能せず、以下のようなエラーが返される可能性があります:
ERRO[0625] TypeError: Cannot read property 'length' of undefined
この問題を修正するためには、チェックをどのようなレスポンスタイプにも対応できるように設定する必要があります。
以下の変更を行うことで、例外を処理できるようになります。
import { check } from 'k6';
import http from 'k6/http';
const res = http.get('https://test.k6.io');
const checkRes = check(res, {
'Homepage body size is 11026 bytes': (r) => r.body && r.body.length === 11026,
});
リソース使用量を削減するためのk6オプション
次のk6設定は、大規模なテストを実行する際のパフォーマンスコストを削減できます。
discardResponseBodiesでメモリを節約 デフォルトでは、k6はリクエストのレスポンスボディをメモリに読み込みます。これによりメモリ消費量が大幅に増加し、しばしば不要です。
k6に対してすべてのレスポンスボディを処理しないように指示するには、次のようにオプションオブジェクトでdiscardResponseBodiesを設定します:
export const options = {
discardResponseBodies: true,
};
ストリーミング時には、–no-thresholdsおよび–no-summaryを使用してください
ローカルテストを実行し、結果をクラウドにストリーミングする場合(k6 run -o cloud)、ターミナルのサマリーとローカルの閾値計算を無効にすることができます。クラウドサービスがサマリーを表示し、閾値を計算するためです。
これらのオプションを使用しない場合、操作はローカルマシンとクラウドサーバーの両方で重複して実行されます。これにより、一部のメモリとCPUサイクルを節約できます。
以下は、すべての言及されたフラグを1つにまとめたものです:
k6 run scripts/website.js \
-o cloud \
--vus=20000 \
--duration=10m \
--no-thresholds \
--no-summary
スクリプトの最適化
ハードウェアから最大のパフォーマンスを引き出すには、テストスクリプト自体のコードを最適化することを検討してください。これらの最適化の一部は、特定のk6の機能の使用方法を制限するものです。他の最適化は、すべてのJavaScriptプログラミングに一般的です。
リソース集中型のk6操作を制限
k6 APIの一部の機能は、実行により多くの計算を必要とします。負荷生成を最大化するために、スクリプトが次のような方法でこれらの機能を使用する制限が必要かもしれません。
- チェックとグループ k6は、各個別のチェックとグループの結果を個別に記録します。多くのチェックとグループを使用している場合、パフォーマンス向上のためにこれらを削除することを検討するかもしれません。
- カスタムメトリクス チェックと同様に、カスタムメトリクス(Trend、Counter、Gauge、Rate)の値も個別に記録されます。カスタムメトリクスの使用を最小限に抑えることを検討してください。
- abortOnFailを持つ閾値 abortOnFail閾値を設定している場合、k6は閾値が越えられなかったことを確認するために結果を常に評価する必要があります。この設定を削除することを検討してください。
- URLのグループ化 k6 v0.41.0では、時系列メトリクスをサポートするために変更が導入されました。この変更の副作用として、すべての一意のURLが新しい時系列オブジェクトを作成するため、予想よりも多くのRAMを消費する可能性があります。これを解決するには、URLのグループ化機能を使用してください。
JavaScriptの最適化
最後に、前述の提案が不十分な場合、一般的なJavaScriptの最適化も考えられます:
- 深くネストされたforループを避ける。
- メモリ内の大きなオブジェクトへの参照をできるだけ避ける。
- 外部のJS依存関係を最小限に抑える。
- ビルドパイプラインがある場合、k6スクリプトのツリーシェイキングを行うなど、さまざまな最適化手法を試すことができます。
V8ランタイムのガベージコレクションに関するこの記事を参照してください。k6が使用するJavaScript VMは非常に異なり、Goで実行されますが、一般的な原則は適用されます。なお、k6スクリプトにはメモリリークが依然として発生する可能性があり、修正されない場合、RAMを迅速に枯渇させる可能性があります。
おわりに
今日は、k6を利用した大規模なテスト実行についてご紹介しました。
何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。
それでは、また明日お会いしましょう(^^)
コメント