AWSメモ >

S3へのファイル登録のパフォーマンス比較

概要

S3へのファイル登録時に使用するAPIによってパフォーマンスがどれだけ変わるか検証する

テストの基本的な方針は以下の通り。
・s3 の client や resource オブジェクトなどは極力再利用する。
・ファイルサイズの異なる10個のファイルをアップロードする
・上記のテストを処理毎に10回繰り返す。
・さらに上記のテストを5回繰り返す。
・一時的なネットワーク遅延などを除外する為、各テストとも最速とさ最遅は結果から除く。

結果(平均)

結果からみるとさすがに大きな違いはなかったが、やはり圧縮して扱った方が速い模様。
boto3.resource.meta.client.upload_fileobj が意外と速い。(一時ファイルの作成と削除まで行っているにも関わらず)

処理結果平均1
boto3.resource.Object.put を使用1.1228
boto3.resource.Bucket.put_object を使用1.0804
boto3.client.put_object を使用1.0642
boto3.resource.meta.client.upload_file を使用1.2192
boto3.resource.Bucket.put_object を使用(かつファイル圧縮)0.9866
boto3.client.put_object で属性を指定1.1036
boto3.resource.Bucket.put_object でファイル圧縮しつつ、属性を指定0.992
boto3.resource.meta.client.upload_fileobj を使用(かつファイル圧縮)1.0028

時間をおいて、client.upload_fileobj のファイル圧縮版を加えて計測してみた。
API的にはどれも変わらないように思う(その時のAWSリソース側やネットワーク環境に左右される気がする)
ただ、今回はマシンリソースの消費具合まで見れなかったが、そこも加味して判断したい。とは思う。

処理結果平均2
boto3.resource.Objectのputメソッドを使用1.0612
boto3.resource.Bucket.put_object を使用1.0368
boto3.client.put_object を使用1.068
boto3.resource.meta.client.upload_file を使用1.19
boto3.resource.Bucket.put_object を使用(かつファイル圧縮)1.027
boto3.client.put_object で 属性を指定1.1918
boto3.resource.Bucket.put_object でファイル圧縮しつつ、属性を指定0.956
boto3.resource.meta.client.upload_fileobj 使用(かつファイル圧縮)1.0342
boto3.client.put_object を使用(ファイル圧縮)0.9948

テストスクリプト

テスト処理の雛形

s3_put_test_base.py

import boto3
from datetime import datetime
import decimal
import json
import random
import time
import sys 
import hashlib

class S3PutTestBase:

    BUCKET_NAME = 'test-s3-put-and-get'
    FILE_DIR_PATH = 'dir1/dir2/dir3'
    FILE_PUT_COUNT = 10
    RESULT_FILE = sys.argv[1]
    TIMES = sys.argv[2]
    time_info = []

    @classmethod
    def main(cls):
        cls.print_title()
        stime = time.time()
        for i in range(cls.FILE_PUT_COUNT):
            file_name = f'test{i+1:04d}.json'
            file_key = f'{cls.FILE_DIR_PATH}/{file_name}'
            file_data = cls.get_file_data(i+1)
            cls.upload(cls.BUCKET_NAME, cls.add_prefix(file_key), file_data)
        etime = time.time()
        cls.time_info.append(etime - stime)
        with open(cls.RESULT_FILE, 'a') as f:
            line = f'{cls.TIMES}回目\t' + '\t'.join(list(map(lambda x: str(round(x, 3)), cls.time_info)))
            f.write(f'{line}\n')
            print(f'{line}')

    @classmethod
    def add_prefix(cls, file_key):
        return file_key
        #file_prefix = hashlib.md5(bytes(file_key,'utf-8')).hexdigest()[0:4]
        #return f'{file_prefix}-{file_key}'

    @classmethod
    def print_title(cls):
        res = ''
        try:
            with open(cls.RESULT_FILE) as f:
                res = f.read()
        except:
            pass

        if not res:
            with open(cls.RESULT_FILE, 'a') as f:
                line = 'N回目\t' + '\t'.join([ f'ファイル{i+1}' for i in range(cls.FILE_PUT_COUNT)]) + '\t合計'
                f.write(f'{line}\n')

        if sys.argv[2] == '1':
            line = 'N回目\t' + '\t'.join([ f'ファイル{i+1}' for i in range(cls.FILE_PUT_COUNT)]) + '\t合計'
            print(line)

    @classmethod
    def get_file_data(cls,file_no):
        data = {
            'var1': 'abcdefg',
            'var2': 'xyz1234',
            'data': [{f'key{i+1:08d}': random.randrange(1,100) } for i in range(file_no*100)]
        }
        data_bytes = bytes(json.dumps(data, default=decimal.Decimal), 'utf-8')
        etime = time.time()
        return data_bytes

