タム
2023.03.21
6
こんにちは、タムです。
今回は、(AWS初心者の僕が、)RDS Proxyの負荷検証を行ったので、その結果を報告させていただきます。
RDS Proxyとは何か?については多数の記事が存在するため割愛しますが、
現在自分が関わっている案件で、LambdaからRDSに接続する必要性が出てきたため、RDS Proxyを導入することを検討することになりました。
導入を検討するにあたり、RDS Proxyの挙動、特にどれくらいのアクセス数に耐えられるかが不明だったため検証を行いました。
RDS Proxy コンピューティングリソースはサーバーレスであり、データベースのワークロードに基づいて自動的にスケーリングされます。
と記載がありますが、この一行だけを根拠に導入しても問題ないと判断するには心許なかったため、今回検証を行いました。
時間がない人のために先に結論だけ申し上げると、結局ドキュメントに記載のとおりだった。
ただ、もう少し付け加えると、無限に多くのリクエストを受けられるわけではなく、
RDS Proxyが利用できる最大接続数が多いほど、多くのリクエストを捌くことができた。
リクエスト数が一定以上多くなるとレイテンシーが増加し、それ以上は増えないという結果になった。
また後述するが使用する言語・ライブラリ・実装によっても挙動が異なる場合があるため注意が必要で、
上記の結論はPython / sqlalchemy(NullPool)+pymysqlを使用した場合。
import os
import pymysql
import mysql.connector
import time
from sqlalchemy import create_engine, text
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.pool import NullPool
host = os.environ['RDS_HOST_NAME']
user = os.environ['USER']
password = os.environ['PASS']
db = os.environ['DB']
connect_count = os.environ['CONNECT_COUNT']
connect_timeout = os.environ['CONNECT_TIMEOUT']
def lambda_handler(event, context):
_connect()
return {
'isBase64Encoded': False,
'statusCode': 200,
'headers': {},
'body': '{"message": "Hello from AWS Lambda"}'
}
def _connect():
engine = create_engine(
'mysql+pymysql://{user}:{password}@{host}/{dbname}'.format(user=user, password=password, host=host, dbname=db),
echo=True,
poolclass=NullPool,
)
session = scoped_session(
sessionmaker(
autocommit=False,
autoflush=True,
expire_on_commit=False,
bind=engine
)
)
res = session.execute(text('select now()'))
for v in res:
print(v)
session.close()
EC2から実行した場合のほうがリクエスト数が多くなったのは、レイテンシーが小さかったためで、
おそらく、同じVPCにあるので通信のパケットが長い距離を移動しなくて済むためと考えられる。
なお、2つのマシンから同時に実行した場合もリクエスト数はほぼ変わらなかったため、
クライアントマシンのスペックはあまり関係なく、送れるリクエスト数の上限はネットワーク帯域的な限界で決まるのではと思った。
また、RDSのスペックもあると思うが、RDS Proxyの最大接続数によって最大リクエスト数がほぼ決まることがわかった。
sqlalchemy(NullPool)+pymysqlを使用した場合はsession.close()
してもコネクション自体は切れずにプロキシでプールされ別の接続リクエストに再利用されるが、
pymysqlを単体で使用した場合はコネクションがプールされずに毎回切れる、という全く異なる挙動を示した。
これによりpymysqlを単体で使用した場合はパフォーマンスが悪化し、リクエスト数が多くなるとレイテンシーの増加だけでなく接続失敗も発生するようになった。
問題はコネクションの部分なのでクエリのログには原因を示すようなログは特に見当たらず、
解決のためにはTCP接続の部分やソースコードなどを掘り下げていく必要があり時間がかかりそうだったため、
今回はそこまで深追いすることはやめた。
Lambdaの課金は実行回数によって決まるため、
最初なるべく実行回数を少なくしようとして1関数の中でループして500個のDB接続を取得するようにしていた。
この関数を単体で実行したときは正常に動作したが、
DBリクエスト数が2万件くらいになったあたりからレスポンスが正常に返ってこなくなった。
LambdaのエラーログにはLost Connection
となっていたのでRDSの設定などを見直していたが、
最終的にAPI Gatewayの時間制限(30秒)に引っかかっていることがわかった。
知っていれば回避できたと思うが、この問題の解決にかなりの時間を費やしてしまった。
RDSのサービスを使う際には予め制約を把握しておくことが重要ということが身にしみてわかった。