LoginSignup
3

More than 1 year has passed since last update.

gRPC + Locust + boomerで負荷試験をしてみた

Last updated at Posted at 2021-12-07

はじめに

この記事はミクシィアドベントカレンダー2021 8日目の記事です。

こんにちは。
サービスのリリース前や大きなイベント前には皆さんも負荷試験をしているかと思います。

今回はgRPCを使った新規サービスでboomerというツールを使って負荷試験をしたのですが、なかなか使い心地がよかったのでご紹介したいと思います。

またboomerの選定については先日までアルバイトで来て頂いていた方にメインやって頂いたのですが、非常に助かりました。
この場をお借り致しまして,お礼申し上げます。

boomerとは

LocustのシナリオをGoで書けるWorker Runnerです。
LocustのWorkerとして動作するため、別途Locust Masterは必要になります。
gRPCに特化したツールというわけではありません。

ツールの選定理由

Locust + boomerは以下の理由で採用しました。

  • Locust
    • Web UI がついている
    • 社内で実績/知見がある
    • 情報が多い
    • Workerのスケールがしやすい
  • boomer
    • シナリオがGoで書ける
    • サーバーもGoなのでpb.go含めた既存コードを使いまわせる
    • k8s上でのmulti worker構成も問題なく動作する

使ってみる

それでは実際にgRPCサーバーに対して Locust + boomerで負荷試験を実施してみたいと思います。
今回は同一のマシン上にgRPC Server / Locust Master / boomer(Locust Worker) を立てて実行しています。

バージョン

この記事では以下のバージョンを使っています。

Tool Version
Locust 2.5.0
boomer 1.6.1

※ boomerの1.6.1より前のバージョンだとLocustの1系しか対応していないので注意です

今回使うコード

proto

公式のサンプル とほとんど一緒です。

proto/hello.proto
syntax = "proto3";

option go_package = "pb/";

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

サーバー

ランダムでsleepしてレスポンスを返すUnary RPCを定義したgRPCサーバーです。

cmd/server/main.go
package main

import (
    "context"
    "log"
    "math/rand"
    "net"
    "time"

    "sample/boomer/internal/pb"
    "google.golang.org/grpc"
)

const (
    port = ":50051"
)

type server struct {
    pb.UnimplementedGreeterServer
}

func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
    r := rand.Intn(3)
    time.Sleep(time.Duration(r) * time.Second)

    return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}

func main() {
    lis, err := net.Listen("tcp", port)
    if err != nil {
        log.Fatalf("%+v", err)
    }

    s := grpc.NewServer()
    pb.RegisterGreeterServer(s, &server{})

    if err := s.Serve(lis); err != nil {
        log.Fatalf("%+v", err)
    }
}

シナリオ

SayHelloのRPCを呼び出すだけの単純なシナリオになっています。
gRPCサーバーへのリクエストは特別なことはしておらず、通常のgRPC Clientと同じ方法で呼んでいます。

boomerが行う処理として、RPCの呼び出しに成功した場合はRecordSuccessを、失敗した場合はRecordFailureを呼び出してリクエストの結果を記録しています。

cmd/boomer/main.go
package main

import (
    "context"
    "fmt"
    "github.com/myzhan/boomer"
    "google.golang.org/grpc"
    "log"
    "sample/boomer/internal/pb"
    "time"
)

const (
    target = "localhost:50051"
)

func hello() {
    ctx := context.Background()
    conn, err := grpc.DialContext(ctx, target, grpc.WithInsecure())
    if err != nil {
        log.Fatalf("%+v", err)
    }
    defer conn.Close()

    client := pb.NewGreeterClient(conn)

    // レスポンスタイムを計測するために、リクエストを投げる前の時刻を取得する
    start := time.Now()
    response, err := client.SayHello(ctx, &pb.HelloRequest{Name: "ミクシィ太郎"})

    if err != nil {
        // リクエストに失敗した場合はRecordFailureを呼びます
        boomer.RecordFailure(
            "grpc",     // リクエストの種別
            "SayHello", // rpc名
            time.Since(start).Nanoseconds()/int64(time.Millisecond), // レスポンスタイム
            fmt.Sprintf("failed: %+v", err),                         // エラー理由
        )
    } else {
        // リクエストに成功した場合はRecordSuccessを呼びます
        boomer.RecordSuccess(
            "grpc",     // リクエストの種別
            "SayHello", // rpc名
            time.Since(start).Nanoseconds()/int64(time.Millisecond), // レスポンスタイム
            int64(len(response.String())),                           // レスポンスサイズ
        )
    }
}

func main() {
    task := &boomer.Task{
        Name:   "sample",
        Weight: 10,
        Fn:     hello,
    }

    boomer.Run(task)
}

環境準備

サーバーとシナリオをビルドして実行ファイルを作成しておきます。

$ go build -o server ./cmd/server/main.go
$ go build -o boomer ./cmd/boomer/main.go

サーバーを起動します。

$ ./server

Locust Masterの起動にダミーのシナリオが必要なため、dummy.pyを作成し、Locust Masterを起動します。

$ locust --master -f dummy.py

続いてboomerを起動します。

$ ./boomer

boomerに boomer is connected to master(tcp://127.0.0.1:5557) press Ctrl+c to quit.
Locust Masterに xxxxx/INFO/locust.runners: Client 'xxxx' reported as ready. Currently 1 clients ready to swarm.
のようなログがでていればboomerの起動は成功です。

Let's boomer

それでは準備ができたのでLocustのweb画面にアクセスして負荷をかけてみます。
Number of usersを10、Spawn rateを1にしてStart swarmingをクリックします。
image.png

シナリオに書いた通りSayHelloへのリクエストが記録されました。
SayHelloはランダムでSleepされる処理が入っていますが、レスポンスタイムもそれに合わせて記録されています。
image.png
image.png

今度はエラー時の動作を確認するため、SayHelloからエラーが返されるようにして再度負荷をかけてみます。
SayHelloからエラーが返されているため、Failsのカウントが増加していることが確認できます。
image.png

また、Failuresタブを開くとエラー理由が記録されていることが確認できました。
image.png

まとめ

このようにboomerを使うことでGoでシナリオを書け、gRPCサーバーへの負荷試験を行うことができました。
(もちろん通常のhttpサーバーへの負荷試験もできます)
Pythonを使った場合でも簡単にシナリオは書けますし同様のことはできますが、サーバーでGoを使っている場合は同じ言語でシナリオを書けるのはメリットだと思いますので、是非boomerを使ってみてください。

また今回はboomerの紹介のみとなりましたが、今度はboomerを使って実際に行った負荷試験の内容についてもご紹介できればと思います。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3