boto3.resource.Object.put を使用

s3_put_test1.py

import boto3
from datetime import datetime
import decimal
import json
import random
import time
import sys 
from s3_put_test_base import S3PutTestBase


class S3PutTest1(S3PutTestBase):
    """
    boto3.resource.Object.put を使用
    """

    s3 = boto3.resource('s3')

    @classmethod
    def upload(cls, bucket_name, file_key, file_bytes):
        stime = time.time()
        s3_obj = cls.s3.Object(bucket_name, file_key)
        result = s3_obj.put(Body = file_bytes)
        etime = time.time()
        cls.time_info.append(etime - stime)
        return result

if __name__ == '__main__':
    S3PutTest1.main()

boto3.resource.Bucket の put_object を使用

s3_put_test2.py

import boto3
from datetime import datetime
import decimal
import json
import random
import time
from s3_put_test_base import S3PutTestBase


class S3PutTest2(S3PutTestBase):
    """
    boto3.resource.Bucket.put_object を使用
    """

    s3_bucket = boto3.resource('s3').Bucket(S3PutTestBase.BUCKET_NAME)

    @classmethod
    def upload(cls, bucket_name, file_key, file_bytes):
        stime = time.time()
        s3_object = cls.s3_bucket.put_object(
            Key=file_key,
            Body=file_bytes
        )
        etime = time.time()
        cls.time_info.append(etime - stime)
        return s3_object

if __name__ == '__main__':
    S3PutTest2.main()

boto3.client.put_object を使用

s3_put_test3.py

import boto3
from datetime import datetime
import decimal
import json
import random
import time
import sys 
from s3_put_test_base import S3PutTestBase


class S3PutTest3(S3PutTestBase):
    """
    boto3.client.put_object を使用
    """

    s3_client = boto3.client('s3')

    @classmethod
    def upload(cls, bucket_name, file_key, file_bytes):
        stime = time.time()
        result = cls.s3_client.put_object(
            Bucket=bucket_name,
            Key=file_key,
            Body=file_bytes
        )
        etime = time.time()
        cls.time_info.append(etime - stime)
        return result

if __name__ == '__main__':
    S3PutTest3.main()

boto3.resource.meta.client.upload_file を使用

s3_put_test4.py

import boto3
from datetime import datetime
import decimal
import json
import random
import time
import sys 
import uuid
import os
from s3_put_test_base import S3PutTestBase


class S3PutTest4(S3PutTestBase):
    """
    boto3.resource.meta の client.upload_file を使用
    """

    s3_meta_client = boto3.resource('s3').meta.client

    @classmethod
    def upload(cls, bucket_name, file_key, file_bytes):
        stime = time.time()
        tmp_file_name = '/tmp/' + str(uuid.uuid1()) + '.json'
        with open(tmp_file_name, 'wb') as f:
            f.write(file_bytes)

        result = s3_meta_client.upload_file(tmp_file_name, bucket_name, file_key)
        os.remove(tmp_file_name)
        etime = time.time()
        cls.time_info.append(etime - stime)
        return result

if __name__ == '__main__':
    S3PutTest4.main()

boto3.resource.Bucket.put_object を使用(かつファイル圧縮)

s3_put_test5.py

import boto3
from datetime import datetime
import decimal
import gzip
import json
import random
import time
from s3_put_test_base import S3PutTestBase


class S3PutTest5(S3PutTestBase):
    """ 
    s3_bucket.put_object を使用(かつファイル圧縮)
    """

    s3_bucket = boto3.resource('s3').Bucket(S3PutTestBase.BUCKET_NAME)

    @classmethod
    def upload(cls, bucket_name, file_key, file_bytes):
        stime = time.time()
        file_bytes = gzip.compress(file_bytes)
        s3_object = cls.s3_bucket.put_object(
            Key=file_key,
            Body=file_bytes
        )
        etime = time.time()
        cls.time_info.append(etime - stime)
        return s3_object

if __name__ == '__main__':
    S3PutTest5.main()

boto3.client.put_object で 属性を指定してアップロード

s3_put_test6.py

import boto3
from datetime import datetime
import decimal
import json
import random
import time
import sys 
from s3_put_test_base import S3PutTestBase


