[小ネタ] Python(Boto 3)で5GB以上のS3オブジェクトをコピーするときはcopyメソッドを使おう

Python(Boto 3)で5GBを超える容量のS3オブジェクトをコピーする場合には、copy_objectメソッドではなくcopyメソッドを使いましょう。copy_objectメソッドでは5GB以上のS3オブジェクトをコピーしようとしてもエラーとなってしまいます。
2020.02.29

はじめに

清水です。タイトル通りなのですが、Python(Boto 3)で5GBを超える容量のS3オブジェクトをコピーする場合には、copy_objectメソッドではなくcopyメソッドを使いましょう。copy_objectメソッドでは5GB以上のS3オブジェクトをコピーしようとしてもエラーとなってしまいます。当初このことを知らずにハマってしまったので、備忘録がてらまとめてみたいと思います。

copy_objectメソッドを使ったS3オブジェクトコピーのコード

まずはPython(Boto3)でS3上のオブジェクトをコピーするのに典型的な、copy_objectメソッドを使ったコードを確認しておきましょう。AWS公式のSDKコードサンプルからの引用です。

import logging
import boto3
from botocore.exceptions import ClientError


def copy_object(src_bucket_name, src_object_name,
                dest_bucket_name, dest_object_name=None):
    """Copy an Amazon S3 bucket object

    :param src_bucket_name: string
    :param src_object_name: string
    :param dest_bucket_name: string. Must already exist.
    :param dest_object_name: string. If dest bucket/object exists, it is
    overwritten. Default: src_object_name
    :return: True if object was copied, otherwise False
    """

    # Construct source bucket/object parameter
    copy_source = {'Bucket': src_bucket_name, 'Key': src_object_name}
    if dest_object_name is None:
        dest_object_name = src_object_name

    # Copy the object
    s3 = boto3.client('s3')
    try:
        s3.copy_object(CopySource=copy_source, Bucket=dest_bucket_name,
                       Key=dest_object_name)
    except ClientError as e:
        logging.error(e)
        return False
    return True


def main():
    """Exercise copy_object()"""

    # Assign these values before running the program
    src_bucket_name = 'SRC_BUCKET_NAME'
    src_object_name = 'SRC_OBJECT_NAME'
    dest_bucket_name = 'DEST_BUCKET_NAME'
    dest_object_name = 'DEST_OBJECT_NAME'

    # Set up logging
    logging.basicConfig(level=logging.DEBUG,
                        format='%(levelname)s: %(asctime)s: %(message)s')

    # Copy the object
    success = copy_object(src_bucket_name, src_object_name,
                         dest_bucket_name, dest_object_name)
    if success:
        logging.info(f'Copied {src_bucket_name}/{src_object_name} to '
                     f'{dest_bucket_name}/{dest_object_name}')


if __name__ == '__main__':
    main()

容量の大きくないファイルであればこちらのコードで問題なく動作します。 INFO: 2020-02-29 08:36:52,952: Copied my-s3-bucket-1/s3-copy-object-test/100M.dummy to my-s3-bucket-2/s3-copy-object-test/100M.dummy

しかし容量の大きな、例えば10GBのファイルの場合は以下のようにエラーとなっていしまいます。(後述しますが上限は5GBです) ERROR: 2020-02-29 08:38:38,402: An error occurred (InvalidRequest) when calling the CopyObject operation: The specified copy source is larger than the maximum allowable size for a copy source: 5368709120

copyメソッドを使ったS3オブジェクトコピーのコード

容量の大きなオブジェクト(5GB以上)のコピーに利用できるのがcopyメソッドです。先ほどのcopy_objectメソッドを使ったサンプルの、copy_object関数部分を変更するかたちで使い方を確認してみると以下のようになります。

def copy_object(src_bucket_name, src_object_name,
                dest_bucket_name, dest_object_name=None):
    """Copy an Amazon S3 bucket object

    :param src_bucket_name: string
    :param src_object_name: string
    :param dest_bucket_name: string. Must already exist.
    :param dest_object_name: string. If dest bucket/object exists, it is
    overwritten. Default: src_object_name
    :return: True if object was copied, otherwise False
    """

    # Construct source bucket/object parameter
    copy_source = {'Bucket': src_bucket_name, 'Key': src_object_name}
    if dest_object_name is None:
        dest_object_name = src_object_name

    # Copy the object
    s3 = boto3.resource('s3')
    try:
        s3.meta.client.copy(copy_source, dest_bucket_name, dest_object_name)
    except ClientError as e:
        logging.error(e)
        return False
    return True

変更箇所がハイライトされていますが、boto3.client('s3')の代わりにboto3.resource('s3')s3.copy_object()の代わりにs3.meta.client.copy()を使う、という具合です。

こちらで例えば10GBのオブジェクトコピーでも問題なく行えます。 INFO: 2020-02-29 08:50:56,931: Copied my-s3-bucket-1/s3-copy-object-test/10G.dummy to my-s3-bucket-2/s3-copy-object-test/10G.dummy

copy_objectメソッドとcopyメソッドの違いを確認

copyメソッドについては、必要に応じてマルチパートコピー、マルチプルスレッドを利用するマネージドな転送方法とのことです。S3についてはオブジェクトの最大サイズは5TBですが、1回のPUT処理でアップロードできるオブジェクトサイズは5GBという制限があります。copyメソッドではコピー対象のオブジェクトサイズが5GBを超える場合は自動でマルチパートアップロードを用いて実現しているようです。

また、それならcopy_objectメソッド自体も、copyオブジェクトのように自動でマルチパートアップロード対応など行われるようになるのか、という点については、copy_objectがraw API methodであるから今後も行われないようです。

まとめ

Python(Boto 3)で5GB以上S3オブジェクトをコピーするときはcopyメソッドを使いましょう。またS3においては、オブジェクトのサイズで5GBはマルチパートアップロードが必須になるか否かの上限となります。他のSDKなどでもS3オブジェクトコピーやアップロードする際には、5G以上に対応しているか(そもそも対応する必要があるかも)、確認しておきましょう。