例えば AWS Lambda + Amazon CloudWatch Events で定期的にどこかからデータをひっこ抜いてきて適当に Amazon RDS にでも突っ込んでおくかという場合に、普通の Provisioned インスタンスだと少なくとも数千円/月はかかってしまうが Aurora Serverless にすれば使っていないときは勝手に寝てくれるので格安で済むのではないか。1 2
ただしこのアイデアにはひとつ問題があって、Aurora Serverless が起きるのが遅いということ。
DB クライアントから接続を試みると寝ていた Aurora Serverless は起動し始めるが、接続を受け入れられるようになるまでの間にクライアントがタイムアウトしてしまうなんてこともある。
そこで、使うときはあらかじめ Aurora Serverless を起動 (Capacity を増加) させるリクエストを送っておいて、起動した (Capacity が 0 以上になった) タイミングで DB クライアントから接続すれば良いのではないか。
Lambda Function の実装
Aurora Serveless が起きるまで待って後は何もしないという Lambda Function を実装してみる。
ランタイムは Python 3.8 を使った。
import asyncio
import boto3
def is_aurora_serverless_up(client, identifier: str) -> bool:
"""Aurora Serverless が起動中かどうかを返す"""
response = client.describe_db_clusters(DBClusterIdentifier=identifier)
assert response['ResponseMetadata']['HTTPStatusCode'] == 200
assert len(response['DBClusters']) > 0
assert response['DBClusters'][0]['EngineMode'] == 'serverless'
return response['DBClusters'][0]['Capacity'] > 0
async def wake_aurora_serverless_up(client, identifier: str, capacity: int = 2):
"""Aurora Serverless を起動する"""
if is_aurora_serverless_up(client, identifier):
return
response = client.modify_current_db_cluster_capacity(DBClusterIdentifier=identifier, Capacity=capacity)
assert response['ResponseMetadata']['HTTPStatusCode'] == 200
for i in range(10):
await asyncio.sleep(i ** 2)
if is_aurora_serverless_up(client, identifier):
return
raise TimeoutError()
async def main():
client = boto3.client('rds')
await wake_aurora_serverless_up(client, 'mycluster')
def lambda_handler(event, context):
asyncio.get_event_loop().run_until_complete(main())
あとやる必要があること。
- 実行するために
rds:DescribeDBClusters
とrds:ModifyCurrentDBClusterCapacity
の権限が必要なのでそれっぽい IAM Role を作成して Lambda Function に割り当てておく - Lambda Function のタイムアウト設定のデフォルト値 3秒 だと絶対に終わらないので 3分 とかに設定しておく
今回は asyncio で実装しているが asyncio.sleep で使っているだけなのであまり意味はない。普通に time.sleep でもいい。asyncio で実装しておくと PostgreSQL クライアントとして asyncpg が選べるというメリットはある。
実行してみる
試しに実行してみると以下のようなことがわかる。
- Aurora Serverless が起動するまでに 15秒 くらいかかる
- 起動した (capacity > 0 になった) としてもすぐに DB クライアントから接続できるわけではなく、そこから更に 10 秒くらい待つ。
結局待つので前とあまり変わっていないが、DB クライアントがタイムアウトしなくなったのでこれはこれでよし。
最後に
ここまでやってから「これ普通に DB クライアントの接続タイムアウトを長めに設定しておくだけで解決したのでは?????」ということに気づいてしまった。
import psycopg2
with psycopg2.connect('postgresql://...', connect_timeout=120) as conn:
...
(psycopg2 の場合) これで問題なく動いてしまった。
エッ
あっ、はい・・・。