class S3PutTest6(S3PutTestBase):
    """ 
    boto3.client.put_object で 属性を指定してアップロード
    """

    s3_client = boto3.client('s3')

    @classmethod
    def upload(cls, bucket_name, file_key, file_bytes):
        stime = time.time()
        result = cls.s3_client.put_object(
            ACL='private',
            Bucket=bucket_name,
            Key=file_key,
            Body=file_bytes,
            ContentType='application/json',
            ContentLength=len(file_bytes)
        )
        etime = time.time()
        cls.time_info.append(etime - stime)
        return result

if __name__ == '__main__':
    S3PutTest6.main()

boto3.resource.Bucket.put_object でファイル圧縮しつつ、属性を指定

s3_put_test7.py

import boto3
from datetime import datetime
import decimal
import json
import random
import time
import gzip
from s3_put_test_base import S3PutTestBase


class S3PutTest7(S3PutTestBase):
    """ 
    boto3.resource.Bucket.put_object でファイル圧縮しつつ、属性を指定
    """

    s3_bucket = boto3.resource('s3').Bucket(S3PutTestBase.BUCKET_NAME)

    @classmethod
    def upload(cls, bucket_name, file_key, file_bytes):
        stime = time.time()
        file_bytes = gzip.compress(file_bytes)
        s3_object = cls.s3_bucket.put_object(
            ACL='private',
            Key=file_key,
            Body=file_bytes,
            ContentType='application/gzip',
            ContentLength=len(file_bytes)
        )
        etime = time.time()
        cls.time_info.append(etime - stime)
        return s3_object

if __name__ == '__main__':
    S3PutTest7.main()

boto3.resource.meta.client.upload_fileobj を使用

s3_put_test8.py

import boto3
from datetime import datetime
import decimal
import json
import random
import time
import sys 
import uuid
import gzip
import os
from s3_put_test_base import S3PutTestBase


class S3PutTest8(S3PutTestBase):
    """ 
    boto3.resource.meta.client.upload_fileobj を使用
    """

    s3_meta_client = boto3.resource('s3').meta.client

    @classmethod
    def upload(cls, bucket_name, file_key, file_bytes):
        stime = time.time()
        file_bytes = gzip.compress(file_bytes)
        tmp_file_name = '/tmp/' + str(uuid.uuid1()) + '.gz'
        with open(tmp_file_name, 'wb') as f:
            f.write(file_bytes)

        with open(tmp_file_name, 'rb') as f:
            result = cls.s3_meta_client.upload_fileobj(f, bucket_name, file_key)

        os.remove(tmp_file_name)

        etime = time.time()
        cls.time_info.append(etime - stime)
        return result

if __name__ == '__main__':
    S3PutTest8.main()

結果集計用スクリプト

print_average.py

import sys 

def main(resulf_file):

    num_min = []
    num_max = []
    num_info = []
    line_count = 0 
    with open(resulf_file) as f:
        result = f.read()
        # print(result)
        lines = result.split('\n')
        for i,line in enumerate(lines):
            if i == 0:
                continue
            cols = line.split('\t')
            line_count += 1
            for j,col in enumerate(cols):
                if not col or j == 0:
                    continue
                if i == 1:
                    num_info.append([])
                    num_min.append(999.0)
                    num_max.append(0.0)
                num = float(col)
                num_info[j-1].append(num)
                if num_max[j-1] < num:
                    num_max[j-1] = num 
                if num_min[j-1] > num:
                    num_min[j-1] = num 

    averages = []
    for i,nums in enumerate(num_info):
        min_val = num_min[i]
        max_val = num_max[i]
        summary = 0.0 
        col_count = 0 
        for j,num in enumerate(nums):
            if min_val == num or max_val == num:
                continue
            else:
                col_count += 1
                summary = summary + num 
                #print(f'{i}/{j} : {min_val} : {max_val} : {num}')
        average = summary / col_count
        averages.append(average)

    print(f'平均({resulf_file})\t' + '\t'.join(list(map(lambda x: str(round(x, 3)), averages))))

if __name__ == '__main__':
    resulf_file = sys.argv[1]
    main(resulf_file)

実行用シェル

s3-put-test.sh

#!/bin/bash

for i in `seq 1 8`
do
    file_name="s3_put_test${i}.py"
    result_file="result${i}.csv"
    rm -rf $result_file
    for j in `seq 1 10`
    do  
        python3 $file_name $result_file $j
    done
    python3 print_average.py $result_file
done

上記をさらに5回繰り返すシェル
s3-put-test-all.sh

#!/bin/bash

for no in `seq 1 5`
do
    ./s3-put-test.sh | tee summaryA-${no}.csv
