Embedded SREが負荷試験を進める上で学んだ、押さえておくべきポイント

目次

1. SREと負荷試験

信頼性を向上させるためには、システムの可用性を確保することが重要です。SREの役割は、システムが安定して動作し、予期せぬ障害が発生しないようにすることです。特にデプロイの際には、リスクが伴います。デプロイが複雑になるほど、その影響は大きくなり、障害の規模も拡大する傾向にあります。Testimの記事では、デプロイの複雑性が増すと、手順が増えたり、多くの人が関与することで、問題が発生する確率が高まると述べられています

6 Deployment Risks and How To Mitigate Them

プロダクトのリリース前には、動作確認を行うことが必要ですが、負荷試験を実施せずに本番環境に移行してしまうと、予期せぬ障害が発生する可能性があります。これによってサービスが停止することは、ビジネスにとって致命的な損失となり得ます。Microsoftもまた、デプロイに際して段階的なロールアウトやブルーグリーンデプロイを推奨し、障害リスクを減らす方法を提案しています

Recommendations for designing a deployment failure mitigation strategy

これらのリスクは特に、新機能やプロダクトのリリース時に大きな影響を与える可能性があります。新しい機能やシステム全体のリリースは、ユーザー数やシステムの規模に応じて、複雑さが増し、トラフィックのピーク時には予期せぬ障害が発生するリスクが高まります。

新しいプロダクトをリリースする際には、十分な動作確認が求められますが、実際の環境でどのように動作するかは、負荷試験を通じてシミュレーションすることでしか確認できません。

負荷試験を行うことで、実際の使用状況やアクセスピーク時のパフォーマンスを検証し、どの程度の負荷に耐えられるかをあらかじめ把握することが可能です。これにより、リリース後のシステムダウンや顧客に影響を及ぼす問題を未然に防ぐことができ、信頼性の高いシステム運用が実現できます。

また、負荷試験を継続的に実施することで、システムのパフォーマンス改善のためのデータを得ることができ、今後の拡張や機能追加に備えて、パフォーマンスに影響を与えるボトルネックを事前に解消することができます。

2. 負荷試験について

2.1 種類

負荷試験にはいくつかのタイプがあり、目的に応じて選択されます。以下に代表的な負荷試験の種類を挙げます。

image

詳しくはこちらから参照しております 参照:Load test types

スモークテスト (Smoke Testing)

スモークテストは、システムやスクリプトが正しく動作するかを確認するために、最小限の負荷で実施されるテストです。これにより、負荷試験の前に基本的な機能とパフォーマンスが期待通りに動作しているか確認できます。通常は短時間(数秒から数分)で実行され、機能や基礎的なメトリクスの確認に用いられます image

参照:Smoke tests

平均負荷テスト (Average-Load Testing)

これは、通常の運用状況におけるシステムのパフォーマンスを評価するテストです。システムが予想される通常の負荷に耐えられるかを確認し、平均的なユーザー数やトラフィックを想定してテストが行われます。一般的に、テストの時間は5分から60分程度です

image

参照:Average-load test

ストレステスト (Stress Testing)

システムが限界まで負荷を受けたときにどのように動作するかを確認するためのテストです。通常の使用状況を超える負荷を与え、システムのボトルネックや回復力を評価します。このテストは、システムが過負荷時にどのように対応するか、異常が発生した際の挙動を確認するために用いられます image

参照:Stress tests

ソークテスト (Soak Testing)

長時間にわたって中程度の負荷を与え、システムの安定性とパフォーマンスを確認するテストです。このテストにより、長時間の使用によって引き起こされるメモリリークやリソース枯渇の有無を確認できます。テスト時間は数時間に及ぶことが多いです

image

参照:Soak tests

スパイクテスト (Spike Testing)

急激に増加するトラフィックに対して、システムがどのように応答するかを評価するテストです。短期間でトラフィックが急増するイベントやセールスイベントなどをシミュレーションし、システムの耐性を確認します

image

参照:Spike tests

ブレークポイントテスト (Breakpoint Testing)

