⏩
[AWS] DBに負荷をかけず約800万件のデータマイグレーションを実施した方法
3行まとめ
- 同一DB内のテーブル間で約800万件のデータマイグレーションを実施
- DB(Amazon Aurora)に対しての負荷をほぼかけることなく実施
- マイグレーションは「SQS + Lambda」のサーバーレス構成で実施
データマイグレーション
概要
- とある機能のリリースに合わせて既存テーブルから新規テーブルへのデータマイグレーションが必要となった
- 1回のみ実施
- データは約800万件
- DBへの負荷は極力避けたい
技術スタック
- AWS
- Amazon Aurora
- EC2
- SQS
- Lambda
- 言語
- PHP(Laravel)
実現方法の検討
- マイグレーション方法検討で候補にあがったのは以下
- Laravelのマイグレーション機能を利用する
- Laravelのシーダー機能を利用する
- EC2で移行スクリプト実行する
- Lambdaを利用する
1. Laravelのマイグレーション機能を利用する
概要
- LaravelにはDBのバージョン管理的な機能が備わっています
- 主なユースケースは「DDL」の発行かと思います
- この機能を使いマイグレーションファイルの中に今回のデータマイグレーション処理を行う事を検討した
不採用理由
- Laravelマイグレーションは「リリース工程」の一部
- 約800万件のマイグレーションは相当時間がかかる
- リリース時間がかなり長時間になってしまい他のリリースへの影響も考えられる
- 1度きりのマイグレーション処理が半永久的にシステム内に残存してしまう
2. Laravelのシーダー機能を利用する
概要
- Laravelにはデータをシード(種をまく、初期値の設定)する機能を持っています
- 主なユースケースはマスターテーブル等への「初期値の設定」かと思います
- この機能を使いシーダークラスの中に今回のデータマイグレーション処理を行う事を検討した
不採用理由
※Laravelマイグレーションと同理由なので割愛
3. EC2で移行スクリプト実行する
概要
- 弊システムではEC2からRDSへ接続が可能となっている
- それを利用してEC2上でマイグレーションスクリプトを実行することで実現する
不採用理由
- EC2上で長時間スクリプトが実行状態となる
- 仮に実行速度を早めたくなった場合に並列実行などを組み込むのに一手間必要
- 実行失敗対象の再実行性の考慮も難しい(と思われる)
- EC2が何かしらの原因で落ちてしまう可能性を考慮
4. Lambdaを利用する
概要
- SQSにデータマイグレーション対象を投入
- LambdaがSQSから処理対象のメッセージを取得しデータマイグレーション
- 処理失敗したらSQS DLQへメッセージを移動
採用理由
- メッセージ受信数無制限のSQSへ処理対象を投入することで処理対象が何万件あっても大丈夫
- 処理失敗したものをDLQに移せることで再処理も容易
- トリガーの設定でSQSからの取得メッセージを制御できる
構築
AWS構成図
- 今回は以下の構成で作成していく
DB負荷を考慮したLambdaトリガーの設定と処理
- 今回はLambdaの起動数調整と処理間隔調整によりDB負荷を調整できるようにした
Lambdaトリガーによる負荷調整
以下の値を設定し1回のLambda起動で最小の処理に抑える
-
BatchSize
- 1回で受信するメッセージの数
- 今回は1起動で1メッセージずつ処理をしたいので「1」を設定
- ※必ずしも設定した値でメッセージ数が取得できるとは限らない事に注意
-
MaximumConcurrency
- Lambdaの同時に実行される最大数を制御できる
- 最小値は「2」であり今回はこの値を設定
Lambda処理による負荷調整
- マイグレーション処理の「データ登録」「データ削除」の処理に指定時間インターバルを設けられるようにした
- Lambdaの環境変数に設定し関数内部でその値を使用して
sleep
させる - 1回データ登録後に指定時間
sleep
させ、その後処理が再開される - これにより時間操作でDB負荷を操作する
AWS SAMによるSQSとLambdaの構築
- SQSとLambdaをAWSへデプロイするために AWS SAM を使用する
- SAM を使用することでAWSへのデプロイは勿論、ローカル開発も容易になる
※SAMのインストール、ローカル開発環境などについては以下記事をご参照ください
SAMテンプレート
- テンプレートにSQSとLambdaの構成を記述する
※テンプレートはあくまでサンプルであり適宜必要に応じて修正が必要です
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
migrate-app
Function:
Timeout: 60
MemorySize: 128
Runtime: python3.9
Tracing: Active
Resources:
MigrationAppQueue:
Type: AWS::SQS::Queue
Properties:
QueueName: migration-app-queue
VisibilityTimeout: 60
ReceiveMessageWaitTimeSeconds: 20
MessageRetentionPeriod: 1209600 # 14 days
RedrivePolicy:
deadLetterTargetArn: !GetAtt MigrationAppDeadLetterQueue.Arn
maxReceiveCount: 1 # 1回受信して失敗した時点でDLQへGo
MigrationAppDeadLetterQueue:
Type: AWS::SQS::Queue
Properties:
QueueName: migration-app-dead-letter-queue
MigrationAppFunction:
Type: AWS::Serverless::Function
Properties:
Handler: app.lambda_handler
CodeUri: migration_app
Description: Migration App function
Architectures:
- x86_64
Role: !GetAtt LambdaExecutionRole.Arn
Layers:
- !Sub arn:aws:lambda:${AWS::Region}:133490724326:layer:AWS-Parameters-and-Secrets-Lambda-Extension:11
Events:
SqsEvent:
Type: SQS
Properties:
Queue: !GetAtt MigrationAppQueue.Arn
BatchSize: 1 # 1メッセージずつ受け取る設定
ScalingConfig:
MaximumConcurrency: 2 # Lambda同時実行の最大数を制御する
Environment:
Variables:
POWERTOOLS_SERVICE_NAME: PowertoolsMigrationApp
POWERTOOLS_METRICS_NAMESPACE: Powertools
LOG_LEVEL: INFO
STOP_TIME_FOR_DELETE: 5 # Lambdaコード内でデータ削除間隔を制御(秒)
STOP_TIME_FOR_INSERT: 3 # Lambdaコード内でデータ登録間隔を制御(秒)
Tags:
LambdaPowertools: python
LambdaExecutionRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- 'lambda.amazonaws.com'
Action:
- 'sts:AssumeRole'
Path: '/'
ManagedPolicyArns:
- "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
- "arn:aws:iam::aws:policy/service-role/AWSLambdaSQSQueueExecutionRole"
- "arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess"
- "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
SSMAndKMSAccessManagedPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
ManagedPolicyName: SSMAndKMSAccessManagedPolicy
Path: /
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- 'ssm:Get*'
- 'kms:Decrypt'
Resource: "*"
Roles:
- !Ref LambdaExecutionRole
Lambdaアプリケーションコード
- データマイグレーションする処理を記述します
※今回は詳細なコード記載については割愛します
デプロイ
- AWS環境へデプロイするにはAWS認証情報が必要です
- AWS環境変数やCredentialsファイルやProfileなどいろんな方法があるので各環境に合わせた方法で対応
※デプロイコマンド例
# AWS認証情報を環境変数にセット
export AWS_ACCESS_KEY_ID="xxxxxxxxxxxxxxxx"
export AWS_SECRET_ACCESS_KEY="xxxxxxxxxxxx"
export AWS_SESSION_TOKEN="xxxxxxxxxxxxxxxxxxx"
# デプロイパッケージの作成
sam build
# AWSへデプロイ(今回IAM作成があるのでオプションを指定)
sam deploy --capabilities CAPABILITY_NAMED_IAM
マイグレーション実施
SQSへ処理対象をキュー
※SQSへメッセージ送信例
(実際はスクリプトを作成し処理対象分を一気にSQSへキューしています)
# 最大10件メッセージ送信するコマンド
aws sqs send-message-batch --region ap-northeast-1 --queue-url "$queue_url" --entries "$batch_messages"
上記メトリクスから一気に数万件の処理対象が送信されたことが分かります
Lambdaが実行されているか確認
- Lambdaのメトリクスやログから実行が問題ないか確認
上記メトリクスからエラー無く全ての処理を実行し終えたことが分かります
DB負荷確認
- RDSのメトリクスやログから実行が問題ないか確認
上記メトリクスからマイグレーション実施期間の「8月29日」から「9月1日」において CPU使用率
の変化は殆ど見られないことが分かります
意図通りの負荷で処理を完了させることが出来ました
最後に
無事にDBに負荷をかけることなくデータマイグレーションを完了させることが出来ました。
SQSとLambdaのパターンを用いることで「並列実行」「同時実行」などの本来複雑な部分をAWS側の設定で簡単に制御させることが出来るのは非常にありがたいです。
今回検討したマイグレーション方法以外にも、AWSには多様なマイグレーションサービスやツールが用意されているため、ケースに応じた選定がとても大事になると思います。
- AWS DMS
- AWS Glue
- RDSからS3へのエクスポート
などなど
今回の方法が何かしらのお役に立てばと思います。
Discussion