LoginSignup
181
144

More than 3 years have passed since last update.

【待望リリース!】もう Lambda×RDS は怖くない!LambdaでRDSプロキシを徹底的に検証してみた 〜全てがサーバレスになる〜

Last updated at Posted at 2019-12-04

本日の reinvent でのリリースで衝撃のアップデートがたくさん出ましたね。EKS on Fargate や SageMaker の大幅アップデートも魅力的ですが Lambda の常識をくつがえす RDS のプロキシ機能が登場しました 🎉

Lambda から RDS に対するアクセスはコネクション数の上限に達してしまうという理由からアンチパターンとされてきました。そのため、RDS をデータストアに選択する場合は ECS や EC2 上にアプリケーションをホストする事が一般的でした。Lambda の接続先 DB に RDS を選べるということはほとんどのWebアプリケーションがサーバレスで実行できるようになるので夢が広がります。

本記事では RDS プロキシを使った Lambda の構成を作ってコネクション数の挙動について検証してみました。
https://aws.amazon.com/blogs/compute/using-amazon-rds-proxy-with-aws-lambda/
※本記事は上記のブログを参考にしています。一部文脈で引用している箇所があります。

image

RDS プロキシは、データベースへの接続プールを維持します。これにより Lambda から RDS データベースへの多数の接続を管理できます。
Lambda 関数は、データベースインスタンスの代わりに RDS プロキシと通信します。スケーリング起動した Lambda 関数によって作成された多くの同時接続を保持するために必要な接続プーリングを処理します。これにより、Lambda アプリケーションは関数呼び出しごとに新しい接続を作成するのではなく、既存の接続を再利用できます。

従来はアイドル接続のクリーンアップと接続プールの管理を処理するコードを用意していたのではないでしょうか。これが不要になります。劇的な進化です。関数コードは、より簡潔でシンプルで、保守が容易になります。

現在はまだプレビュー版ですがこの機能を徹底検証していきましょう。

せっかく検証するのですから従来 ECS などで一般的に使ってたフレームワークを例に上げてみましょう。今回は NestJS を Lambda にデプロイして RDS と接続してみます。

lamba-rds-proxy.png

NestJS を Lambda にデプロイする

Serverless Framework を使用して NestJS アプリケーションを AWS Lambda にデプロイします。
こちらのサンプルソースが参考になりました。ほぼそのまま引用させていただきます。

Lambda がデプロイできたことを確認しておきましょう。

$ sls deploy
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
~~~~~~~~~~~~~~~~~~~~~~~~~~ 省略 ~~~~~~~~~~~~~~~~~~~~~~~~~~
endpoints:
  ANY - https://djpjh5aklf.execute-api.us-east-1.amazonaws.com/dev/
  ANY - https://djpjh5aklf.execute-api.us-east-1.amazonaws.com/dev/{proxy+}
functions:
  index: serverless-nestjs-dev-index
layers:
  None
Serverless: Run the "serverless" command to setup monitoring, troubleshooting and testing.

生成されたエンドポイントにアクセスします。

image.png

これで準備ができました。まずは Hello World! と文字列を返す NestJS アプリケーションを Lambda をデプロイできました。これから MySQL と接続できるアプリケーションを作っていきます。開発過程は省略しますが、以下のリポジトリに完成品をアップロードしておきます。

完成品:https://github.com/daisuke-awaji/serverless-nestjs

参考:Nest(TypeScript)で遊んでみる 〜DB 連携編〜

タスクの CRUD 操作ができるアプリケーションを用意しました。
image.png

Secret Manger に RDS への接続情報を登録

事前に作成しておいたこちらの RDS を使用します。
image.png

まずは Secret Manger コンソールで RDS への接続情報を登録するようです。
image.png
image.png

シークレットができたら ARN をメモしておきましょう。あとで使います。

IAM

次に、RDS プロキシがこのシークレットを読み取ることができる IAM ロールを作成します。RDS プロキシはこのシークレットを使用して、データベースへの接続プールを維持します。IAM コンソールに移動して、新しいロールを作成します。 前の手順で作成したシークレットに secretsmanager アクセス許可を提供するポリシーを追加します 。

IAM ポリシー

image.png

image.png

IAM ロール

image.png
image.png

rds-get-secret-role という名前で IAM ロールを作成しました。

RDS Proxy

さて、ここからが本題です。
RDS のコンソールを開くと Proxies の項目があります。Lambda の接続先をこのプロキシに向けることでコネクションプールをうまく使いまわしてくれるようです。

image.png

作成してみましょう。先ほど作成した IAM ロールや RDS を入力します。
image.png
image.png
image.png

作成まではしばらく時間がかかるようです。
image.png

Lambda の向き先を RDS から RDS Proxy に切り替える

