こんにちは。X(クロス) イノベーション 本部 ソフトウェアデザインセンターの耿です。 Amazon Aurora Serverlessは、コンピューティングとメモリのキャパシティ(Aurora 容量ユニット = ACU)をリク エス ト数に応じて自動で変化させることができるデータベースサービスです。事前のスケーリング計画が不要になるほか、実際のワークロードに合わせてキャパシティを増減させるため、費用の最適化に向いています。 Aurora Serverlessは v1 と v2 の2つのバージョンが一般利用可能ですが、v1 の方はしばらくアクセスがないと 0 ACU まで落として一時停止する機能があり、コンピューティングとメモリの料金が全くかからなくなります。本番環境であれば利用されることはあまりないと思いますが、リク エス ト数が限定的な開発環境ではこの機能をうまく利用することで、費用をさらに節約することができます。 しかし、 0 ACU で一時停止している状態のデータベースにアクセスしようとすると、キャパシティがないため初回接続が失敗してしまいます。本記事はシンプルにこの問題を解消し、費用を抑える方法を記載します。 Aurora Serverless の ACU 設定 Aurora Serverless のバージョンの違い 開発で遭遇した問題点 実現したいこと 実現方法 他の選択肢 CDKコード 実際の料金 Aurora Serverless の ACU 設定 Aurora Serverless は Aurora 容量ユニット (ACU) の最大値と最小値を指定することで、その間で負荷に応じてオートスケーリングします。v1に限って一時停止設定が可能で、リク エス トが全くない時に 0 ACU までスケールダウンできます。 Aurora Serverless v1の一時停止設定 Aurora Serverless のバージョンの違い 2022年5月時点において、Aurora Serverless v1にできてv2にできないことがあります。 v2は Data API を利用できない CloudFormationはまだv2をサポートしていない v2は 0 ACU(一時停止)にできず、最小ACUは 0.5 である ACUに関する補足として、2022年5月時点でv2のACU費用はv1の2倍に設定されています。すなわちv2を最小キャパシティ0.5 ACUで常時稼働した場合の費用は、v1を1 ACUで常時稼働した場合の費用と同等になります。v1は一時停止が可能なため、一時停止をした時間の分だけv2より費用を節約できます。 ※その他のAurora Serverless v2の特徴は、以下の動画で詳しく解説されています。 JAWS-UG横浜 #44 Aurora Serverless v2 開発で遭遇した問題点 Data API とCDKを利用したかったため、Aurora Serverless v1を利用して開発している環境があります。費用を抑えるために一時停止機能を有効にしていますが、0 ACUのときはコンピューティングとメモリの動作が完全に停止するため、 初回接続ではデータベースへの接続が失敗する という問題に遭遇しました。 Communications link failure The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.; SQLState: 08S01 しばらく(体感30秒〜1分程度)するとデータベースが起動し、問題なく接続できるようになるのですが、費用を抑えつつ初回接続でも失敗しないようにしたかったため、その仕組みを検討しました。 実現したいこと Aurora Serverlessに アクセスする可能性のある時間帯は一時停止をさせずに最低でも1 ACUで稼働させ、それ以外の時間帯は0 ACUへのスケールダウンを許容 すれば、余分な費用を削減し、初回接続でエラーになる問題も解消できます。今回はアクセスする可能性のある時間帯は余裕を持って 7:00 ~ 22:00 と広めに定義しています。 整理すると、実現したいことは次のようになります。 平日の7:00 ~ 22:00 は、Aurora Serverlessが 0 ACUにならないようしに、DBへの接続が常に成功するようにしたい それ以外の時間(平日深夜と土日)は 0 ACUにスケールダウンしても良い 実現方法 Aurora クラスタ が一時停止しない場合の最小 ACU を 1 とする Aurora クラスタ へ 2時間 アクセスがなかった場合、一時停止するよう(0 ACU になるよう)に設定する Aurora クラスタ へ参照系のクエリを発行するLambda関数を作成し、 平日の7:00 ~ 20:00 の間は1時間ごとに定期実行 する こうすることで、平日は20:00に最後のLambda関数が実行され、22:00まではAurora クラスタ が0 ACUにならないことが保証されます。夜間や土日はAurora クラスタ への接続がないと一時停止し、次の平日の朝7:00に最初のLambda関数が実行され、Aurora クラスタ が起動します。 他の選択肢 シビアに費用を抑える必要がない場合 、一時停止設定を無効にし、0 ACUにスケールダウンしないようにすれば、データベースへのアクセスがなくても停止しなくなり、本記事で述べるような ワークアラウンド は必要ありません。 あるいは 0 ACUにスケールダウンした後の初回接続が失敗しても、 起動を待ってからクエリを再実行することが許容できる場合 も、本記事の ワークアラウンド は必要ありません。 CDKコード 以下のCDKコードで環境を作成します。 import { Duration , Stack , StackProps } from "aws-cdk-lib" ; import * as ec2 from "aws-cdk-lib/aws-ec2" ; import * as events from "aws-cdk-lib/aws-events" ; import * as eventTargets from "aws-cdk-lib/aws-events-targets" ; import * as iam from "aws-cdk-lib/aws-iam" ; import * as lambda from "aws-cdk-lib/aws-lambda" ; import * as lambdaNodejs from "aws-cdk-lib/aws-lambda-nodejs" ; import * as rds from "aws-cdk-lib/aws-rds" ; import { Construct } from "constructs" ; export class MyStack extends Stack { constructor( scope: Construct , id: string , props?: StackProps ) { super( scope , id , props ); const privateSubnetName = "myPrivateSubnet" ; // VPCを作成 const vpc = new ec2.Vpc ( this , "MyVpc" , { cidr: "10.0.0.0/16" , enableDnsHostnames: true , enableDnsSupport: true , subnetConfiguration: [ { name: privateSubnetName , subnetType: ec2.SubnetType.PRIVATE_ISOLATED , cidrMask: 20 , } , ] , } ); // Aurora Serverless v1 クラスタを作成 const auroraCluseter = new rds.ServerlessCluster ( this , "MyAuroraCluster" , { engine: rds.DatabaseClusterEngine.AURORA_MYSQL , vpc: vpc , vpcSubnets: { subnetGroupName: privateSubnetName } , // 最小ACUを1、最大ACUを2、一時停止までに必要な非アクティブ時間を2時間に指定 scaling: { minCapacity: 1 , maxCapacity: 2 , autoPause: Duration.hours ( 2 ) } , // Data APIを有効化 enableDataApi: true , } ); // Lambda関数 const awakeAuroraFunction = new lambdaNodejs.NodejsFunction ( this , "MyFunction" , { // Lambda関数へのファイルパス entry: "functions/awake-aurora-serverless.ts" , runtime: lambda.Runtime.NODEJS_14_X , // Data APIを利用するため、AuroraクラスタARNとシークレットのARNを環境変数で渡す environment: { CLUSTER_ARN: auroraCluseter.clusterArn , SECRET_ARN: auroraCluseter.secret?.secretArn ?? "" , } , } ); // Lambda関数の実行ロールにData APIへのアクセスを許可するポリシーを追加 if ( auroraCluseter.secret?.secretArn ) { const auroraDataApiPolicy = new iam.ManagedPolicy ( this , "MyAuroraDataApiPolicy" , { statements: [ new iam.PolicyStatement ( { resources: [ ` ${ auroraCluseter.secret.secretArn } *` ] , actions: [ "secretsmanager:GetSecretValue" ] , effect: iam.Effect.ALLOW , } ), new iam.PolicyStatement ( { resources: [ auroraCluseter.clusterArn ] , actions: [ "rds-data:ExecuteStatement" ] , effect: iam.Effect.ALLOW , } ), ] , } ); awakeAuroraFunction.role?.addManagedPolicy ( auroraDataApiPolicy ); } // 日本時間 平日の 7:00 ~ 20:00 の間に1時間ごとにLambda関数を定期起動する // cron式は UTC で記載するため、「日曜 ~ 木曜の22:00 ~ 23:00」と「月曜 ~ 金曜の 0:00 ~ 11:00」の2つに分ける new events.Rule ( this , "MyEventRule1" , { schedule: events.Schedule.cron ( { minute: "0" , hour: "22-23" , weekDay: "SUN-THU" } ), targets: [ new eventTargets.LambdaFunction ( awakeAuroraFunction , { retryAttempts: 3 } ) ] , } ); new events.Rule ( this , "MyEventRule2" , { schedule: events.Schedule.cron ( { minute: "0" , hour: "0-11" , weekDay: "MON-FRI" } ), targets: [ new eventTargets.LambdaFunction ( awakeAuroraFunction , { retryAttempts: 3 } ) ] , } ); } } Lambda関数の部分では、 NodejsFunction コンストラクト を利用することで、TypeScript で記述した Lambda 関数の JavaScript への コンパイル からデプロイまでをCDKがやってくれます。 Aurora クラスタ へクエリを発行するLambda関数は、 functions/awake-aurora-serverless.ts に以下のように実装します。Data API を利用してクエリを発行します。 import * as RDS from "@aws-sdk/client-rds-data" ; import { Handler } from "aws-lambda" ; const client = new RDS.RDSDataClient ( { region: "ap-northeast-1" } ); export const handler: Handler = async () => { const input = { // Data APIを利用するための、AuroraクラスタのリソースARNとシークレットのARNを環境変数から取得する resourceArn: process .env.CLUSTER_ARN , secretArn: process .env.SECRET_ARN , // 発行するSQL文 sql: "SHOW databases;" , } ; const command = new RDS.ExecuteStatementCommand ( input ); await client.send ( command ); } ; 発行する SQL 文は更新系でなければ何でも良いですが、シンプルに SHOW databases; としています。 実際の料金 以上の構成でしばらく稼働した場合の料金を確認しました。 土日はAurora クラスタ が停止し、料金がかかっていないことがわかります。平日も24時間のうち15時間しか稼働していません。 今回の条件では一週間 24h * 7日 = 168h のうち、 15h * 5日 = 75h 稼働しているため、常時稼働に比べて 75h / 168h = 45% の費用に抑えることができています。 一方で平日の業務時間中は常に稼働状態のため、接続が失敗することがなくなり、今回の要件を満たすことができました。 執筆: @kou.kinyo2 、レビュー: @higa ( Shodo で執筆されました )