Golangを使ったDB用負荷テストツールの開発

13.4K Views

November 23, 23

スライド概要

Golangを使ったDB(MySQL/PostgreSQL)用の負荷テストツールの作り方について発表します。
DB用の負荷テストツールはあまり多くはないのですが、並列処理を書きやすいGolangを使うことで、シンプルな負荷テストツールを簡単に作れることを紹介します。

profile-image

https://gravatar.com/sgwrdts

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

ダウンロード

関連スライド

各ページのテキスト
1.

Golangを使ったDB用負荷テストツールの開発 Go Conference mini 2023 Winter IN KYOTO Genki Sugawara

2.

自己紹介 Genki Sugawara Kanmu, Inc., SRE github.com/winebarrel twitter.com/sgwr_dts Blog so-wh.at

3.

DBの負荷テストについて

4.

DB用の負荷テストツール(一部) • sysbench • mysqlslap • pgbench • HammerDB • JdbcRunner

5.

DB負荷テストのやり方 • 定型のシナリオを実行する ◦ TPC、各ツールの独自シナリオ • 任意の数クエリを実行する ◦ mysqlslap --query 'SELECT 1' • 任意のシナリオを実行する ◦ HammarDB (TCL)

6.

WebサービスでのDB負荷テスト • 定型のシナリオ ◦ サービスの特性と合わない • 数クエリを実行 ◦ サービスのSQLに対して少ない • 任意のシナリオを実行 ◦ シナリオを書くのが手間

7.

WebサービスでのDB負荷テスト • クエリログをテストのシナリオに使う ◦ サービスのSQLを模倣できる • しかし、クエリのリピーターはあんまりない

8.

DB用負荷テストツールを自作しよう

9.

Goを使ったDB用負荷テストツール • goroutineで並列化しやすい • contextでgoroutineを制御しやすい • channelでgoroutine間のデータの受け渡しがやりやすい、 • select/defaultループ処理に割り込みを入れやすい • 準標準パッケージにレート制限パッケージがある

10.

サンプルツール github.com/winebarrel/qube • MySQL・PostgreSQL対応 • NDJSON形式のSQLをDBに実行

11.

Demo

12.

構成図 Recorder (goroutine) chan []DataPoint Report Start Agent (goroutine) SQL DB Data SQL (NDJSON)

13.

コードの説明

14.

main.go func main() { options := parseArgs() task := qube.NewTask(options) // タスクの実行 report, err := task.Run() if err != nil { log.Fatal(err) } // レポートの出力 report.Print(os.Stdout) }

15.
[beta]
タスクの開始
func (task *Task) Run() (*Report, error) {
  // エージェントの作成
  agents, rec, err := task.makeAgents()
  // ...
  eg, ctx := errgroup.WithContext(context.Background())
  ctx, cancel := context.WithCancel(ctx)
  // タイムアウトの設定
  if task.Time > 0 {
    ctx, cancel = context.WithTimeout(ctx, task.Time)
  }
  // ...
  fire := make(chan struct{})
  for _, v := range agents {
    agent := v
    // errgroupでエージェントを実行
    eg.Go(func() error {
      <-fire
      return agent.Start(ctx)
    })
  }
  // errgroupでエージェントを待つ
  close(fire)
  err = eg.Wait()
}
16.
[beta]
エージェントの生成
func (task *Task) makeAgents() ([]*Agent, *Recorder, error) {
  agents := make([]*Agent, task.Nagents)
  // レコーダーとリミッターを生成
  rec := NewRecorder(task.ID, task.Options)
  limiter := rate.NewLimiter(rate.Limit(task.Rate), 1)
  for i := 0; i < task.Nagents; i++ {
    var err error
    // agentにレコーダーとリミッターを渡す
    agents[i], err = NewAgent(task.ID, i, task.Options, rec, limiter)
    if err != nil {
      return nil, nil, err
    }
  }
  return agents, rec, nil
}
17.
[beta]
エージェント - SQLの実行
func (agent *Agent) Start(ctx context.Context) error {
  for { // 無限ループでクエリ実行
    agent.limiter.Wait(ctx)
    // select/defaultでcontextの割り込み
    // 一定時間ごとのデータ送信
    select {
    case <-ctx.Done():
      break L
    case <-tkrec.C:
      agent.rec.Add(dps)
    default:
    }
    // ファイルからSQLを読み込んで実行
    q, err := agent.data.Next()
    dur, err := agent.execQuery(ctx, q)
  }
}
18.
[beta]
レコーダー - データポイントの収集
func (rec *Recorder) Start() {
  push := func(dps []DataPoint) {
    rec.Lock()
    defer rec.Unlock()
    rec.DataPoints = append(rec.DataPoints, dps...)
  }
  go func() {
    // チャンネルからデータポイントを受信
    for dps := range rec.ch {
      push(dps)
    }
  }()
  // ...
}
func (rec *Recorder) Add(dps []DataPoint) {
  // チャンネルでデータポイントを送信
  rec.ch <- dps
}
19.

mysqlslapとの性能比較 mysqlslap (C) qube (Go) 100 75 50 25 0 1 2 4 8 16 24 32 40 48 56 64 並列数 110000 100000 90000 80000 70000 60000 50000 40000 30000 20000 10000 0 CPU使用率 qps 100 75 50 25 0 1 2 4 8 16 24 32 40 48 56 64 並列数 110000 100000 90000 80000 70000 60000 50000 40000 30000 20000 10000 0 CPU使用率 qps

20.

まとめ • Golangを使うと簡単にDB用の負荷テストツールを作れます • 同じやり方で別のミドルウェアの負荷テストツールも作れると思います • 簡単なので、自作負荷テストツールおすすめです