システムが限界に達するまで徐々に負荷を増加させ、最終的にシステムがどの時点で耐えられなくなるかを確認するテストです。これは、システムの最大キャパシティを特定するために使用され、通常はシステムの容量計画に役立ちます

image

参照:Breakpoint tests

2.2 コンポーネント

負荷試験における主要な「登場人物」についてご紹介します

  • 負荷試験クライアント: 負荷試験を実施する側で、テストシナリオを設計し、負荷をかけるためのリクエストを送信します。
  • 負荷試験対象(システム/アプリケーション): テスト対象のシステムやアプリケーションです。リクエストを受け取り、そのパフォーマンスやスケーラビリティを確認します。
  • 攻撃ツール(負荷生成ツール): k6やJMeterなどの負荷生成ツールを使用して、システムに大量のリクエストをシミュレーションします。

image

2.3 ツール

負荷試験にはさまざまなツールが活用され、それぞれが異なるシナリオ記述言語や特性を持っています。

以下に代表的なツールとその特徴を紹介します。

  1. Apache JMeter
  • JMeterは、最も古くから使われている負荷試験ツールで、幅広いプロトコルとアプリケーションに対応しています。特に大規模なシステムやJavaを中心とした企業環境で広く使われており、GUIを用いたシナリオ作成が可能です。
  • シナリオ言語: XMLベースの設定ファイル、GUI

補足

AWSが提供している「Distributed Load Testing on AWS」でもJMeterは採用されております

  1. k6
  • k6は、軽量でスクリプトベースの負荷試験ツールです。特にAPIテストやCI/CDパイプラインへの統合が容易で、JavaScriptで負荷テストシナリオを記述します。軽量な上にパフォーマンスも良く、分散テストやクラウドでのスケーリングにも対応しています。
  • シナリオ言語: JavaScript
  1. Locust
  • 概要: LocustはPythonベースの負荷試験ツールで、スクリプトを使ってユーザー行動をシミュレートします。シンプルな構造ながら、高いスケーラビリティを持ち、大規模な負荷テストにも対応可能です。
  • シナリオ言語: Python
  1. Gatling
  • 概要: GatlingはScalaベースの負荷試験ツールで、高速なテスト実行が可能です。特にWebアプリケーションの負荷試験に強く、スクリプトベースで柔軟なシナリオを作成できます。
  • シナリオ言語: Scala

補足

OSS Insightで比較してみました

JMeter vs k6 image

Locust vs Gatling image

GitHubスター数

  • Locust: 25,066
  • k6: 24,770
  • JMeter: 8,418
  • Gatling: 6,542

k6とLocustが特に人気が高いです。

コミット数

  • JMeter: 52,207
  • k6: 12,866
  • Gatling: 11,614
  • Locust: 4,337

JMeterは最も多くのコミット数を持ち、長期的な開発が進んでいます。

Issues数

  • Gatling: 3,508
  • k6: 1,768
  • Locust: 1,656
  • JMeter: 318

Gatlingとk6は活発なフィードバックがあります。

フォーク数

  • Locust: 2,953
  • JMeter: 2,082
  • k6: 1,230
  • Gatling: 1,187

k6Locustは、スター数やコミュニティの支持で他を上回り、モダンな開発環境で人気。一方、JMeterは長期的な信頼性と多機能性を持ち、企業向けで強みがあります。

2.4 全体の流れ

  1. 負荷試験計画の立案
  2. 負荷試験の準備及び実施(3を満たすまで繰り返す)
  3. 計画時に立てた目標値と前提条件をクリアしたか、意味のある数字が取れたかの確認
  4. 負荷試験レポートの作成、またはシステムの改善、目標値や前提条件の見直し。次の負荷試験に繋げる

参照:Amazon Web Services負荷試験入門

3. 負荷試験の押さえておくべきポイント

3.1 負荷試験を本当に行う必要があるのか

負荷試験を実施すべきかどうかは、システムの規模や想定されるトラフィック量、運用環境に大きく左右されます。例えば、小規模なシステムやアクセスが少ないアプリケーションであれば、通常のテストで十分な場合もあります。

