AWSアカウント間のS3, DynamoDBデータ移行計画の記録 (背景と転送方法の検討)

2021.06.09

はじめに

こむろ@事業開発部です。

今回、5年以上稼働していた SpringBoot アプリケーションを エンドユーザーへの影響を最小にして、別のAWSアカウントへ完全に移行させました。その調査、検討、実施した際の想定外のトラブルやその対処について記録します。

私は主に全体のとりまとめを担当し、アプリケーション内で使うデータの関係性のチェックや移行作業全体のスケジュール作成、切り戻し計画の作成、メンバーへのタスク依頼、顧客調整等々を行いました。

概要

全体が無駄に長くなってしまったのでまとめると以下になります。

  • Elastic Beanstalk (1 Container Docker)で動作していた Spring Boot アプリケーションを、別の AWS アカウントの Fargate 環境へ引っ越し
  • 作業時間は最大 5 時間
    • 転送はもちろん、データの完全一致の保証も含めて
  • 移行対象データの特徴
    • 同一である保証が必要なデータは1100万件。DynamoDB のみ
    • できれば転送したいデータは 1億6000万件。DynamoDB, S3, ElastiCache
  • 稼働アプリケーションの全データ(DynamoDB, S3, ElastiCache for Redis)の AWS アカウント間転送の方法を検討して実行
    • 検討したものの S3, ElastiCache for Redis データの移行は未実施
  • (次回) データの完全性検証の手法の検討
  • (次回) 実施時の想定外のトラブルと対処方法

以降は、細かい背景やこんなこと調べましたという詳細になるので、ご興味がある方のみ読み進めていただければと思います。

移行の背景

5 年以上稼働している Elastic Beanstalk 上に展開している SpringBoot ベースの Java アプリケーションをほぼ稼働当初から二人で運用・メンテナンスしていました *1。様々な障害等も乗り越え、移行当時には常時、分間数万から10万アクセスほどのリクエストを捌いていました。しかし、一人しか運用者がいないことは長年 SPOF として認識されており、決して好ましい状況とは言えません。そのため、運用チームが組める体制への移行が急がれていました。

運用していたアプリケーションはすでに 5 年以上稼働、さらに常時それなりのリクエストがあるため、アプリケーションが蓄積・管理しているデータは数千万から数億に上ります。一つ一つのデータサイズは小さいのですが、そこそこの量です。ほとんど文字列情報が主であり、画像データ等のバイナリデータはありません。

上記運用上の問題(それ以外にも色々ありますが割愛)から、今回 SpringBoot アプリケーションを稼働するプラットフォームを Fargate へ変更し、さらに別の AWS アカウントに移行するため移行計画が必要になりました。既存の AWS アカウントに蓄積しているデータを移行先へ引っ越しさせ、そこで以前と変わらない動作となるよう保証します。

移行の要件

今回の移行では、以下のような要件がありました。

  • エンドユーザーへの影響を最小限に抑える(MUST)
  • 移行作業に費やせる時間は最大5時間(MUST)
  • 重要情報は移行前と移行後で 1 バイトの差分も許されない(MUST)
  • 認証状態を保持したい(WANT)

これら要件を満たすように移行計画を作成します。

MUST 要件を満たすのはもちろんですが WANT の要件を計画に含めるには、全体でどの程度のコスト(時間、料金)が増えるかを把握し、移行計画を複数作成する必要があります。

データ移行の全体像

移行するシステムのふんわりとした全体像は以下のとおりです。

移行元環境の Elastic Beanstalk で動作している Spring Boot アプリケーションは、データ管理には以下のマネージドサービスを利用しています。

  • Amazon DynamoDB
  • Amazon S3
  • Amazon ElastiCache for Redis

アプリケーションで利用するデータは、これらのサービスから取得し、復元されます。元々スケールする前提で作られているため、サーバー内にデータを溜め込むことはありません。そのため、これらのデータを正確に移行先の環境に転送すれば、移行先のアプリケーションでも同じ状況を復元することが可能です。

移行元、移行先アプリケーションは共に Docker Image から起動されたコンテナで稼働しており、同じバージョンを起動しています。そのため環境によるアプリケーションの差分はありません。あくまで起動しているプラットフォームの違いのみになります。

