先日の re:Invent で RDS Proxy がプレビュー版ですが発表されました。
AWS Lambda は水平スケールするため RDS と相性が良くないと常々言われてきましたが、この機能でそれが解消されるのでしょうか?というわけで実際機能を試してみようと思います。
リソース作成
テスト用のVPC、RDS、Lambda Function、RDS Proxy を作成します
VPC, RDS 作成
今回 VPC、RDS はさくっと CloudFormation で作成
https://github.com/kobarasukimaro/rds-proxy-test/blob/master/cfn/rds-proxy-sample.yml
RDS の設定
パラメータグループで max_connection
を 20 に設定しています。
RDS Proxy 作成
RDS Proxy は画面から作成します。 awscli でも 1.16.300
以上でしたら作成できます。
以下のような感じで作成しました。サブネットが空になってますがちゃんと設定しています。関連付けるデータベースのサブネットが自動的にサジェストされるのであまり迷うことはないかと思います。
シークレットはこの画面から作成しても候補に出てこないで予め作っておいたほうが良いかも。
Lambda 作成
Lambda は Serverless Framework で作成します。
sls deploy --stage xxx
テスト用コード
Python で簡単に作成
import mysql.connector
DB_USER = os.environ["DB_USER"]
DB_PASSWORD = os.environ["DB_PASSWORD"]
DB_HOST = os.environ["DB_HOST"]
DB_NAME = os.environ["DB_NAME"]
def execute_query(event):
config = {
'user': DB_USER,
'password': DB_PASSWORD,
'host': DB_HOST,
'database' : DB_NAME,
}
cnx = mysql.connector.connect(**config)
cursor = cnx.cursor()
query = ("SELECT SLEEP(10)")
cursor.execute(query)
def lambda_handler(event, context):
execute_query(event)
Lambda Function に RDS Proxy を登録
しなくても Proxy に接続できました。
↓に Proxy を登録すると IAM ロールが作られるので、 IAM 認証必須の場合に必要なのかもしれません。
試してみる
最大コネクションが7個ある RDS で、 10秒スリープする Lambda Function を同時に 10並列で実行してみます
実行の前に
RDS に設定した max_connection
は 20 ですが、
RDS 自体がコネクション 4個と RDS Proxy が 8個確保していて、さらにテスト中はコネクションの様子を見るためにコンソールから1個コネクションを使っているため実質的に Lambda が使えるコネクションは 7個となります。
MySQL [test]> show processlist;
+-----+---------------+---------------------+------+---------+------+-------------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+-----+---------------+---------------------+------+---------+------+-------------+------------------+
| 2 | rdsproxyadmin | xxx.xxx.xxx.xxx:xxxx | NULL | Sleep | 0 | cleaning up | NULL |
| 3 | rdsproxyadmin | xxx.xxx.xxx.xxx:xxxx | NULL | Sleep | 0 | cleaning up | NULL |
| 4 | rdsproxyadmin | xxx.xxx.xxx.xxx:xxxx | NULL | Sleep | 0 | cleaning up | NULL |
| 5 | rdsproxyadmin | xxx.xxx.xxx.xxx:xxxx | NULL | Sleep | 0 | cleaning up | NULL |
| 6 | rdsproxyadmin | xxx.xxx.xxx.xxx:xxxx | NULL | Sleep | 0 | cleaning up | NULL |
| 9 | rdsadmin | localhost | NULL | Sleep | 1 | cleaning up | NULL |
| 11 | rdsadmin | localhost | NULL | Sleep | 0 | cleaning up | NULL |
| 55 | rdsadmin | localhost | NULL | Sleep | 14 | cleaning up | NULL |
| 56 | rdsproxyadmin | xxx.xxx.xxx.xxx:xxxx | NULL | Sleep | 0 | cleaning up | NULL |
| 57 | rdsproxyadmin | xxx.xxx.xxx.xxx:xxxx | NULL | Sleep | 0 | cleaning up | NULL |
| 67 | rdsadmin | localhost | NULL | Sleep | 34 | cleaning up | NULL |
| 122 | rdsproxyadmin | xxx.xxx.xxx.xxx:xxxx | NULL | Sleep | 0 | cleaning up | NULL |
| 124 | admin | xxx.xxx.xxx.xxx:xxxx | test | Query | 0 | starting | show processlist |
+-----+---------------+---------------------+------+---------+------+-------------+------------------+
13 rows in set (0.01 sec)
Proxy なし
まずは今まで通り、普通に RDS に接続した動作を試してみます
実行
Serverless Framework でローカルから10回実行
$ for ((i=0; i<10; i++)); do serverless invoke --function proxy-test --stage xxx -r ap-northeast-1 &; done
結果
CloudWatch で結果を見てみると、10回中 3回失敗しているのを確認できます
CloudWatch Logs を見ると Too many connections
が発生していたので想定通りの挙動となります
$ aws logs get-log-events --log-group-name /aws/lambda/xxxxxxxxx --log-stream-name "2019/12/10/[\$LATEST]xxxxxxxxxxxx"
<中略>
[ERROR] OperationalError: 1040 (08004): Too many connections
<中略>
Proxy あり
DB の接続先を RDS Proxy に変更して試してみます
実行
先ほどと同じように Serverless Framework でローカルから10回実行
$ for ((i=0; i<10; i++)); do serverless invoke --function proxy-test --stage xxx -r ap-northeast-1 &; done
結果
CloudWatch で結果を見てみると、10回中 全て成功しているのを確認できます
Duration を見てみると Max が約 20秒、 Min が約 10秒となってるので、max connection からあぶれたコネクションは空きが出るまで待っているものと思われます
ログも見てみましたが、 20秒かかっている実行はコネクションで待っているのが確認できます(赤枠がコネクション待ち)
RDS Proxy が想定通りの役割を果たしてくれています。 Lambda + RDS 使う場合の大きな心配事が一つ解消されるので GA になるのが楽しみですね。
ハマりどころ
CloudFormation から作成したシークレットだとエラー
最初 CloudFormation から RDS 用のシークレットを作成して RDS Proxy で読み込んでみたのですが、
Client authentication failed for user "{DBユーザー名}" with auth-plugin "mysql_native_password" and TLS on. Reason: Invalid credentials.
というエラーが出てコネクションが張れませんでした。
画面からシークレットを作成した場合は上手くいったので、 CloudFormation 側で何か足りないものがあったとは思いますが原因は今の所不明です。
Lambda でコネクションするときに Too many connections が出てしまう
これはテスト用にコネクション数の設定をいじっていたのが原因だったのですが max_connection
を 1 や 10 に設定したところ、 RDS と RDS Proxy がコネクションを確保するのに気づかなかったので、そっちの確保が優先されて Lambda からコネクションが張れませんでした。 RDS のデフォルトのコネクション数で使う場合には全く問題はないかと思います。
設定は正しいのに Lost connection が出てコネクションが張れない
リソースは全て構築したしコネクション数の設定も問題ないし、さあ試そうと思ったところ、 Lambda で
[ERROR] InterfaceError: 2055: Lost connection to MySQL server at '{RDS Proxy のDNS}:3306', system error: 8 EOF occurred in violation of protocol (_ssl.c:1076)
というエラーが出てコネクションできませんでした。
どうやら mysql-connector-python
のバージョンが原因で、 8.0.17
だとこのエラーが発生するようです。
また、Python 3.7 でこの事象を確認してます。 3.8 では未確認です。
やり残し
- フェイルオーバーの速度比較
- RDS 直と Proxy のレイテンシー比較
- Python3.8 と mysql-connector-python >= 8.0.17 が動作するかどうか
- Lost connection の原因
コード
今回試したコード一式はこちら
https://github.com/kobarasukimaro/rds-proxy-test