AWS Lambdaを使ってRedshift料金を節約する
エンジニアの佐藤です。こんにちは。
前回AWS Lambdaを使ってEC2料金を節約するで、監視サーバを待機させずにEC2夜間自動停止を実装する手法をお話させていただきましたが、今回は同様の手法をRedshiftに適用してみましょう。
Redshiftは、カラム駆動型のデータ配置、圧縮、PostgreSQLに近いSQL言語などが特徴の高速分散DBMSと筆者は理解しているのですが、その課金体系はCPUとストレージのセット(ノード)の利用時間の従量課金です(長期事前予約による割引適用もありますが)。EC2と同様、利用が無い時間帯がある場合はリソースの自動解放・復旧を仕掛けることにより利用料金の節約が期待できます。
いくつかの問題と対処
ただしEC2のように単純にはいきません。以下の問題に対処する必要があります。
- ノードを解放するとデータも消去されてしまうので、事前にスナップショットを作成する必要がある。復旧のときは、スナップショットからリソースを復元する。
- 再起動した場合はエンドポイントが変更になる可能性がある。
幸い、いずれの場合についても少々の工夫で対処できました。前回同様にNodeJSとAWS SDK for Javascriptを使って実装してみましょう。今回もサンプルコード全体は以下の弊社公開リポジトリで公開しています。
https://github.com/FLECT-DEV-TEAM/cloudblog20151110
スナップショットの使用
稼働中のRedshiftをスナップショットにエクスポートして削除するには、以下のようにクラスターIDとスナップショットIDを指定してdeleteClusterを呼び出します。
var params = { ClusterIdentifier: ctx.ClusterId, FinalClusterSnapshotIdentifier: ctx.SnapshotId }; REDSHIFT.deleteCluster( params, function(err, data) { if (err) { return cb(err); } cb(null, ctx); } ); // end of deleteCluster
このスナップショットから復元するには、クラスターIDとスナップショットのIDを指定してrestoreFromClusterSnapshotを呼び出します。(今回はVPC内部にRedshiftを設定しますので、設定先サブネットやセキュリティグループも設定する必要があります。)ノード数やマスターユーザ設定などは、特に指定しなければスナップショット作成時の設定が引き継がれます。
var params = { ClusterIdentifier: ctx.ClusterId, SnapshotIdentifier: ctx.SnapshotId, AvailabilityZone: ctx.AvailabilityZone, ClusterSubnetGroupName: 'default', VpcSecurityGroupIds: ctx.VpcSecurityGroupIds, PubliclyAccessible: false, AutomatedSnapshotRetentionPeriod: 0 }; REDSHIFT.restoreFromClusterSnapshot( params, function(err, data) { if (err) { return cb(err); } cb(null, ctx); } ); // end of restoreFromClusterSnapshot
エンドポイント(接続先)
Redshiftのクラスターを起動すると、以下のようなエンドポイントが払い出され、このエンドポイントにPosgreSQLプロトコルで接続することでクラスタを利用することができます。
<クラスターID>.<ランダム文字列>.<リージョン>.redshift.amazonaws.com:<ポート番号>
このエンドポイントは「クラスターIDやノード数を変更した場合に変更される」とSDKでは説明されていますが、「そうしない限り、保存される」とは書いてありません。つまりスナップショットの保存と復元の前後で同じエンドポイントが使える保証は無いようです。このままでは、エンドポイントの都度調査という面倒なことになります。
最も簡単な解決方法はElastic IP AddressでグローバルIPアドレスを設定してしまうことでしょう。しかし今回はVPC内での利用を想定しており、本来グローバルIPアドレスは不要です。
筆者が選択した方法は、「Route 53の内部DNSを設定する」というものです。Route 53ではVPCに接続された内部用のHosted Zoneを設定することができます。このHosted ZoneにCNAMEレコードをAPIで設定し、アプリケーションからはCNAME名でアクセスしてもらいます。具体的には、以下のようなレコードを設定します。
redshift.internal. 60 CNAME cluster-id01.aaaabbbbcccc.ap-northeast-1.redshift.amazonaws.com.
実装にあたっては、最初にクラスターのエンドポイントをクエリします。
var params = { ClusterIdentifier: ctx.ClusterId }; REDSHIFT.describeClusters( params, function(err, data) { var clusters = data.Clusters; var c = clusters[0]; ctx.Endpoint = c.Endpoint.Address; cb(null, ctx); } ); // end of describeClusters
次にこのエンドポイントを内部DNSのCNAMEレコードとして設定します。
var params = { HostedZoneId: ctx.HostedZoneId, ChangeBatch: { Changes: [{ Action: 'UPSERT', ResourceRecordSet: { Name: 'redshift.internal', Type: 'CNAME', TTL: 60, ResourceRecords: [{Value: ctx.Endpoint}] } }] // end of Changes } // end of ChangeBatch }; ROUTE53.changeResourceRecordSets( params, function(err, data) { if (err) { return cb(err); } cb(null, ctx); } ); // end of changeResourceRecordSets
Route 53 Hosted Zoneは以下のようになっています。
タイミングの問題
実は筆者はこの段階で一つの壁にぶつかってしまいました。
Redshiftクラスターをスナップショットへ保存する処理も、またスナップショットから復元する処理も、時に長い処理時間を要します。この処理時間は課金対象になりませんが(課金はクラスターが「available」になってから)、長いと復元処理を開始してからエンドポイントが参照可能になるまでの所要時間が、AWS Lambdaの実行時間上限(5分)を超えてしまうのです。また、Lambdaを5分も待機させるのは、そもそもナンセンスにも思えます。
筆者はこの内部DNS設定処理を、スナップショットからの復元処理が完了しそうな時間帯に10分おきに繰り返し実行することにしました。何回かは空打ちとなりますが、エンドポイントが利用可能になってから10分以内にはCNAMEレコードが設定されるはずです。1回の試行時間はわずかで待機中は課金されませんので、リーズナブルです。
最終的には以下のように3つのLambda Functionをスケジュールしました。
- スナップショットからの復元:午前7時
- DNS設定試行:午前7時から8時まで、10分おきに実行
- スナップショット作成とクラスター削除:午前1時
Lambda Functionの設定方法については前回投稿をご参照ください。なお、Lambda Functionの設定にあたっては、前回同様Description項目に設定情報をJSON形式で記載することを前提にしています。以下はこのDescription文字列の例です。
{
"ClusterId": "xxxxxxxx",
"SnapshotId": "xxxxxxxx",
"AvailabilityZone": "ap-northeast-1c",
"VpcSecurityGroupIds": ["sg-xxxxxxxx"],
"HostedZoneId": "XXXXXXXXXXXXX"
}
まとめ
実装が若干複雑ですが、Redshiftという重量級コンポーネントについても「夜間自動停止」が実装できました。今回ご紹介した手法は、RDSにも容易に転用可能でしょう。
コメント