実際には、これらの周辺に連携システムが多数あり、Lambda や SQS 等で接続されている部分もあるのですが、データ移行のフォーカスから外れるため、意図的に省略しています。

転送対象となるデータについて

各種サービスを利用して保管しているデータの概要と関連は以下のようになります。

実際の項目は諸事情により一部マスクしています。

データ関連概要図

重要データα, βはそれぞれ、同じパーティションキー(hashId)を持ちますが、DynamoDB 上では別テーブルで管理しています。重要データβの方は、基本的にフラグ等の情報が多く、αとβの 1 アイテムごとのデータサイズはおよそ 5:1 。α側の方が 1 つのアイテムにつき多くの情報を保持しています。

ログイン情報は重要データα, β と直接リンクはしていませんが、α, β と必ずセットで必要な情報です。

重要データα, β, ログイン情報は、 3 つ正しく揃って始めてアプリケーションで正しく状況を復元できる関係を持っています。どれか 1 つでも欠損していると、アプリケーション側で正しく復元することができません。こちらデータの転送対象は全てで1100万件程度です。

もう一方のデータの組み合わせがあり、こちらは認証情報になります。認証情報は DynamoDB の 1 つのテーブルで管理されたマスタデータを中心に、大きめのオブジェクトは S3 で管理します。テーブル内のアイテムには、S3 Key が記載されており、間接的に関連を持ちます。ElastiCache は Redis をエンジンとして利用し、テーブルの情報とオブジェクト情報をそれぞれ Cache します。

こちらも、それぞれのデータに関連性があり、DynamoDB のテーブル情報と S3 のオブジェクト情報は完全に揃っていなければアプリケーション上で正しく復元できません。ElastiCache についてはそれぞれの Cache 情報であるため、データは存在しなくてもアプリケーションの動作に影響はありません。こちらデータの転送対象は全てで1億6000万程度です。

これらがそれぞれ転送対象の候補となるデータセットです。

要件とデータの関連

要件とそれぞれ転送対象となるデータの関連について確認します。

重要データα, β, ログイン情報のデータセットは、要件の MUST に相当する「重要情報は 1 バイトの差分も許されない」に関連するため、転送対象となります。さらに移行元と移行先のデータが完全一致していることを担保する必要があります。

認証情報のデータセットは要件の「認証状態を保持したい」に関連します。こちらはMUSTではありませんが、できれば実現したい要件の一つであるため、どれくらいの作業時間が必要かを検証する必要があります。あまりにも作業時間が長くなってしまう場合、MUST な要件の「移行作業に費やせる時間は最大5時間」 を満たせない可能性があります。

データ転送手法の検証

データ転送にはいくつかの方法があります。その中で以下を検証する必要がありました。

  • データ転送手法の確立
    • マネージドサービス or 自前での転送ソリューションの構築
  • データ転送完了までの時間予測
  • 並列実行の有無

データ転送手法の確立

まずはマネージドサービスによるAWSアカウント間のデータ転送が可能かを探ります。

クロスアカウントによるデータ転送の方法は、それぞれのサービスである程度手法を確認することができます。

Amazon S3 の転送手法の検証

Amazon S3 のデータ転送では、およそ1億6000万件を短時間で完全に転送する必要があります。

しかし、件数が非常に多いため全体を一度に 5 時間以内で転送しきるのは難しいと考えました。そこで以下のような戦略で転送できないかを検討します。

S3データ移行概要

事前移行作業のチェックポイント時に存在する大部分のオブジェクトを事前に転送しておき、作業当日には事前転送した際のスナップショットからの差分のみを転送します。

これにより 1億6000万件全てを一気に転送する必要がなくなります。ただし、事前に移行したオブジェクトは作業当日までの時間差で削除されている可能性もあるため、単純な増分ではありません(削除されたものも差分の件数として現れるはず)

転送手法の候補として以下を調査しました。

  • 作業当日の転送: s3 sync
  • 事前の転送: S3 Batch Operations