負荷試験には時間やリソース、費用がかかるため、単純に実施するのではなく、その目的と見合ったコストと効果を事前に慎重に検討することが重要です。システムの安定性やスケーラビリティを確認するための投資が必要か、他の手段で代替できるかを見極める必要があります。

3.2 負荷試験の目的を定義する

負荷試験を実施する際には、「何を検証したいのか」を明確にすることが非常に重要です。単に「リリース前だから負荷試験をするのが当たり前」という理由で行っても、目的が不明確だと、その負荷試験が無意味になってしまうこともあります。

具体例として負荷試験を通じて知りたいのは何でしょうか?

  • システムがどの程度の負荷に耐えられるか(性能限界)
  • 高負荷時にどのような障害が発生するか(障害テスト)
  • スループットや応答時間、エラーレートといった特定のメトリクスの確認
  • インフラのスケーラビリティや耐障害性の評価

これらの具体的な目的を定義することで、負荷試験の設計が的確になり、より意味のある負荷試験になります。

3.3 スケジュールの調整

負荷試験は通常、開発プロセスの最終段階で実施されますが、リリーススケジュールやテスト結果を反映するための時間を考慮して計画を立てることが重要です。負荷試験の結果によってはシステムの改修が必要になることもあるため、予備日やバッファを持たせた柔軟なスケジュールを組む必要があります。

具体的なプロセスとしては、以下のようなサイクルを繰り返すことが多いです。機能追加やメジャーバージョンのアップデートなど、要件に応じてこのサイクルが何度も行われる場合があります。

  1. 負荷試験シナリオの作成・計画: テストの目的と条件を定義し、どのようなシナリオで負荷試験を実施するか計画します。
  2. 負荷試験の実施: 計画に基づいてテストを実行し、システムのパフォーマンスを測定します。
  3. ボトルネックの特定と改修: テスト結果を分析し、目標とする性能に達していない場合は、ボトルネックを特定してシステムを改修します。
  4. 再負荷試験の実施: 改修が完了した後、再度負荷試験を実施して、性能が改善されたかどうかを確認します。

3.4 ツール選定

負荷試験を実施するためのツールは、システムの特性、シナリオの作成しやすさ、そしてチームが持つ知見や経験に基づいて選定することが重要です。一般的に、k6、Locust、JMeterなどのツールが使われますが、それぞれのツールが持つ機能や特性、スケーラビリティ、対応しているプロトコルを十分に考慮して選ぶ必要があります。

特にシナリオ作成については、開発チームがシナリオを書くことが多いので、そのシナリオの柔軟性や使いやすさを重視することをお勧めします。

以下のポイントを考慮すると、ツール選定がスムーズになります。

  • シナリオの要件を満たせるか
    • 例:GraphQL、gRPC、tRPCといった特定のプロトコルに対応しているかどうか
  • 柔軟性
    • シナリオが複雑な場合、組み込み関数やライブラリの充実度。カスタマイズが可能かどうか、複雑な条件や動作を再現できるか。
  • シナリオの形式に対応しているか
    • JavaScript、Python、XMLなど、どの言語や形式でシナリオを書くかも重要。チームのスキルセットに合わせて、扱いやすい形式を選ぶ。

3.5 モックが必要かどうか

理想的には、本番環境と同じ条件で負荷試験を行うことが望ましいですが、現実的にはすべての要素を本番環境でシミュレートするのは難しい場合があります。

具体例として、負荷試験対象のサーバーが外部システムと通信している場合、社内のサービスであれば運用中のサービスに影響を与えかねませんし、社外のサービスを利用している場合、試験のたびにコストが無駄に発生する可能性があります。(もちろん、これらの要素を含めて複合的に負荷試験を行い、必要に応じて関係者に許可を得るのも一つの方法です。)

このようなケースでは、モックやスタブを使って一部の機能を置き換えることで、現実に近い負荷を再現しつつ、コストや影響を抑えることが可能です。どの部分をモックにするか、どの程度実環境に近づけるかを慎重に決定することが、テスト結果の信頼性に大きく影響します。

