paint-brush
ターボチャージ負荷テスト: YandexTank + ghz コンボによる超高速コード チェック!@mrdrseq
503 測定値
503 測定値

ターボチャージ負荷テスト: YandexTank + ghz コンボによる超高速コード チェック!

Ilia Ivankin14m2023/11/23
Read on Terminal Reader

長すぎる; 読むには

要約すると、1 秒あたり 100 以上のリクエストの負荷を処理するサービスの能力を迅速に評価する必要がある場合、または潜在的な弱点を特定する必要がある場合、チームが関与する複雑なプロセスを開始したり、AQA に支援を求めたり、インフラストラクチャ チームに頼ったりする必要はありません。 多くの場合、開発者は小規模な負荷テストを実行できる高性能のラップトップやコンピューターを持っています。それでは、時間を節約して、ぜひ試してみてください。
featured image - ターボチャージ負荷テスト: YandexTank + ghz コンボによる超高速コード チェック!
Ilia Ivankin HackerNoon profile picture

やあ!


場合によっては、ローカル環境でもテスト プラットフォームでも、迅速な負荷テストが必要になることがあります。通常、このようなタスクは、事前の完全な理解を必要とする特殊なツールを使用して取り組まれます。しかし、市場投入までの時間を短縮し、仮説を迅速に検証することが最も重要である企業や新興企業では、ツールを過度に使いこなすことが贅沢になってしまいます。


この記事は、開発者中心のソリューションに焦点を当てることを目的としています。これにより、深い関与の必要性がなくなり、ドキュメントを何ページも読み込むことなく初歩的なテストが可能になります。


ローカル実行中

インストールする必要があります::

  1. Docker — すべてのサービスとツールが必要です。


  2. Java 19+ — kotlin サービス用。また、Java 8 バージョンを使用してみることもできます。動作するはずですが、Gradle 設定を変更する必要があります。


  3. Golang — サービスの 1 つは golang サービスです =)


  4. Python 3+ — Yandex タンク用。

技術要件

作業を開始する前に、テスト目的の説明例として役立つサービスをいくつか生成することをお勧めします。


スタック: Kotlin + webflux。 r2dbc + postgres。


私たちのサービスには次のような特徴があります。

– すべてのストックを取得 (制限 10) GET /api/v1/ストック
– 名前で株式を取得 GET__ /api/v1/stock ?name=アプリケーション
– 在庫を保存 POST /
API/v1/ストック


負荷テストに集中する必要があるので、簡単なサービスになるはずです =)

Kotlin と HTTP サービス

まずは、内部に基本的なロジックを備えた小さなサービスを作成しましょう。この目的のためにモデルを準備します。


 @Table("stocks") data class Stock( @field:Id val id: Long?, val name: String, val price: BigDecimal, val description: String )


シンプルなルーター:

 @Configuration @EnableConfigurationProperties(ServerProperties::class) class StockRouter( private val properties: ServerProperties, private val stockHandler: StockHandler ) { @Bean fun router() = coRouter { with(properties) { main.nest { contentType(APPLICATION_JSON).nest { POST(save, stockHandler::save) } GET(find, stockHandler::find) GET(findAll, stockHandler::findAll) } } } }


およびハンドラー:

 @Service class StockHandlerImpl( private val stockService: StockService ) : StockHandler { private val logger = KotlinLogging.logger {} private companion object { const val DEFAULT_SIZE = 10 const val NAME_PARAM = "name" } override suspend fun findAll(req: ServerRequest): ServerResponse { logger.debug { "Processing find all request: $req" } val stocks = stockService.getAll(DEFAULT_SIZE) return ServerResponse.ok() .contentType(MediaType.APPLICATION_JSON) .body(stocks, StockDto::class.java) .awaitSingle() } override suspend fun find(req: ServerRequest): ServerResponse { logger.debug { "Processing find all request: $req" } val name = req.queryParam(NAME_PARAM) return if (name.isEmpty) { ServerResponse.badRequest().buildAndAwait() } else { val stocks = stockService.find(name.get()) ServerResponse.ok() .contentType(MediaType.APPLICATION_JSON) .body(stocks, StockDto::class.java) .awaitSingle() } } override suspend fun save(req: ServerRequest): ServerResponse { logger.debug { "Processing save request: $req" } val stockDto = req.awaitBodyOrNull(StockDto::class) return stockDto?.let { dto -> stockService.save(dto) ServerResponse .ok() .contentType(MediaType.APPLICATION_JSON) .bodyValue(dto) .awaitSingle() } ?: ServerResponse.badRequest().buildAndAwait() } }


