スタイル・エッジ技術ブログ

士業集客支援/コンサルティングのスタイル・エッジのエンジニアによるブログです。

Amazon S3にある大量のオブジェクトをGoogleドライブに移行しました

はじめに

こんにちは!システム事業部のNTです。

ある日、クライアント様から「スタイル・エッジ提供のシステムに保存されているファイルを、Googleの共有ドライブに移行したい」というご相談をいただきました。

システムに登録されている現状のファイル数を確認すると約50万にもなり、共有ドライブに移行するにあたっては作業時のパフォーマンスが大きな課題となりました。
また、システム上で表示されているファイル名と、ストレージとして利用しているAmazon S3に保存されている実際のオブジェクト名が異なるため、移行時にオブジェクトのリネーム処理を挟む必要があることも課題でした。

上記2つの課題を克服しながら進めた オブジェクトのリネーム作業Googleドライブへの転送作業 について、使用したサービスと具体的な手順をご紹介します!

generated by DALL-E3

利用したサービスについて

移行作業をスムーズに進めるために、今回は以下4つのサービスを組み合わせて活用しました。

  • Amazon S3バッチオペレーション + AWS Lambda
  • Rclone + Google Drive API (Google Cloud)

Amazon S3バッチオペレーションとは

Amazon S3バッチオペレーションは、S3上に格納されているオブジェクトに対して一括操作を実行できるサービスです。
大規模なデータセットの一括処理が必要な場合に特に有効で、具体的には以下のようなケースで利用されます。

  • 数百万から数十億個のオブジェクトに対して一括コピーや複製をする場合
  • オブジェクトへのタグ付与や削除、メタデータの変更等を一括で行う場合

S3バッチオペレーションでは、操作対象のオブジェクトを指定するためにマニフェストファイル(CSV形式やS3インベントリレポートなど)を使用します。

実際のジョブを設定する際には、マニフェストファイルの指定やLambda関数の呼び出しといった操作を行います。
Lambda関数を利用することで、オブジェクトのリネームやフィルタリングなどの柔軟な操作が可能です。 aws.amazon.com

Rcloneとは

Rcloneは、クラウドストレージ上のファイル管理を行う、オープンソースのコマンドライン型プログラムです。
UNIXの rsynccp に相当するコマンドを使用でき、ファイルの転送、コピー、バックアップ、暗号化などの機能を有しています。
Amazon S3やGoogle Drive、Dropbox等、標準的なサービスを含めて70を超えるクラウドストレージに対応しています。

rclone.org

今回、Rcloneの利用を決めた理由は大きく二つあります。

  • 並列でのファイルアップロードが可能で、転送速度の高速化が期待できる
  • スクリプトやコマンドラインから容易に操作ができ、エラーハンドリングやリトライ機能が充実している

今回は移行対象のファイル数が多かったため、並列実行可能な点は特に魅力的でした!

移行計画について

移行環境

  • 移行元: Amazon S3バケット
  • 移行先: Googleドライブ(クライアント様指定の共有ドライブ)
  • ファイル数: 約50万ファイルで、合計サイズは約180GB。
  • 注意点: S3上のオブジェクト名を、システム上の名前に合わせてリネームする必要がある

ざっくり移行手順

  1. S3上のオブジェクトをリネーム
    S3バッチオペレーションでマニフェストファイルを読み込み、Lambda関数を呼び出してオブジェクトを別バケットにコピーし、リネームします。
  2. S3からGoogleドライブへ転送
    Rcloneを使用してS3からGoogleドライブに転送を行います。

移行準備

S3バッチオペレーションの設定

1. マニフェストファイルの作成

マニフェストファイルを作成するために、下記の情報を取得します。

  • 変更前のファイル名(システム上のファイル名)
  • 変更後のファイル名(S3 上のオブジェクト名)
  • S3 バケット名

取得した情報を基に、CSV形式のマニフェストファイルを作成します。

なお、マニフェストファイルは、「オブジェクトのS3バケット名」と「オブジェクトキー」、「オブジェクトバージョン(※オプション)」という3カラムからなる CSV ファイルです。
オブジェクトキーにはURLエンコードされたJSON文字列を利用でき、後続のLambda関数にパラメータを渡すことが出来ます。

ここでは、「オブジェクトのS3バケット名」と「オブジェクトキー」の2つを指定しています。
システム内のDBに全ての情報が揃っていたので、SELECT文で取得して整形しました。

"S3バケット名","変更前ファイル名,変更後のファイル名"
"s3-bucket","{s3Key:test/フォル1/変更前.xlsx,newKey:test/フォル1/変更後.xlsx}"

次に、Lambda関数に値を渡すために「"変更前ファイル名,変更後のファイル名"(オブジェクトキー)」をURLエンコードしたJSON文字列に変換します。

"s3-bucket","%7B%22s3Key%22%3A%22test%2F%E3%83%95%E3%82%A9%E3%83%AB%E3%83%801%2F%E5%A4%89%E6%9B%B4%E5%89%8D.xlsx%22%2C%22newKey%22%3A%22test%2F%E3%83%95%E3%82%A9%E3%83%AB%E3%83%801%2F%E5%A4%89%E6%9B%B4%E5%BE%8C.xlsx%22%7D"

Lambda関数には、下記のようなJSONがS3 バッチオペレーションより連携されます。