S3 の Object Replication も検討したのですが、クロスアカウントでの既存のオブジェクトの Replication はサポートへの連絡が必要なことや、どれくらいで対応されるかが不明なため、検討から外しました。

s3 sync

まず s3 sync によるデータ転送については簡易的に計測した結果が以下になります。巨大なEC2インスタンスを立て、その中でコマンドを実行して計測しました。

$ nohup aws s3 sync s3://test-replication-records-20201028/t/ s3://test-restore/t/ > 202011061740-s3-sync.txt &

効率を上げるため AWS CLI S3 Configuration こちらを参照して並列実行数を変更しています。

1600万件

時刻 イベント
15:25:31 +0900 s3 sync 開始
17:21:58 +0900 s3 sync 終了
$ stat 202011061740-s3-sync.txt
  File: ‘202011061740-s3-sync.txt’
  Size: 235585614   Blocks: 524160     IO Block: 4096   regular file
Device: 10301h/66305d   Inode: 128914      Links: 1
Access: (0664/-rw-rw-r--)  Uid: ( 1000/ec2-user)   Gid: ( 1000/ec2-user)
Access: 2020-11-09 06:25:31.835868930 +0000
Modify: 2020-11-09 08:21:58.260641221 +0000
Change: 2020-11-09 08:21:58.260641221 +0000
 Birth: -
  • 空のBucket へ転送。差分は1600万件ほど
  • 転送時間は2時間ほど
  • aws s3 ls で件数確認
Total Objects: 759290
   Total Size: 741.5 MiB
  • 75万件しか転送されていない

大量のデータを一気に同期しようとすると厳しいようです(いろいろなチューニングの結果で改善する可能性も高いです。)

S3 Batch Operations

S3 Batch Operations は非常に有用だったのですが、計測してみると実行時間に不安が残る結果でした。

300万件

時刻 イベント
2020/11/06 09:48:54 PM JST ジョブ作成
2020/11/06 09:55 ジョブ準備完了
2020/11/06 09:57 ジョブ実行開始
2020/11/06 11:02:57 PM JST ジョブ完了
  • 件数は 3000000
  • エラーは0
  • 300万件で1h

1600万件

時刻 イベント
2020/11/08 10:11:15 PM JST ジョブ作成開始
2020/11/08 10:23:00 PM JST 作成終了
2020/11/08 10:25:00 PM JST ジョブ実行開始
2020/11/09 03:41:12 AM JST ジョブ完了
  • 件数は 15958819
  • エラーは0
  • およそ5h
  • およそ5倍のデータ量で 5 倍の時間がかかっており、データ数に応じて線形に所要時間が増加することが予想できる。

その他

S3 Batch Operations は Amazon S3 Inventory のデータを利用して転送するため、この Inventory Report の出力がいつ行われた事が重要です。従って、Inventory Report 出力後に変更されたデータに関しては、Operations の対象にならないため、Batch Operations で大部分を転送しても当日までの時間差で必ず差分が発生します。

従って、S3 Batch Operations を実行し完了までにどれくらい時間がかかるか、その間に変更される可能性のあるデータ数はどれくらいかを予測し、その件数から S3 sync 等での同期にまたどれくらいかかるかを予測する必要があります(これが当日の作業時間となる)

上記結果から

最大で1600万件のデータ転送を実行していますが、転送時間が線形に伸びてしまっていたため、単純計算で1億6千万件の転送完了には50時間以上かかると予想しました。従って、S3 Batch Operations を実行して大部分を同期するにはおよそ3日。当日作業ではアプリケーションを停止し、データの静止状態をキープした上、 3 日間の更新分を S3 Sync 等によって最終的に完全に同期させるという二段構えの作業が必要です。

データ構造の概要で説明した通り、認証情報はこの S3 の情報が揃っていなければ正常に復元できません。大部分の移動+差分同期で完全に移行できることを確認するには、事前に大部分を移行したとしても、作業時間 5 時間という枠の中で作業するのはかなりギリギリになり、件数の検証やデータのチェックなどを行うのは難しそうです。

ElastiCache の転送手法の検証

ElastiCahce については以下のドキュメントに従って手法を検証したところ、問題なく転送、復元できることが確認できたため、作業時間の検証のみです。