done

結果

cat summaryA* | grep 平均 | sort


N回目ファイル1ファイル2ファイル3ファイル4ファイル5ファイル6ファイル7ファイル8ファイル9ファイル10合計
平均(result1.csv)0.190.0830.0750.0880.0860.1080.0930.0990.1090.0921.257
平均(result1.csv)0.1930.0740.0720.080.0820.0870.080.0880.0970.0871.005
平均(result1.csv)0.1930.0810.0740.0950.0840.090.0790.0950.0890.0951.018
平均(result1.csv)0.260.0830.10.0980.0850.0820.0850.0960.0970.1021.225
平均(result1.csv)0.2650.0830.0840.0950.0820.0920.0840.090.0950.1011.109
平均(result2.csv)0.1870.0760.0730.0850.0870.0860.0880.0870.0970.091.002
平均(result2.csv)0.1880.0810.0740.0870.0840.1020.0880.0940.0960.0921.043
平均(result2.csv)0.2070.0920.0780.0890.0830.0870.0810.1160.0990.0921.064
平均(result2.csv)0.2220.080.0750.0810.0880.0840.0860.1150.0970.1091.114
平均(result2.csv)0.2250.0780.0880.0890.0780.0850.1320.0980.0960.0851.179
平均(result3.csv)0.1840.0760.0880.0810.0840.10.0930.0950.0990.0971.048
平均(result3.csv)0.1840.0810.0730.0940.1320.0880.1060.0860.0890.0881.108
平均(result3.csv)0.1850.0780.0790.090.0840.0840.0940.1150.0990.091.111
平均(result3.csv)0.1910.0770.0730.0960.0830.0820.080.0920.0890.1021.007
平均(result3.csv)0.1970.0780.0820.0890.0850.0910.0930.0950.0970.0991.047
平均(result4.csv)0.2070.0990.0890.0910.0980.0920.1120.1140.0990.1121.217
平均(result4.csv)0.2080.080.0740.0990.0860.0980.0920.0990.10.1031.1
平均(result4.csv)0.2130.0860.0870.090.0890.0940.1020.1070.1040.1021.177
平均(result4.csv)0.2150.090.0840.0940.0890.1380.0940.1140.1070.1051.3
平均(result4.csv)0.2190.2110.0820.1060.0950.0940.0940.1050.1040.1061.302
平均(result5.csv)0.1810.0790.0760.0780.090.0750.0850.0810.0770.0820.967
平均(result5.csv)0.2010.0810.0750.0820.0690.0710.0740.0860.0830.0770.975
平均(result5.csv)0.2020.0750.0720.0740.070.0750.0780.0780.0730.0780.951
平均(result5.csv)0.2090.0790.070.0740.0710.0790.0760.0790.0710.0730.947
平均(result5.csv)0.3460.0770.0710.0780.0790.0720.0810.0770.0750.081.093
平均(result6.csv)0.1870.0790.0770.0960.080.0840.0820.0850.090.0941.031
平均(result6.csv)0.1890.0770.0770.0940.0820.0870.0860.0940.0890.1171.047
平均(result6.csv)0.2080.0870.0770.0970.0950.0940.0950.0950.1040.1111.156
平均(result6.csv)0.2370.0880.0750.0890.0880.0860.0950.0920.0920.111.113
平均(result6.csv)0.2480.080.0760.0910.0880.0950.0880.0910.0980.0971.171
平均(result7.csv)0.1890.0770.080.0760.0690.070.0770.0750.0720.0720.907
平均(result7.csv)0.1890.0850.0750.0840.0770.0740.0790.0760.0790.0830.973
平均(result7.csv)0.1930.0760.0750.0780.0720.0930.0750.0770.0740.0760.952
平均(result7.csv)0.2320.0860.080.1010.080.0780.0750.0890.0770.091.039
平均(result7.csv)0.2380.0810.0810.0750.0790.0740.080.0860.0850.0771.089
平均(result8.csv)0.1930.0820.0690.0810.0730.0740.0880.0840.0770.0780.983
平均(result8.csv)0.2070.0820.0870.0750.0740.0760.0750.0790.080.0780.975
平均(result8.csv)0.2110.0840.0780.0850.0760.0760.0780.080.0820.0810.977
平均(result8.csv)0.2180.0750.0780.0910.0730.0790.080.0760.0910.0830.995
平均(result8.csv)0.2320.0810.0780.0760.0740.0740.0760.0790.0850.0861.084

トップ   差分 バックアップ リロード   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2018-07-23 (月) 06:41:25 (139d)