特にモックやスタブを導入する場合、アプリケーション側に改修が必要になることが多いため、これを早期に決定することが重要です。 これにより、開発チームの工数やスケジュールを調整しやすくなります。仕様を早めに固めておくことで、負荷試験全体の進行がスムーズになります。

3.6 負荷試験クライアントの準備

負荷試験を行う際、テストクライアント自体がボトルネックにならないよう、事前に十分な準備をすることが重要です。特に大規模なテストでは、負荷を分散するために複数のクライアントを使用する方法が推奨されます。また、負荷を生成するインフラやネットワーク帯域がテストに十分対応できるかどうかを確認し、必要に応じてリソースをスケーリングすることも考慮すべきです。

例えば、NAT Gatewayを経由している場合、テスト時に十分な負荷を発生させられるか、ネットワークがボトルネックになっていないかを確認する必要があります。

また、負荷試験の特性上、実際のユーザーからのリクエストとは異なり、負荷試験クライアントのみからリクエストを再現することになります。これは、実際のシステムで発生する多数のユーザーからのリクエストと異なる点であり、例えば共通のセッションが使われる場合、本来のレイテンシやネットワークの状況を正確に表現できないことがあります。

テストが正しく実行されているかどうかを確認するためには、以下のポイントなどがあります。

  • 十分な負荷がかかっているか: 負荷試験ツールが意図した負荷を正確に生成できているかを確認します。
  • 正しいレスポンスが返っているか: 生成されたリクエストに対して、サーバーから期待通りのレスポンスが返っているかどうかを検証し、エラー率やタイムアウトなどを確認します。

多くの負荷試験ツールは、必要なメトリクスを自動的に収集し、集計・レポートを生成する機能を備えています。このレポートをもとに、どの程度負荷がかかり、サーバーがどのように応答したかを把握し、結果を分析することができます。

3.7 負荷試験環境の準備

負荷試験を実施する際、特定の項目を重点的に監視したい場合は、負荷試験専用のDatadog Dashboardを作成することが有効です。もし、負荷試験中にボトルネックが見つかり、その原因が特定できない場合は、 APM(Application Performance Monitoring) を導入してトレースを取得したり、APIごとのリクエスト量を測定するためにカスタムメトリクスを活用することが必要になるかもしれません。ただし、APMなどのトレースを挟むと、システムのパフォーマンスに影響を与える可能性があるため、必要に応じて導入することをお勧めします。

個人的には初期段階では基本的なメトリクスのみを収集し、問題が発生した場合にAPMを追加する方が効率的で、コストも抑えられます。

また、負荷試験に使用するデータのボリュームと内容も重要で、テストデータが少なすぎたり偏っていたりすると、現実的な負荷を正確に再現できない可能性があります。そのため、実際のデータや実運用に近いデータを用意し、十分な量のテストデータを準備することを推奨さします。

3.8 Topic: 障害テスト

負荷試験に加えて、システムの耐障害性を検証する障害テストも重要です。例えば、コンテナがSIGTERMシグナルを受け取った際に、ログが正常に記録された後に適切にシャットダウンするかを確認したりです。よくある例としては、データベースのフェイルオーバーが挙げられます。この際、システムが正常に復旧するか、ダウンタイムが発生する場合はその時間がどの程度かを検証することが重要です。

AWSでは、Fault Injection Simulator (FIS) というサービスが提供されており、システムに故意に障害を発生させることで、障害発生時の挙動や耐障害性をテストすることができます。

これにより、障害発生時の復旧プロセスや、システムの信頼性向上に向けた改善点を特定することができます。

AWS Fault Injection Simulator

4. まとめ

最後までお読みいただき、ありがとうございました。この記事は、私がEmbedded SREとして転職後のタスクのひとつとして、負荷試験に取り組み、開発チームとの密なコミュニケーションを通じて学んだ経験を元にまとめたものです。

負荷試験に初めて取り組む中で、転職したばかりの私にとっては、わからないことだらけでしたが、多くの方々の協力を得ながら進めることができております。サポートしてくださる方々に感謝しております。