移行元環境で稼働している ElastiCache のノードで利用しているのは、およそ 9 GB弱。 10 GB のダミーデータを作成し、以下を検証しました。

  1. Snapshot の作成
  2. S3 Bucket へのコピー
  3. 移行先の S3 Bucket へ転送 ※1
  4. 移行先の S3 Bucket へ転送した Snapshot から Cache クラスタを作成し、復元させる

Snapshot の作成におよそ15分ほど。Snapshot から Cache クラスタの生成まではおよそ10分ほどで完了しました。そのため、長くても 1 時間以内には転送作業が完了できそうと判断しました。

注意事項としては、 Cache ノードを起動する際に利用する Seed の .rdb ファイルへ、ElastiCache がアクセスできるよう S3 側の Permission 設定を操作してやる必要があります。この手順を正常に行わないと、Seed を読み取ることができず、ノードの起動に失敗します。

※1 の部分ですが、移行元の S3 Bucket の Snapshot を直接クロスアカウントで参照して Cache クラスタを構成する方法も調査したのですが、うまく行かず断念しました。

DynamoDB の転送手法の検証

DynamoDB のデータ転送には以下の手順検証が必要になります。

  1. 移行元のデータをどのように取得するか
  2. 移行先のDynamoDB へどのように転送するか

それぞれ検証します。

移行元データの取得について

DynamoDB テーブルをある AWS アカウントから別のアカウントに移行するにはどうすればよいですか?

こちらのリンクにあるように、いくつかのマネージドサービスが利用できそうでしたが、ちょうど手法検証中のタイミングで以下が発表されました。

新機能 – Amazon DynamoDB テーブルデータを Amazon S3 のデータレイクにエクスポート

DynamoDB のテーブル情報を Point In Time Recovery (PITR) を利用して、S3 にダンプすることができるようになりました。今回の移行で MUST になっている重要データのテーブルにはそれぞれすでに PITR の設定がされており、こちらは問題なく利用できそうです。

DynamoDB Export to S3 の検証

軽く検証してみたところ、PITR からの復元であるため、既存テーブルの RCU, WCU に影響を与えることなく S3 にすべてのデータを Export できることを確認しました。

検証では実際の件数に近いデータ量を Export するのにどれくらいかかるかを計測しました。件数が増えると線形に処理時間が伸びるかを確認します。

フォーマットは DynamoDB JSON を指定します。

データ数 1100 万件

1100 万件のダミーデータを作成したテーブルを 3 つ同時に Export を実行して計測した時間になります。データ量は 10 GB になります。

時刻 イベント
23:21:33 DynamoDB テーブルデータ Export 開始
23:40:00 テーブルデータ Export 完了

およそ 20 分前後で終了しています。予想以上に早いです。PITR から復元しているため、ある時点からのスナップショットからの復元になります。従って、アプリケーションを完全に停止しデータが静止状態になっていることを確認してから実施する必要があります。

データ数 2 億件

2 億件のダミーデータを作成したテーブルを 1 つ作成し、 Export を実行して計測した時間になります。データ量は 60 GB になります。

時刻 イベント
22:12:26 DynamoDB テーブルデータ Export 開始
22:35:06 テーブルデータ Export 完了

こちらもおよそ20分前後で終了しています。データ数は 20 倍以上、データ量も 6 倍近くあっても Export 時間は一定でした。

少々サンプル数が少ないですが、データ数、量ともに変化があってもさほど処理時間に影響を与えるものではないようです。処理時間が変わらないのは計画がしやすく非常に助かります。

転送方法の検討

前述した Export to S3 で出力した DynamoDB JSON フォーマットのデータを読み込んで、登録を行います。転送方法にはいくつか紹介されていたのですが、今回は自前で Worker アプリケーションを構築して移行先の DynamoDB テーブルへ登録します。理由としては以下になります。

  • Write Capacity Unit が On-Demand Capacity で設定されているため、ある程度意図的に書き込みイベントのスロットリングを発生させる必要がある。
  • 限界まで Write Capacity Unit を上げるためにスロットリングは発生しつつ、書き込みエラーイベントを極力抑えるための細かい調整が必要なため。

