はじめに
本記事ではLambdaとRDS(Aurora)構成の
可用性と処理速度を向上させるためのポイントをまとめてみました。
なお、コスト要件で不要になる判断もあると思いますが
本記事ではコスト要件は考えずに理想形とさせていただきます。
確認ポイント①:RDS Proxyを使っていますか?
NGパターン:Lambdaが直接Auroraに接続する
この構成だと、Lambdaの同時実行数が増えたときに
Auroraの同時接続数の上限に引っ掛かり、リクエストが拒否されて未処理のデータが失われてしまいます。
OKパターン:LambdaがRDSProxyを経由してAuroraへアクセスする
RDSProxyを間に入れてあげると接続プールを確立して再利用することで効率的な接続が可能になります。
確認ポイント②:Aurora Serverless v2を使っていますか?
NGパターン:RDS,Auroraを使用する
RDSやAuroraでは自動スケールしないので、Lambdaの同時リクエストが大量に増えたとき
RDSが処理に耐えきれなくなってしまいます。
OKパターン:Aurora Serverless v2を使用する
Aurora Serverless v2を使ってあげることで、DBのオートスケール可能になります。
とわいえ、コストが高いのは否めないので
なるべくNoSQLが使用できないか検討してDynamoDBを使いたいところ。
確認ポイント③:LambdaのDB接続をhandlerの中でやってませんか?
Lambdaはhandler外のコードはコールドスタートの時1回だけ
実行します。逆に言うとhandlerの中はウォームスタートのときも
毎回実行します。DB接続をhandlerの中で実施してしまうとlambda関数が
一回一回DB接続をしてしまい、コネクションを張るのに時間がかかってしまいます。
詳しくは↓を参照ください。
NGパターン:handler内でDB接続をしてしまっている
import json
import os
import boto3
import pymysql
# RDS settings
proxy_host_name = os.environ['PROXY_HOST_NAME']
port = int(os.environ['PORT'])
db_name = os.environ['DB_NAME']
db_user_name = os.environ['DB_USER_NAME']
aws_region = os.environ['AWS_REGION']
# Fetch RDS Auth Token
def get_auth_token():
client = boto3.client('rds')
token = client.generate_db_auth_token(
DBHostname=proxy_host_name,
Port=port
DBUsername=db_user_name
Region=aws_region
)
return token
def lambda_handler(event, context):
token = get_auth_token()
try:
connection = pymysql.connect(
host=proxy_host_name,
user=db_user_name,
password=token,
db=db_name,
port=port,
ssl={'ca': 'Amazon RDS'} # Ensure you have the CA bundle for SSL connection
)
with connection.cursor() as cursor:
cursor.execute('SELECT %s + %s AS sum', (3, 2))
result = cursor.fetchone()
return result
except Exception as e:
return (f"Error: {str(e)}") # Return an error message if an exception occurs
OKパターン:handler外でDB接続をして環境変数に保持している
import json
import os
import boto3
import pymysql
# RDS settings
proxy_host_name = os.environ['PROXY_HOST_NAME']
port = int(os.environ['PORT'])
db_name = os.environ['DB_NAME']
db_user_name = os.environ['DB_USER_NAME']
aws_region = os.environ['AWS_REGION']
# handlerの外で接続を初期化(コールドスタート時のみ実行)
def get_connection():
client = boto3.client('rds')
token = client.generate_db_auth_token(
DBHostname=proxy_host_name,
Port=port,
DBUsername=db_user_name,
Region=aws_region
)
return pymysql.connect(
host=proxy_host_name,
user=db_user_name,
password=token,
db=db_name,
port=port,
ssl={'ca': 'Amazon RDS'}
)
# グローバル変数として接続を保持
connection = get_connection()
def lambda_handler(event, context):
global connection
try:
# 接続が切れている場合は再接続
if not connection.open:
connection = get_connection()
with connection.cursor() as cursor:
cursor.execute('SELECT %s + %s AS sum', (3, 2))
result = cursor.fetchone()
return result
except Exception as e:
return f"Error: {str(e)}"
こうすることでDB接続をグローバル変数にしてウォームスタートした関数
が使いまわしてくれて処理速度が向上します。
まとめ
- LambdaとRDSと使う時はRDSProxyを使おう!
- RDSではなく、できるだけAurora Serverless v2を使おう!
- LambdaのDB接続はハンドラーの外で定義しましょう!