Goadを使った負荷試験とパフォーマンス分析手法について

この記事は[『ドワンゴ AdventCalendar 2017』](https://qiita.com/advent-calendar/2017/dwango)の14日目の記事です。

## はじめに
アプリケーション開発において、近年ではQA(Quality Assurance)という品質やパフォーマンスを保証することに対してエンジニアリングがどう対応していくかといったことに注目が集まりつつあります。

例えばGoogleでは既存の品質保証プロセスで存在していた手動プロセスを自動化するという試みが行われており[1]、MercariではQA-SETチームという職務横断型プロジェクトの発足といった取り組みがなされているようです[2]。

今回はこのようなQAの確認項目の一つである負荷試験とパフォーマンス分析についてお話できればと思います。

## Goadの紹介
負荷試験(LoadTest)は、構築したシステムがパフォーマンス要件を満たしているかの確認、ボトルネックの発見、パフォーマンス最大化のために行われる測定プロセスです[3]。

負荷試験に使われるツールとしてはApache JMeterなどが有名でしょうか。
今回はJMeterという選択肢もあったのですが、自分たちでそこそこ高性能なサーバを何台か用意しなければならないことや、実際にアクセスがスパイクした場合と環境を似せるためにGoadを採用してみることにしました。

[Goad](https://github.com/goadapp/goad)は、AWS LambdaとSQSを使ったオープンソースの負荷試験ツールで、最大で同時10万リクエストまで行うことができます。実際に同時接続数を数百に設定し、数万リクエスト程度の負荷試験を行ったところ問題なく動作している感じでした。Lambdaの料金も数十万リクエスト程度ではほとんどゼロなので非常にコストパフォーマンスが高いツールだと思います。

<img width="1143" alt="スクリーンショット 2017-12-14 1.28.47.png" src="https://qiita-image-store.s3.amazonaws.com/0/51540/5744d670-374e-e21b-ff30-5839635100c4.png">
(図1: [https://goad.io/](https://goad.io/) から引用)

### インストール
バイナリを[こちら](https://goad.io/)からDownloadして適当な場所に展開する必要があります。
MaxOS版のリンクがなぜか404なので、MacOSを使っている場合は[githubのreleases](https://github.com/goadapp/goad/releases)からダウンロードしないといけません。

### 使い方
基本的にはReadmeの通りに実行するだけですが、日本語の記事だと

[Lambda を利用した分散 Web 負荷テストツール Goad を使ってみた](https://qiita.com/quickguard/items/995d8e28d53e569bab73)
こちらの記事が参考になるかと思います。

ただ現在の最新版(v2.0.4)を実行すると、Goadの内部で使っているSQSのFIFOキューがまだ東京リージョンに対応していないのでエラーが発生します。

**そのため東京リージョンでGoadを動かしたい場合v2.0.3を使用する必要があります。**

最新版を使うとハマるので要注意です。

### サービス公開前や開発環境でテストを行う
実際にGoadを使って負荷試験を行う際に問題となるのが、Lambdaを使ってrequestが行われる都合上アクセス元のIPアドレスを固定することができないことです。

公開前のサービスや開発環境の場合、Firewallでアクセス制限をかけていることがほとんどであるため、Lambdaのような非固定的なPublicIPからのアクセスをどう扱うかは難しいところです。

とはいえLambdaが使用するEC2インスタンスに使われるIPアドレス帯はリージョンごとに決まっているため([https://ip-ranges.amazonaws.com/ip-ranges.json](https://ip-ranges.amazonaws.com/ip-ranges.json))、今回は上記のリンクからAWS東京リージョンのEC2インスタンスに割り当てられているCIDRを取得し、このCIDRからのみアクセスを許可するSecurityGroupを作成、負荷試験時のみアクセスを受け付けるようにして対応しました。

上記のSecurityGroupを作成するサンプルスクリプトをベタ書きですがNode.jsで作りましたのでご参考まで。

```javascript

const AWS = require('aws-sdk')
const request = require('request-promise');

AWS.config.update({ region: 'ap-northeast-1' });
const ec2 = new AWS.EC2({ apiVersion: '2016-11-15' });

// security groupを作成するVPCのID
const vpc = "";

const paramsSecurityGroup = {
  Description: 'security group for load test.',
  GroupName: 'aws-tokyo-region-ec2-http-inbound',
  VpcId: vpc
};

let paramsIngress = {
  GroupId: "",
  IpPermissions: []
};

ec2.createSecurityGroup(paramsSecurityGroup).promise()
  .then((data) => {
    const SecurityGroupId = data.GroupId;
    console.log("Security Group Created", SecurityGroupId);
    paramsIngress.GroupId = SecurityGroupId;

    const options = {
      url: 'https://ip-ranges.amazonaws.com/ip-ranges.json',
      method: 'GET',
      headers: { 'Content-Type': 'application/json' }
    }

    return request(options);

  }).then((body) => {
    const prefixes = JSON.parse(body).prefixes;

    // 東京リージョンのEC2インスタンスに使われるip-rangeを抜き出してparamsにset
    prefixes.forEach((element) => {
      if (element.service === "EC2" && element.region === "ap-northeast-1") {
        paramsIngress.IpPermissions.push({
          IpProtocol: "tcp",
          FromPort: 80,
          ToPort: 80,
          IpRanges: [{ "CidrIp": element.ip_prefix }]
        })
      }
    });
    return ec2.authorizeSecurityGroupIngress(paramsIngress).promise();

  }).then(() => {
    console.log("Ingress Successfully Set.");
  }).catch((err) => {
    console.log("Error", err);
  });

```

## USEメソッド
負荷試験を行う環境が整ったところでさあ実際にやってみようと思うわけですが、次に負荷をかけてみるのはいいけどそもそもシステムパフォーマンスの分析ってどうやればいいの？とか、そもそも何の指標をみればいいの？という疑問が湧きます。

ここで使えるのがUSEメソッドです。

USEメソッドは、システムの個々のリソースを使用率(Utilization)、飽和(Saturation)、エラー(Errors)の3つの観点から分析する、パフォーマンス分析手法の一つです。Brenda Gregg氏が提唱する方法であり、彼が著者である詳解システムパフォーマンスという本にも出てきます。

詳細はGregg氏のこちらの[ブログエントリ](http://www.brendangregg.com/usemethod.html)にもまとまっています。

### 具体的に何をみるのか
USEメソッドはCPUやメモリ、ネットワークといったシステムを構成する全てのリソースに対して以下の3つの観点から分析を行います。

- 使用率.. ある時間内にどれだけ対象のリソースが使われていたか
- 飽和... 対象リソースにおいて処理できていない要求がどれだけあったか
- エラー.. エラーイベントの回数

例えばCPUやメモリの場合は使用率は仮想環境では `vmstat` などのコマンドで取得できるリソース使用率になりますし、飽和はCPUの場合はLoadAverage、メモリの場合はswap-outの値になります。

使用率は馴染みがありますがなぜ飽和をチェックしなければならないのでしょうか。

これは使用率の定義が、ある時間内で定義される平均の側面を持っているからです。例えば一分あたりの使用率が低くても、ある特定の瞬間の使用率がすごく高く、バーストして処理しきれない場合というのは想定できます。例えばアクセスがスパイクするような場合がありますね。
そのためパフォーマンス検証はリソースが飽和して処理ができていない要求は存在しなかったか、要はキューイングされて処理待ちになっているかどうかをチェックする必要があります。


### USEメソッドの分析手順

USEメソッドは各リソースに対して以下の観点からパフォーマンスの分析を行います。

1. Errorが発生していないか
2. 利用率は高いか
3. 飽和はどの程度あるか

flow chartで表すと以下になります

<img width="449" alt="スクリーンショット 2017-12-13 22.51.39.png" src="https://qiita-image-store.s3.amazonaws.com/0/51540/82db8a2c-7d8c-634a-9dfa-e8ec42f8a393.png">
(図2: [Gregg氏の記事から引用](http://www.brendangregg.com/usemethod.html))


Greggの記事にもありますが、環境によってはリソースの指標を取得することが難しかったりするものもあるため(クラウド環境のCPUエラーなど)そのあたりは臨機応変に取捨選択します。
自分は負荷試験時に例外などのアプリケーションエラーが発生していた場合はまずそこから分析し、その後でCPUやメモリ、ネットワークといったリソースの使用率と飽和を分析していくというやり方にしています。

### USEメソッドの利点
さて、なぜ自分がUSEメソッドをここで紹介したかというと、このメソッドはパフォーマンスを分析するための極めてシンプルで見通しの良いロールモデルだと思うからです。
Gregg氏曰くUSEメソッドは5%の努力で約80%の問題を解決することができる手法とのことですが、パフォーマンス分析時に調べるべき指標や内容が上記の手順のようにはっきりとしているため効率良く解析を行うことができました。


## まとめと所感
QAの一環として、負荷試験ツールであるGoadとUSEメソッドを使ったパフォーマンス分析手法を紹介しました。QAやSREというとまだまだ概念としては新しく、なおかつ対象の範囲が広いためどうしても内容が抽象的になってしまいます。

しかし実際のタスクをこなしていくと、まだまだ手動で行われている部分が多く、またこれまでの慣例や経験則から導かれたヒューリスティックな設定項目があったりと様々な効率化ポイントがあることがわかります。

ソフトウェア開発というと要件定義や設計に焦点があたりがちではありますが、ユーザ数が増えるにつれリリース時に要求されるクオリティのハードルが高まり、必然的にQAのマネジメントが必要になってきます。
そのため今後QAの効率化と自動化が、機敏な開発を継続していくためにますます重要な要素になると思っています。

今回の記事がさらなる開発の効率化に少しでも貢献できれば幸いです。


## 参考
[1] From QA to Engineering Productivity, https://testing.googleblog.com/2016/03/from-qa-to-engineering-productivity.html
[2] メルカリQA-SETチームが考えているQAやテストの未来のはなし, http://tech.mercari.com/entry/2017/08/18/100138
[3] Load testing, https://en.wikipedia.org/wiki/Load_testing