例えば過去検証した中で Data Pipeline は対象の DyanmoDB テーブルの Capacity Unit が On-Demand で設定されていると、お行儀よく転送するためか、 Capacity Unit の消費が 1 に抑えられてしまい、全く速度が出ませんでした。スロットリングイベントが重要であることが経験上わかっていたため、転送手法としては自前でエラー直前まで叩く方法を採用しました。

DynamoDB テーブルのデータを移行先に最短で叩き込む Worker アプリケーションの全体概要図は以下になります。

DynamoDB のバックアップしたデータはそれぞれ独立しているため、いくらでも並列して登録処理をしても問題ありません。また、1 つのアイテムを登録していくため冪等性が担保されています。従って、万一エラーが発生し、登録処理に失敗しても対象を記録しておくことで、登録処理を再実行することが可能です。

DynamoDB テーブルへの登録とスロットリング管理

On-Demand Capacity に設定したテーブルは徐々にスケールするため、初手から全力で登録を始めると大量の Throttling Event が発生し、データ登録に失敗する可能性があると考えました。そこで徐々に登録速度を上げ、ある程度の Write Throttling Event を発生させつつ、登録エラーが発生しないよう管理するようにしました。

あくまで、我々の今回の目的のために条件を設定して計測したものですので、汎用的な結果ではない可能性があります。

Write Capacity Unit の管理

On-Demand Capacity が設定されているテーブルは Capacity Unit がスケールします。動作の概要については以下の公式ドキュメントを参照します。

Amazon DynamoDB - 読み込み/書き込みキャパシティーモード

意図的に負荷をかけて Write Capacity Unit をスケールさせながら、データの登録速度を徐々に上げていく必要があります。目標とする Write Capacity Unit の値に到達するまで、欠落しない程度の Throttling Event を発生させつつ、目標値に達した段階では Throttling Event が 0 になっていることが理想です。

またプログラム内での DynamoDB のデータ登録には batchitemwrite を利用しており、 1 度のリクエストで 20 件同時に登録するようにしています。

目標とする Capacity Unit について

1 テーブルあたりの Capacity Unit の Quota は40000 です。

Service, Account, and Table Quotas in Amazon DynamoDB

この値を上限値として動作が安定するギリギリを計測して見極めていきます。検討する上で以下を重点的に確認しています。

  • 登録元の件数と登録先の件数が完全に一致すること
  • 書き込みシステムエラーがなるべく少ないこと

書き込みシステムエラーは、発生すると登録失敗としてリトライされます。従ってこのエラーが多すぎると何度もリトライすることになり、登録処理の効率が落ちてしまうため、エラーの発生は許容するもののなるべく安定して登録できるようにしています。

結果

計測の結果 Producer を 32 コンテナ、 Worker を 50 コンテナで起動し、1コンテナ内15プロセスを同時に実行して登録するのが一番効率が良い転送方法でした。

1 億件のダミーデータを登録する際の結果が以下になります。

  • 2 時間 20 分で登録完了
  • 件数完全一致
    • 登録元、登録先テーブルを Fullscan して計測

Capacity Unit, Throttling Event, Write System Error のグラフは以下のとおりです。

Write Capacity Unit 15000 弱を目標値として登録処理を行っています。

一部グラフが降下している箇所については、想定以上に Index が分散し、 Worker 側の処理が詰まってしまった結果、並列して登録する処理が一時的に減少し、それが Capacity Unit のスケールインにつながってしまったと考えています(詳細は未検証です)

これ以上の Write Capacity Unit の値を目標値として計測も行ったのですが、線形に処理時間が減少するわけではないWrite System Error が増大してしまいリトライ対象が増え効率がよくなさそう、という計測結果があったため、一旦この値を目標値として設定しました。

計測結果が正確に記録されていなかったため、提示することができず大変申し訳ありません :pray:

Worker の設定値について

上記の計測結果から、登録 Worker は以下のような設定値で実行することにしました。

  • Producer=32 コンテナ、Worker=50 コンテナ
  • Write Capacity Unit の目標値 15000
  • 1 億件のデータ投入の最大時間 2 h 20 min