完全なコードはここにあります: GitHub


docker ファイルを作成します。

 FROM openjdk:20-jdk-slim VOLUME /tmp COPY build/libs/*.jar app.jar ENTRYPOINT ["java", "-Dspring.profiles.active=stg", "-jar", "/app.jar"]


次に、Docker イメージを構築して調整します 🤤

 docker build -t ere/stock-service . docker run -p 8085:8085 ere/stock-service


ただし、今のところは、すべてを Docker コンテナーを通じて実行するという考えに固執し、サービスを Docker Compose セットアップに移行することをお勧めします。

 version: '3.1' services: db: image: postgres container_name: postgres-stocks ports: - "5432:5432" environment: POSTGRES_PASSWORD: postgres adminer: image: adminer ports: - "8080:8080" stock-service: image: ere/stock-service container_name: stock-service ports: - "8085:8085" depends_on: - db 



OK、OK、テストはどこですか?

今後: テストをどのように進めればよいでしょうか?具体的には、最近開発したサービスに対して適度な負荷テストを開始するにはどうすればよいでしょうか?テスト ソリューションはインストールが簡単で、ユーザーフレンドリーであることが不可欠です。


時間の制約を考えると、広範なドキュメントや記事を詳しく調べることは現実的な選択肢ではありません。幸いなことに、実行可能な代替手段があります - Yandex Tank を入力してください。タンクはテスト用の強力な機器であり、以下の重要な統合が行われています。 Jメーター, ですが、この記事では簡単なツールとして使用します。


ドキュメント: https://yandextank.readthedocs.org/en/latest/


テスト用のフォルダーを作成することから始めましょう。構成ファイルとその他の重要なファイル (幸いなことに、そのうちの 2 つだけ) を配置したら、準備は完了です。

私たちのサービスでは、「get-all」メソッドと「save」メソッドをテストする必要があります。 find メソッドの最初の構成。

 phantom: address: localhost port: "8085" load_profile: load_type: rps schedule: line(100, 250, 30s) writelog: all ssl: false connection_test: true uris: - /api/v1/stocks overload: enabled: false telegraf: enabled: false autostop: autostop: - time(1s,10s) # if request average > 1s - http(5xx,100%,1s) # if 500 errors > 1s - http(4xx,25%,10s) # if 400 > 25% - net(xx,25,10) # if amount of non-zero net-codes in every second of last 10s period is more than 25


構成のための主要な設定:

  • アドレスとポート: アプリケーションと同じ。


  • ロード テスト プロファイル (load_profile): 1 秒あたり 100 リクエストから 30 秒の制限付き 250 リクエストまでの範囲の「lined」タイプを使用します。


  • URI: テストする URL のリスト。


  • 自動停止パターン: サービスがすでにダウンしている場合にストレス テストを行う必要はありません。 =)


bash スクリプト (tank sh) をコピーして貼り付けます。

 docker run \ -v $(pwd):/var/loadtest \ --net="host" \ -it yandex/yandex-tank


そして走れ!


その結果、何が見えるでしょうか? Yandex Tank は、テスト中に価値があると判断したすべてを記録します。 99 パーセンタイルや 1 秒あたりのリクエスト数 (rps) などのメトリクスを観察できます。


ターミナル?本当に?


それで、私たちは今ターミナルに行き詰まっていますか? GUIが欲しい!心配しないでください、Yandex Tank にはそれに対する解決策もあります。オーバーロード プラグインの 1 つを利用できます。追加方法の例を次に示します。

 overload: enabled: true package: yandextank.plugins.DataUploader job_name: "save docs" token_file: "env/token.txt"


トークンを追加する必要があります。ここにアクセスして、GitHub でロジックを作成するだけです: https://overload.yandex.net

コード


GET リクエストの処理は簡単ですが、POST の場合はどうでしょうか?リクエストをどのように構成すればよいでしょうか?問題は、リクエストを単にタンクに投げ込むことはできないということです。そのパターンを作成する必要があります。これらのパターンは何ですか?それは簡単です。小さなスクリプトを作成する必要があります。これをドキュメントから取得し、ニーズに合わせて少し調整することができます。


そして、独自の本文とヘッダーを追加する必要があります。

 #!/usr/bin/env python3 # -*- coding: utf-8 -*- import sys import json # http request with entity body template req_template_w_entity_body = ( "%s %s HTTP/1.1\r\n" "%s\r\n" "Content-Length: %d\r\n" "\r\n" "%s\r\n" ) # phantom ammo template ammo_template = ( "%d %s\n" "%s" ) method = "POST" case = "" headers = "Host: test.com\r\n" + \ "User-Agent: tank\r\n" + \ "Accept: */*\r\n" + \ "Connection: Close\r\n" def make_ammo(method, url, headers, case, body): """ makes phantom ammo """ req = req_template_w_entity_body % (method, url, headers, len(body), body) return ammo_template % (len(req), case, req) def generate_json(): body = { "name": "content", "price": 1, "description": "description" } url = "/api/v1/stock" h = headers + "Content-type: application/json" s1 = json.dumps(body) ammo = make_ammo(method, url, h, case, s1) sys.stdout.write(ammo) f2 = open("ammo/ammo-json.txt", "w") f2.write(ammo) if __name__ == "__main__": generate_json()


結果:

 212 POST /api/v1/stock HTTP/1.1 Host: test.com User-Agent: tank Accept: */* Connection: Close Content-type: application/json Content-Length: 61 {"name": "content", "price": 1, "description": "description"}


それでおしまい!スクリプトを実行するだけで、ammo-json.txt が作成されます。新しいパラメータを config に設定し、URL を削除するだけです。

 phantom: address: localhost:9001 ammo_type: phantom ammofile: ammo-json.txt


そしてもう一度実行してください!

GRPC をテストする時が来ました!

まだ?


HTTP メソッドの読み込みについて理解したので、GRPC のシナリオを考えるのは自然なことです。戦車のシンプルさに似た、GRPC 用の同様にアクセス可能なツールがあるのは幸運でしょうか?答えは肯定的です。 「ghz」についてご紹介します。ちょっと見てください:


しかし、その前に、優れたテスト サービスとして Go と GRPC を使用して小さなサービスを作成する必要があります。


小さな proto ファイルを準備します。

 syntax = "proto3"; option go_package = "stock-grpc-service/stocks"; package stocks; service StocksService { rpc Save(SaveRequest) returns (SaveResponse) {} rpc Find(FindRequest) returns (FindResponse) {} } message SaveRequest { Stock stock = 1; } message SaveResponse { string code = 1; } message Stock { string name = 1; float price = 2; string description = 3; } message FindRequest { enum Type { INVALID = 0; BY_NAME = 1; } message ByName { string name = 1; } Type type = 1; oneof body { ByName by_name = 2; } } message FindResponse { Stock stock = 1; }


そしてそれを生成してください! (また、 protocをインストールする必要があります)

 protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative stocks.proto


私たちの結果:
grpc ファイルはここにあります!

コーディングタイム!

次のステップ: できるだけ早くサービスを作成します。

私たちにとっては簡単です!


  1. dto (db レイヤーのストック エンティティ) を作成します。

     package models // Stock – base dto type Stock struct { ID *int64 `json:"Id"` Price float32 `json:"Price"` Name string `json:"Name"` Description string `json:"Description"` }


  2. サーバーの実装

    // Server is used to implement stocks.UnimplementedStocksServiceServer. type Server struct { pb.UnimplementedStocksServiceServer stockUC stock.UseCase } // NewStockGRPCService stock gRPC service constructor func NewStockGRPCService(emailUC stock.UseCase) *Server { return &Server{stockUC: emailUC} } func (e *Server) Save(ctx context.Context, request *stocks.SaveRequest) (*stocks.SaveResponse, error) { model := request.Stock stockDto := &models.Stock{ ID: nil, Price: model.Price, Name: model.Name, Description: model.Description, } err := e.stockUC.Create(ctx, stockDto) if err != nil { return nil, err } return &stocks.SaveResponse{Code: "ok"}, nil } func (e *Server) Find(ctx context.Context, request *stocks.FindRequest) (*stocks.FindResponse, error) { code := request.GetByName().GetName() model, err := e.stockUC.GetByID(ctx, code) if err != nil { return nil, err } response := &stocks.FindResponse{Stock: &stocks.Stock{ Name: model.Name, Price: model.Price, Description: model.Description, }} return response, nil }


完全なコードはここにあります:クリックしてください!

試して!

  1. brew で GHz をインストールします (通常どおり): リンク


  2. 簡単な例を確認してみましょう: リンク


ここで、少し変更する必要があります。

  1. proto ファイルのあるフォルダーに移動します。


  2. メソッドを追加: Stocks.StocksService.Save


  3. 単純な本文を追加します: '{"株": { "名前":"APPL", "価格": "1.3", "説明": "リンゴ株"} }'。


  4. 10の接続が20 goroutine ワーカー間で共有されます。 2のゴルーチンの各ペアは 1 つの接続を共有します。


  5. サービスのポートを設定する


そして結果:

 cd .. && cd stock-grpc-service/proto ghz --insecure \ --proto ./stocks.proto \ --call stocks.StocksService.Save \ -d '{"stock": { "name":"APPL", "price": "1.3", "description": "apple stocks"} }' \ -n 2000 \ -c 20 \ --connections=10 \ 0.0.0.0:5007


それを実行します!

 Summary: Count: 2000 Total: 995.93 ms Slowest: 30.27 ms Fastest: 3.11 ms Average: 9.19 ms Requests/sec: 2008.16 Response time histogram: 3.111 [1] | 5.827 [229] |∎∎∎∎∎∎∎∎∎∎∎ 8.542 [840] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ 11.258 [548] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ 13.973 [190] |∎∎∎∎∎∎∎∎∎ 16.689 [93] |∎∎∎∎ 19.405 [33] |∎∎ 22.120 [29] |∎ 24.836 [26] |∎ 27.551 [6] | 30.267 [5] | Latency distribution: 10 % in 5.68 ms 25 % in 6.67 ms 50 % in 8.27 ms 75 % in 10.49 ms 90 % in 13.88 ms 95 % in 16.64 ms 99 % in 24.54 ms Status code distribution: [OK] 2000 responses


それで、ターミナル内のすべてをもう一度見つめてください。いいえ、ghz を使用してもレポートを生成できますが、Yandex とは異なり、レポートはローカルに生成され、ブラウザーで開くことができます。それを設定するだけです:

 ghz --insecure -O html -o reports_find.html \ ...

-O + html → 出力形式

-o ファイル名


結果はウェブページとして表示されます:D


結論

:D


要約すると、1 秒あたり 100 以上のリクエストの負荷を処理するサービスの能力を迅速に評価する必要がある場合、または潜在的な弱点を特定する必要がある場合、チームが関与する複雑なプロセスを開始したり、AQA に支援を求めたり、インフラストラクチャ チームに頼ったりする必要はありません。


多くの場合、開発者は小規模な負荷テストを実行できる高性能のラップトップやコンピューターを持っています。それでは、時間を節約して、ぜひ試してみてください。


この短い記事が有益であると感じていただけたと思います。

読むことをお勧めする貴重なドキュメント:

Yandex Tank: ドキュメントリンク

Yandex Tank GitHub: GitHub リンク

Yandex タンク設定: リンク

ghz公式ページ:リンク

GHz 設定:リンク
GHz 構成: リンク


フォースがあなたとともにありますように!

改めて感謝します。そして幸運を祈ります! 🍀🕵🏻