RDS インスタンスに対して直接接続する代わりに、RDS プロキシに接続します。これを行うには、2 つのセキュリティオプションがあります。IAM 認証を使用するか、Secrets Manager に保存されているネイティブのデータベース認証情報を使用できます。IAM 認証は、機能コードに認証情報を埋め込む必要がないため、推奨されているようです。この記事では、Secrets Manager で以前に作成したデータベース資格情報を使用します。

DBに接続するアプリケーションの設定を変更してデプロイしましょう。

db.config.ts
import { TypeOrmModuleOptions } from "@nestjs/typeorm";
import { TaskEntity } from "./tasks/entities/task.entity";

export const dbConfig: TypeOrmModuleOptions = {
  type: "mysql",
  host: "rds-proxy.proxy-ch39q0fyjmuq.us-east-1.rds.amazonaws.com", // <-- DBの向き先をProxyに切り替える
  port: 3306,
  username: "user",
  password: "password",
  database: "test_db",
  entities: [TaskEntity],
  synchronize: false
};
$ npm run build && sls deploy

まだ Serverless では RDS Proxy をサポートしていないようでしたので Lambda のコンソールから設定してみます。セキュリティグループやサブネットなどは適宜各自の環境に合わせて作成してください。

image.png
image.png

RDS Proxy 経由でも無事に接続できました 🎉
※事前に DB にはテストデータを入れてあります
image.png

準備に使用した SQL

CREATE TABLE `tasks` (
  `id` int(36) unsigned NOT NULL AUTO_INCREMENT,
  `overview` varchar(256) DEFAULT NULL,
  `priority` int(11) DEFAULT NULL,
  `deadline` date DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=94000001 DEFAULT CHARSET=utf8mb4;

INSERT INTO `tasks` (`id`, `overview`, `priority`, `deadline`)
VALUES
    (1, '掃除', 0, '2020-11-11'),
    (2, '洗濯', 2, '2020-12-03'),
    (3, '買い物', 0, '2020-11-28');

負荷テストを実行してみる

コネクション数が Lambda のスケールに合わせて増え続けるような挙動を取らないか確認してみましょう。

今回は負荷のために Artillary を使用します。
yaml ファイルでシナリオを記述して実行する Nodejs 製の負荷テストツールです。

Artillary のインストール

$ npm install -g artillery

実行

yaml ファイルを記述しなくてもワンラインで実行できる手軽さも魅力的なツールで愛用しています。
以下のようなコマンドで簡単に実行できます。30 ユーザが 300 回リクエストを送るといった内容です。

$ artillery quick --count 300 -n 30 https://djpjh5aklf.execute-api.us-east-1.amazonaws.com/dev/tasks

実行された Lambda を確認します。Invocations が 9000 回を記録しています。

image.png

一方で RDS のコネクション数はなんと 43 になっていました。すごい。
ちなみに MySQL の現在のコネクション数は show status like 'Threads_connected' で確認できます。

負荷テスト開始前 最大リクエスト時
18 43

RDS Proxy を使わない場合はどうなるか

アプリケーションの向き先を RDS 本体に直接接続するように変更してみます。
この状態でもう一度負荷テストを行うとどうなるでしょうか。


import { TypeOrmModuleOptions } from '@nestjs/typeorm';
import { TaskEntity } from './tasks/entities/task.entity';

export const dbConfig: TypeOrmModuleOptions = {
  type: 'mysql',
  host: 'aurora.cluster-ch39q0fyjmuq.us-east-1.rds.amazonaws.com', // <-- RDS 本体に向ける
  port: 3306,
  username: 'user',
  password: 'password',
  database: 'test_db',
  entities: [TaskEntity],
  synchronize: false,
};

実行

コネクション数が 124 まで膨れ上がってしまいました。
やはりプロダクションロードで普通に Lambda+RDS の組み合わせはやってはいけないアンチパターンになりそうですね。RDS Proxy の威力を改めて感じることができました。

$ artillery quick --count 300 -n 10 https://djpjh5aklf.execute-api.us-east-1.amazonaws.com/dev/tasks
負荷テスト開始前 最大リクエスト時
18 124

まとめ

RDS プロキシを使用することで、データベースへの接続プールを保持することが確認できました。これで API やユーザリクエストを受けるようなワークロードでも Lambda から RDS への多数の接続を管理できます。とてつもなく強力なアップデートを体感できました。今後追加で RDS Proxy を使用する場合と使用しない場合とで、レスポンスタイムに違いが出てくるのかなど細かなところまで検証したいと思います。

クラウドはいよいよここまで成長してきました。
次は RDS がインスタンスを意識することなく水平にスケールするようになるのでしょうか。
その時は完全にサーバレスなクラウドが完成しますね。待ち遠しいです。

181
144
1

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
181
144