この値をもって転送計画に組み込みました。

重要データを転送するために必要な時間

上記検証から、重要データに関連する 3 つのテーブルを転送する時間についてまとめると以下のようになります。

  • DynamoDB
    • 1100 万件のデータ Export に 20 分 + データ転送に 20 〜 30 分
    • 1億件のデータ転送から単純計算するとおよそ 15 分ほどですが、実際に1100万件の転送を計測したところ 20分 ほどかかっていたため、バッファをとって 30 分としています。
    • テーブル数は 3 つありますが、念の為並列で作業するのは最大 2 テーブルで 2 回に分けて転送を行うため(30 分 x 2回)、およそ 1 時間 20 分要すと算出しました。
    • さらに後述するデータ完全性チェックの時間と、万一差分が出た場合のリカバリのためのバッファを加えます。
    • 1 % 程度の差分があった場合(100000 件ほど)を想定しデータ再投入のリトライ時間を考慮し、1.5 時間のバッファを積みました。
    • 従って 1100 万件のデータには 2 時間 50 分としました。

バッファが比較的多めなのは、データの完全性を求められているため、後述するチェックを実行する必要があるのと、万が一データ欠落や不整合が発生した場合、確実にリカバリを実施する必要があるのが理由です。

認証情報を転送するために必要な時間

上記検証から、ボリュームとしては最大の認証情報の転送時間についてまとめると以下のようになります。

  • Amazon S3
    • 3 日分の差分を 2000万件ほどと想定
    • 事前の転送に3日ほど + S3 Sync で 2000万件 で 2時間強
  • ElastiCache
    • Export から復元まで最大1時間
  • DynamoDB
    • 1億件 のデータ Export に 20分 + データ転送に 2 時間 20 分

すべてを並列で実行したとして認証情報の転送に最大で 2 時間 30 分ほどの時間を計画にいれる必要があります。

本来は件数が合致することも確認したいのですが、 DynamoDB はまだしも、 S3 のアイテム数カウントを短時間で行う良い方法がどうしても見つからなかったため、認証情報に関しては件数チェックもスキップしています。

そのため、DynamoDB, S3 いずれかにデータ欠落や不整合が起きていたかどうかは、アプリケーションを実行し、該当データにアクセスするまで分かりません。

「重要データの転送が一発ですべてスムーズに行った場合に限り、認証情報の転送についてはギリギリ完了できそう」くらいの感覚値になりました。バッファ時間を少しでも使うと破綻しかねないギリギリの計画になります。 *2

今回は失敗が許されないため *3、一発OKで通らないと計画が破綻するようなタイムスケジュールはできるだけ避けたいというのが本音です。

1億件を超えるデータの同一であること、件数チェックも難しいことからもし転送が完了したとしても動作の保証ができません。

動作が保証できないものに対して、ギリギリの計画となるリスクをかけて転送を行うかを検討、調整の結果、認証情報については転送対象から除外させてもらいました。

ここまでのまとめ

ここまでをまとめると以下になります。

  • Amazon S3 の転送方法の検討と想定される時間の計測 → 完了
  • Amazon ElastiCache for Redis の転送方法の検討と想定される時間の計測 → 完了
  • Amazon DynamoDB の転送方法の検討と想定される時間の計測 → 完了
  • 転送対象の絞り込み → 完了
    • 重要データ、ログイン情報の3テーブルにフォーカスを絞る
    • 認証情報は除外
  • データ完全性検証の方法検討 → 未着手
  • 全体のスケジュール作成 → 未完成
  • 転送作業の実施 → 未着手

小休止

長くなってきたのでひとまずこの辺で一旦区切ります。続きは

  • 1100万件 x 3 テーブルの完全性チェック方法の検討
  • 全体のスケジュール概要
  • 計画の実施と実行して初めて分かる現象とその対処について

を記載する予定です。

参照

脚注

  1. 諸事情により途中から一人体制。
  2. そして、実際に重要データ転送時に何か起こっています。ギリギリの計画を採用しなくて本当に良かった。
  3. 様々な要因が元ですでに何度かリスケされていて、元の移行計画時期から6ヶ月ほど遅延しているため。