はじめに
LambdaからRDSに接続する時に、Lambdaでは全てのプロセスが独立してしまうので、connectionも使いまわすことが出来ません。
このため、Lambdaに対する同時接続数を増やしていくと、RDS側でconnection数が上限を超えた時にエラーになります。
※ただ、これはLambdaに限らず、Connection Poolingが出来ないシステムでは同じです。
このことにより、Lambda+RDSはアンチパターンであるという見方をすることがあるようです。
結論
- Lambdaを利用してもRDSのmax connection設定"だけ"が原因でスループットが実用に耐えなくなるということはなさそう。(即アンチパターンというわけではさそう)
- [追記]RDSがSingleAZの場合、(試験した範囲では)インスタンスタイプを変えることでスループットをスケールすることができる。
- [追記]RDSがMultiAZの場合、(試験した範囲では)インスタンスタイプに関わらず書き込みスループットは700rps前後を上限に止まってしまう。
- Lambdaでは外部への通信が発生するケースにおいて、レイテンシを一定時間以下に抑えることは難しそうだ。
RDS for MySQLのmax-connectionについて
RDS for MySQLでは、インスタンスの搭載メモリ毎にmax-connection数が決定されています。
そのため、このconnection数を超えると接続時に接続エラーが発生します。
これは、DB Parameter Groupsのmax_connectionsを確認するとわかるのですが、デフォルトでは、以下の数値となっています。
max_connections: {DBInstanceClassMemory/12582880}
インスタンスタイプ | 搭載メモリ | max_connections |
---|---|---|
db.t2.micro | 1 GB | 85 |
db.t2.small | 2 GB | 170 |
db.t2.midium | 4 GB | 341 |
db.t2.large | 8 GB | 682 |
db.m4.large | 8 GB | 682 |
db.m4.xlarge | 16 GB | 1365 |
db.m4.2xlarge | 32 GB | 2370 |
db.m4.4xlarge | 64 GB | 5461 |
ただし、アプリケーション側の使い方の問題で、メモリ使用量は比較的小さいにも関わらず、同時接続数をあげたいケースには、この制限を変更することが出来ます。(※Apacheの子プロセス数分のconnection poolingを行っている場合など)
max_connections: {DBInstanceClassMemory*2/12582880}
上げすぎるとメモリ不足でエラーとなりますので、落ちない程度に適宜調整して下さい。
Lambda + RDSの構成で負荷試験実施
負荷試験対象API
以下の機能を持つLambda function + API Gatewayを作成しました。
- 外部APIコール用のtokenをgetの引数として取得
- 外部のAPIにhttpでのコールを実施して情報取得
- RDSに上記の結果を格納
インフラ構成
種別 | タイプ |
---|---|
Jmeter | t2.large |
RDS | t2.micro |
試験実施結果
Jmeter 攻撃スレッド数 | Average | 95% Line | Throughput | RDS CPU負荷 |
---|---|---|---|---|
50 | 335 msec | 394 msec | 146 rps | 12% |
100 | 352 msec | 416 msec | 276 rps | 14% |
攻撃スレッド数が50スレッドの段階で既に335msecと比較的応答が遅いですが、攻撃スレッド数を2倍にした時にレイテンシがほとんど低下せず、スループットがほぼ2倍にきちんとスケールしています。
RDSのCPUにもまだまだ余裕はありますし、API Gatewayには、デフォルトで1000rpsでスループットの上限がかかってしまいますので、まずはそこまでは上げることが出来るか試してみましょう。
Jmeter 攻撃スレッド数 | Average | 95% Line | Throughput | RDS CPU負荷 |
---|---|---|---|---|
200 | N/A | N/A | N/A | N/A |
と思ったのですが、いきなりエラーで落ちてしまいました。
デフォルトでは、API Gatewayの制限ではなく、Lambdaの同時実行数制限の100同時起動までという制約に引っかかってしまうようです。
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/limits.html
現在、こちらの上限緩和申請中ですが、「担当部署での確認に数日のお時間を要することがございます。」との事なので続きは気長に待ちたいところです。
応答が遅い原因の調査
スループットとしてはJmeterの攻撃スレッド数を増やすことで上げることはできそうですが、実際問題として、このAPIの応答は非常に遅いことはかわりがありません。それでAPIに少し細工をして、実行結果に実行時の区分ごとの実行時間を含めるようにしました。
{
"Message": "***",
~中略~
"time": {
"prepare": "0msec",
"execute external api": "141msec",
"db connection": "18msec",
"db insert": "121msec"
}
}
npm requestパッケージを利用して外部APIの実行を行っているのですが、その部分だけで実行時間の半分を占めているようです。
実は、この外部APIの実行部分は外部で静的なファイルを返答するのみとしているため、外部への通信の確立のみでかなりのオーバーヘッドはありそうです。
追試
Lambdaの同時実行数制限を1000までに解除してもらったので、追試を行いました。
リファクタリングにより、上記とは少しアプリケーションを変えたため、直接の数値の比較は出来ないことに注意して下さい。
SingleAZ環境における試験
まずは、RDSをSingleAZとして立てて、攻撃スレッド数を上げていくことでスループットを計測していきました。
途中で挙動が不安定になった時点や、レイテンシが遅くなりすぎた時点でインスタンスタイプのスケールアップをして再計測しています。
RDS | 攻撃スレッド | Average (msec) |
95% Line (msec) |
Throughput (rps) |
特記事項 |
---|---|---|---|---|---|
db.t2.micro | 50 | 218 | 281 | 205 | |
db.t2.micro | 100 | 326 | 330 | 282 | 途中からconnectionが不安定 |
db.t2.micro | 200 | 565 | 717 | 331 | 途中からconnectionが不安定 |
db.t2.small | 50 | 218 | 280 | 207 | |
db.t2.small | 100 | 226 | 288 | 379 | |
db.t2.small | 200 | 331 | 366 | 547 | 途中からconnectionが不安定 |
db.t2.medium | 50 | 210 | 271 | 215 | |
db.t2.medium | 100 | 211 | 272 | 429 | |
db.t2.medium | 200 | 240 | 319 | 745 | |
db.t2.medium | 250 | 252 | 366 | 878 | |
db.t2.medium | 300 | N/A | N/A | N/A | 1000rpsまでのスロットリング |
db.t2.large | 50 | 209 | 268 | 226 | |
db.t2.large | 100 | 214 | 275 | 443 | |
db.t2.large | 200 | 229 | 307 | 812 | |
db.t2.large | 250 | N/A | N/A | N/A | 1000rpsまでのスロットリング |
SingleAZ環境においては、インスタンスタイプを選べばスロットリングによる制限の1000rps程度の応答をするAPIは作ることができそうです。
MultiAZ環境における試験
通常、サービスに利用するDBサーバは冗長化のため、MultiAZオプションを利用します。しかし、このMultiAZオプションを利用すると、ゾーン(データセンター)間でのデータの同期を待つことになるため、Write性能がインスタンスのCPUやメモリではなく、ゾーン間のネットワークレイテンシにより制限されてしまう現象が発生することが有ります。
具体的に言うと、より高価なインスタンスを利用しても書き込み性能が増えない可能性があるということです。
実際のサービスを想定して、MultiAZでの負荷試験を行ないます。
RDS | 攻撃スレッド | Average (msec) |
95% Line (msec) |
Throughput (rps) |
特記事項 |
---|---|---|---|---|---|
db.t2.medium | 50 | 221 | 281 | 196 | |
db.t2.medium | 100 | 261 | 349 | 350 | |
db.t2.medium | 200 | 303 | 507 | 592 | |
db.t2.medium | 300 | 392 | 899 | 676 | 途中からconnectionが不安定 |
db.t2.medium | 400 | 508 | 1688 | 600 | 途中からconnectionが不安定 |
db.t2.large | 50 | 249 | 310 | 182 | |
db.t2.large | 100 | 248 | 311 | 367 | |
db.t2.large | 200 | 269 | 351 | 662 | ※ピーク時で720rps程度 |
db.t2.large | 300 | 371 | 805 | 722 | 途中からconnectionが不安定 |
db.t2.large | 400 | 590 | 2282 | 643 | 途中からconnectionが不安定 |
db.m4.xlarge | 50 | 229 | 290 | 198 | |
db.m4.xlarge | 100 | 236 | 303 | 387 | |
db.m4.xlarge | 200 | 267 | 348 | 658 | ※ピーク時で720rps程度 |
db.m4.xlarge | 300 | 376 | 833 | 717 | 途中からconnectionが不安定 |
db.m4.xlarge | 400 | 520 | 1975 | 618 | 途中からconnectionが不安定 |
SingleAZの場合においてdb.t2.small以上のインスタンスであればスロットリングされるまでの1000rpsまでのスループットが出せたにも関わらず、MultiAZでは、インスタンスタイプを変更しても700rps付近を限界にスループットが上がらないことがわかりました。
念のため、Auroraのdb.t2.mediumインスタンスでも同じ試験をしたのですが、若干数値は良いもののほぼ同じ結果となってしまいました。
※このSingleAZとMultiAZの違いは、Lambdaを利用している事が原因とは限らないことには注意が必要です。