{
"invocationSchemaVersion": "1.0",
    "invocationId": "YXNkbGZqYWRmaiBhc2RmdW9hZHNmZGpmaGFzbGtkaGZza2RmaAo",
    "job": {
        "id": "f3cc4f60-61f6-4a2b-8a21-d07600c373ce"
    },
    "tasks": [
        {
            "taskId": "dGFza2lkZ29lc2hlcmUK",
            "s3Key": "%7B%22s3Key%22%3A%22test%2F%E3%83%95%E3%82%A9%E3%83%AB%E3%83%801%2F%E5%A4%89%E6%9B%B4%E5%89%8D.txt%22%2C%22newKey%22%3A%22test%2F%E3%83%95%E3%82%A9%E3%83%AB%E3%83%801%2F%E5%A4%89%E6%9B%B4%E5%BE%8C.txt%22%7D",
            "s3VersionId": "1",
            "s3BucketArn": "arn:aws:s3:::S3-bucket"
        }
    ]  
}

docs.aws.amazon.com

2. Lambda関数の作成

S3バッチオペレーションから連携されたJSON内に記述された変更前後のファイル名を取得し、オブジェクトを別のS3バケットにコピーするLambda関数を作成します。
JSON内の s3Key には、マニフェストファイルの "変更前ファイル名,変更後のファイル名" の値が格納されており、
この値をデコードすることで、S3バッチオペレーションから任意のオブジェクト名を指定することが可能になります。

import json
from datetime import datetime
from urllib.parse import unquote
import boto3

s3 = boto3.client('s3')

destination_bucket = 'コピー先のバケット名を指定' # 簡略化のため、定数として定義

def lambda_handler(event, context):
    print('Loading function')

    date = datetime.utcnow()
    print('invoke: ' + date.isoformat())

    results = []

    for task in event['tasks']:
        try:
            task_id = task['taskId']
            s3_key_encoded = task['s3Key']
            bucket_arn = task['s3BucketArn']
            source_bucket = bucket_arn.split(':')[-1]

            s3_key = unquote(s3_key_encoded)
            print(f'decoded s3Key: {s3_key}')

            key_data = json.loads(s3_key)
            original_key = key_data['s3Key']
            new_key = key_data['newKey']

            copy_source = {'Bucket': source_bucket, 'Key': original_key}
            s3.copy_object(CopySource=copy_source, Bucket=destination_bucket, Key=new_key)
            print(f'Copied {original_key} from {source_bucket} to {new_key} in {destination_bucket}')

            results.append({
                'taskId': task_id,
                'resultCode': 'Succeeded',
                'resultString': s3_key
            })
        
        except json.JSONDecodeError as e:
            print(f'JSONDecodeError: {str(e)}')
            results.append({
                'taskId': task_id,
                'resultCode': 'Failed',
                'resultString': f'JSON decode error: {str(e)}'
            })
        except Exception as e:
            print(f'Exception: {str(e)}')
            results.append({
                'taskId': task_id,
                'resultCode': 'Failed',
                'resultString': f'Error: {str(e)}'
            })

    return {
        'invocationSchemaVersion': '1.0',
        'treatMissingKeysAs': 'PermanentFailure',
        'invocationId': event['invocationId'],
        'results': results
    }

なお、今回は一時的に別のS3バケットにコピーしてからリネームする方式としましたが、別バケットにコピーすることで次のメリットがあります。

  • 既存バケット内のオブジェクトに影響を与えず、システムを稼働しながらでも安全なリネームが可能
  • 既存バケットとは異なったディレクトリ階層でのオブジェクト保存が可能

Google Cloudの設定とRcloneの準備

1. Google Cloudの設定

Google CloudコンソールからGoogle Drive APIを有効化し、Rclone用のサービスアカウントを発行します。

サービスアカウントとは、ユーザーではなくアプリケーションに使用されるアカウントを指しています。
アカウント固有のメールアドレスで識別も可能です。 cloud.google.com

2. Rcloneの準備

EC2上にRcloneをインストールし、必要な設定を行います。
Google Cloudコンソールで発行したサービスアカウントのキーをRcloneの設定ファイルに追加します。
rclone.org

3. 共有ドライブの作成

今回は、後述する理由により移行先の共有ドライブを複数用意していただきました。
各ドライブのメンバーとして、Rclone用サービスアカウントのメールアドレスを追加します。
サービスアカウントを追加することで、Rcloneでのファイル転送が可能になります。

移行時の課題点と対応策

課題①:Lambda関数の同時実行数の制限

AWS Lambdaの同時実行数は、デフォルトで1,000に制限されています。
操作対象のオブジェクト数が多い場合には同時実行数の上限を超えてしまう可能性があるため、サービスクォータの引き上げも視野に入れる必要があります。

今回は10万ファイルごとに操作し、同時実行数は最大で1,040ほどになりました。
デフォルトの1,000を超えてしまいましたが、念のためにサービスクォータを引き上げていたので、問題にはなりませんでした!

課題②:Google 共有ドライブの制約

共有ドライブの最大アイテム数上限が約50万のため、すべてのファイルを1つのドライブに移行することは不可能でした。 support.google.com

今後ファイルが増加することも見越し、今回は複数の共有ドライブに分散して移行しました。

おわりに

移行作業には、以下の時間を要しました。

  • ファイルのリネーム:約10万ファイルを処理するのに、30~45秒
  • Rcloneでの転送:並列転送を実施したことにより、約15時間

特にリネーム処理は、想定していた以上に時間が短く衝撃でした!
大量のS3オブジェクトの操作や移行時は、S3バッチオペレーションやRcloneの利用を検討してみてはいかがでしょうか?

スタイル・エッジでは、一緒に働く仲間を絶賛大募集しています。 もし興味を持っていただけましたら、以下の採用サイトも一度覗いてみてください! recruit.styleedge.co.jp