AWS API GateWay (Labmda)に負荷テスト（ラッシュテスト）を実施したときのTips

# 概要
サーバレスアーキテクチャ（今回でいうとサーバレスAPI）を構築した際、負荷テスト（ラッシュテスト）を実施した際にいろいろ試行錯誤した時のTipsです。

# アーキテクチャ構成
よくある構成。APIGW->Lambda->DynamoDBと処理しているAPIがほとんどです。

![arch.png](https://qiita-image-store.s3.amazonaws.com/0/123578/91142415-b285-8e4c-3367-b09897890c4d.png)
　

# 実施内容
## どうやってラッシュテストしようか。
下記のパターンがさっと思いつきます。
  1. Device Farmを使ってテストケース投入してやってみる。
  2. JMeterとかを使ってシナリオテストっぽくしてみる。
  3. APIエンドポイントに対して負荷をかけてみる。

JMeterを使おうとも考えましたが、シンプルに想定RPSを耐え切るかを見たかったので、３を選択しました。

## ラッシュテスト 前準備
[結論](#結果)から述べると、ApacheBenchもvegetaもうまくいかなかったので、wrk使います。
wrkでEC2を大量に立ち上げ、小さい時間間隔でリクエストを投げ続け、求めるRPSが出るまでチューニングを繰り返しました。


### シンプルにApacheBench(ab)テストをしてみる
負荷テストといえばApacheBench、ABテストが一番に思い浮かぶのでEC2をさくっと立てて実施して見ます。構成的には下記のような感じですね。

![ab.png](https://qiita-image-store.s3.amazonaws.com/0/123578/faaed805-373d-75fa-0164-968ced45ebbe.png)


#### インストール
ApacheBenchインストールは、[【初心者向け】ApacheBench入門](http://dev.classmethod.jp/tool/ab-tutorial/)を参考にしてください。

#### 実行
API Gatewayのエンドポイント（下記図のURL)に対してabコマンドを実行します。
<img width="960" alt="apiurl.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/123578/8936d065-ef65-2ce5-e7d5-3a5b4c249521.jpeg">

```
$  [ec2-user@test ~]$ ab -n 100 -c 10　https://XXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/prod/YYYYYYYYYYY

```

#### 結果
ABコマンド実行結果、大量のSSLハンドシェイクエラーが発生しました。。。
![ab_error.png](https://qiita-image-store.s3.amazonaws.com/0/123578/e4e0a13d-b748-5500-0cf3-71eef6d63939.png)

なお、同じ事象に見舞われている記事がHITしました。どうやらAPIGatewayはSSL/TLSハンドシェイクがTLSv1を強要するようで、sslv3ではハンドシェイクできないのが原因の様子です。風の噂でjavaのバージョンを8にあげたら通るとかいう話もあったので試してみましたが、一度だけなぜか通り、それ以降は同様のSSLエラーに見舞われました。

* [ AWS Developer Forums: Benchmark URL (with ab, wrk, or siege)](https://forums.aws.amazon.com/thread.jspa?threadID=193615)
* [Amazon API Gateway 経由でちょっとしたアクセスを流してみた](http://uchimanajet7.hatenablog.com/entry/2015/07/22/130342)
* [Amazon API Gatewayのパフォーマンスオプションを理解する | Developers.IO](http://dev.classmethod.jp/cloud/api-gateway-performance/)

### vegetaというツールを使ってみる
[Amazon API Gateway 経由でちょっとしたアクセスを流してみた](http://uchimanajet7.hatenablog.com/entry/2015/07/22/130342)で紹介されていた、
Go言語製ツールである[vegeta](https://github.com/tsenart/vegeta)を使ってみました。

#### 結果
vegetaは、あのべジータですｗｗ最強のサイヤ人の王子です！
これならばAPIエンドポイントに猛攻をかけてくれるに違いない！！とおもいながら試しましたが、同様にSSLハンドシェイクエラーで死にました。。。

### wrkというツールを使ってみる
[wrk](https://github.com/wg/wrk)という、Modern HTTP benchmarking toolを試してみました。

#### インストール
C言語で書かれているのでgithubにあがっているMakefileをつかってmakeします。make -j4ぐらいでやるとすぐ終わります。

```
$ sudo yum -y git
$ git clone https://github.com/wg/wrk.git
$ cd wrk
$ make -j4
```

#### 実行
wrkコマンドをたたくとヘルプが表示されるので、必要なオプションを設定して対象のAPIエンドポイントに対してwrkコマンドを実行します。

```
./wrk -c 10 -t 2 -d 5s --latency "https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/prod/YYYYYYYY"
```
上記コマンドでは、2スレッドで10コネクションを確立し、5秒間リクエストを送り続けると意味になります。--latencyオプションをつけることで50-90パーセンタイルごとのレイテンシーを結果に表示することが出来ます。これはよくシステム開発の性能テストでは80パーセンタイルが性能目標を満たしているかを問われるシチュエーションでは非常に有効です。

#### 結果
下記の通り、結果を取得することが出来ます。
![wrk.png](https://qiita-image-store.s3.amazonaws.com/0/123578/1ccbb1e7-cbb4-661e-8a10-449c90a311b3.png)

なお、注意点としては持続時間を長くしたり、大きなコネクション数、スレッド数を指定するとタイムアウトエラーであったり、Non 2xx or 3xx レスポンスが返ってきたりします（下記参照）。短い時間で繰り返しコマンドを実行させることで、つどつどスレッドやコネクションを解放してあげる必要がありそうです。

![wrk_error1.png](https://qiita-image-store.s3.amazonaws.com/0/123578/7e60eeb3-0f9c-1c1c-4a1e-dccb4b6a1648.png)

![wrk_error2.png](https://qiita-image-store.s3.amazonaws.com/0/123578/af32b127-6d5d-c110-b3cc-591e37c139e7.png)


## ラッシュテスト
今回はとりあえず200RPSを目標にパラメータチューニングをしつつ、ラッシュテストを実施しました。

### EC2起動時にwrkコマンドを自動実行
[EC2で起動時やterminate時にシェルを実行する](http://dev.classmethod.jp/cloud/ec2shell/)を参考にし、EC2インスタンスが起動するときに自動でwrkコマンドがうごくような設定をしました。

これにより、AWS CLIなどからEC2インスタンスを大量起動させたときに自動でwrkが走り、ラッシュが開始されるという仕組みになっています。

### API Gatewayの負荷テスト時の注意点
これはAPI Gatewayというより、CloudFront利用時の注意点になるのですが、API Gatewayは内部的にCloudFrontを使っているのでCloudFront利用時の注意点と同様の注意が必要です。

AWS公式ページで[CloudFront の負荷テスト](http://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/load-testing.html)については記載されています。

今回は、下記観点より、AllRegionからのリクエスト発行はしていません。
* API Gatewayのキャッシュは無効に設定
* キャッシュを利用せずラッシュをかける
* 日本からしかアクセスしない

### CloudWatch で実行状況をモニタリング
APIGateway, DynamoDBの様子をモニタリングします。

#### 試行１
下記パラメータで試行してみます。最初、Lambdaのデフォルト上限数で挑みましたが、HTTP429エラーが多発（ようはスロットリング）が発生したので上限緩和申請をしています。

* APIGWの同時実行数（デフォルト：1000rps、バースト時：2000rps）
* Lambdaの同時実行数（デフォルト100．下記試験では600まで上限緩和）、
* DynamoDBのRead Provisioned Capacity（下記試験では読み込みAPIコールのため、Read:200を割り当て）

なお、注意点としては、パラメータチューニングしたとしても予期せぬ事態でAPIGWかLambdaがタイムアウトするとHTTP429エラーを返すため、クライアント側でリトライする機構が本来は必要ですが今回は無視します。
（APIGW－＞Lambda呼び出しは同期呼び出しとなるため、自動リトライやDeadLetterQueueは使えない）

#### 試行１の結果
下記の結果となっています。サイズやスケールがあっておらずすいません。
RPS、DynamoのRead Provisioned Capacisy, throttle、API Gatewayの順です。

* RPSを見ると、最初はどかーんと出ていますが、DynamoDBのスロットリングが発生し出したあたりからRPSが下がり、一定の値で進んでいます。
* DynamoDBを見ると、なぜかProvisioned超えながらConsumed Capacityが走っています。このグラフを見ると完全にDynamoで詰まっていることが分かります。
* API Gatewayの結果を見ても、APIGatewayは1000rpsまで許可しているのでスロットリングは発生していないがDynamo側で詰まり、Lambdaの実行時間が長くなり、LambdaでタイムアウトしているためAPI Gatewayでも一定のエラーが発生しています。

![rps.png](https://qiita-image-store.s3.amazonaws.com/0/123578/a9838f52-77c7-2e13-2bce-efc08af4a689.png)

![dynamo.png](https://qiita-image-store.s3.amazonaws.com/0/123578/9fed780d-6f2b-b65e-ed65-eb3f806bc274.png)

![APIgateway.png](https://qiita-image-store.s3.amazonaws.com/0/123578/538ad1be-9f94-4c09-0584-28480600f9fe.png)


#### 試行２
下記パラメータで試行してみます。最初、Lambdaのデフォルト上限数で挑みましたが、HTTP429エラーが多発（ようはスロットリング）が発生したので上限緩和申請をしています。
過剰な気もしますが、Lambdaの上限数とDynamoから読み込むレコードサイズをもとにキャパシティを600としています。

* APIGWの同時実行数（デフォルト：1000rps、バースト時：2000rps）
* Lambdaの同時実行数（デフォルト100．下記試験では600まで上限緩和）
* DynamoDBのReadProvisioned Capacity　600

#### 試行２の結果
下記の結果となっています。サイズやスケールがあっておらずすいません。
RPS、DynamoのRead Provisioned Capacisy, throttle、API Gatewayの順です。

![rps.png](https://qiita-image-store.s3.amazonaws.com/0/123578/420f34dc-dba8-0944-5976-ac7a1c939e4e.png)

![dynamo.png](https://qiita-image-store.s3.amazonaws.com/0/123578/b6cf4fc3-9bc3-3843-ec5a-7ebee3be07a5.png)

![apigateway.png](https://qiita-image-store.s3.amazonaws.com/0/123578/bb4e05cc-f95c-a852-f0a8-13b08379c153.png)


* リクエストが一気になだれ込むがLambdaの上限設定値あたりでストップします。
* rpsの目標値である200はとりあえずクリアしています。
* Dynamoのキャパシティをあげたので十分さばききれています（Provisioned > Consumedとなっている）。スロットリングも発生していません。
* APIGatewayをみると時々エラーが発生（１、２件）いますが、前回ほどタイムアウトは発生していません。

## まとめ
* サーバレスアーキテクチャ（今回でいうとサーバレスAPI）の負荷テスト（ラッシュテスト）を実施
* オンプレの環境と違い、ツール実行までに時間がかかったがwrkで着地（余裕があればJMeterやガトリングも使いたい）
* フルマネージドということもあり、LambdaやAPI Gatewayはオートスケーリングしてくれる
	* 注意点としてはDynamoのキャパシティであるがマネジメントコンソールからボタン１つでキャパシティ設定できる
	* サーバレスアーキテクチャ的にスロットリングが一定時間発生したらキャパシティを現在より増加させるなどのCloudWatchEventsを設定してオートスケーリングさせるとよい気がする。
* サーバレス楽しいです！