[[AWSメモ]] >
* S3へのファイル登録のパフォーマンス比較 [#i6ef5fce]
#setlinebreak(on)

#contents

** 概要 [#s07a1537]
#html(<div style="padding-left:10px;">)
S3へのファイル登録時に使用するAPIによってパフォーマンスがどれだけ変わるか検証する

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

** 結果(平均) [#s07a1537]
#html(<div style="padding-left:10px;">)

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

|処理|結果平均1|h
|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|h
|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|

#html(</div>)

** テストスクリプト [#dc02198f]
#html(<div style="padding-left:10px;">)

*** テスト処理の雛形 [#l16473a4]
#html(<div style="padding-left:10px;">)
s3_put_test_base.py
#mycode2(){{
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
}}
#html(</div>)

*** boto3.resource.Object.put を使用 [#l628eed2]
#html(<div style="padding-left:10px;">)
s3_put_test1.py
#mycode2(){{
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()
}}
#html(</div>)

*** boto3.resource.Bucket の put_object を使用 [#xb1ead91]
#html(<div style="padding-left:10px;">)
s3_put_test2.py
#mycode2(){{
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()
}}
#html(</div>)

*** boto3.client.put_object を使用 [#yc731a7d]
#html(<div style="padding-left:10px;">)
s3_put_test3.py
#mycode2(){{
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()
}}
#html(</div>)

*** boto3.resource.meta.client.upload_file を使用 [#meedabf5]
#html(<div style="padding-left:10px;">)
s3_put_test4.py
#mycode2(){{
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()
}}
#html(</div>)

*** boto3.resource.Bucket.put_object を使用(かつファイル圧縮) [#wf49f33d]
#html(<div style="padding-left:10px;">)
s3_put_test5.py
#mycode2(){{
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()
}}
#html(</div>)

*** boto3.client.put_object で 属性を指定してアップロード [#xf046ac9]
#html(<div style="padding-left:10px;">)
s3_put_test6.py
#mycode2(){{
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()
}}
#html(</div>)

*** boto3.resource.Bucket.put_object でファイル圧縮しつつ、属性を指定 [#s3f9ba3b]
#html(<div style="padding-left:10px;">)
s3_put_test7.py
#mycode2(){{
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()
}}
#html(</div>)

*** boto3.resource.meta.client.upload_fileobj を使用 [#vff53e4f]
#html(<div style="padding-left:10px;">)
s3_put_test8.py
#mycode2(){{
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()
}}
#html(</div>)
#html(</div>)

** 結果集計用スクリプト [#mef40cca]
#html(<div style="padding-left:10px;">)
print_average.py
#mycode2(){{
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)
}}
#html(</div>)

** 実行用シェル [#n010266a]
#html(<div style="padding-left:10px;">)

s3-put-test.sh
#myterm2(){{
#!/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
#myterm2(){{
#!/bin/bash

for no in `seq 1 5`
do
    ./s3-put-test.sh | tee summaryA-${no}.csv
done
}}
#html(</div>)

** 結果 [#ya26e801]
#html(<div style="padding-left:10px;">)

#myterm2(){{
cat summaryA* | grep 平均 | sort
}}

&br;

|N回目|ファイル1|ファイル2|ファイル3|ファイル4|ファイル5|ファイル6|ファイル7|ファイル8|ファイル9|ファイル10|合計|h
|平均(result1.csv)|0.19|0.083|0.075|0.088|0.086|0.108|0.093|0.099|0.109|0.092|1.257|
|平均(result1.csv)|0.193|0.074|0.072|0.08|0.082|0.087|0.08|0.088|0.097|0.087|1.005|
|平均(result1.csv)|0.193|0.081|0.074|0.095|0.084|0.09|0.079|0.095|0.089|0.095|1.018|
|平均(result1.csv)|0.26|0.083|0.1|0.098|0.085|0.082|0.085|0.096|0.097|0.102|1.225|
|平均(result1.csv)|0.265|0.083|0.084|0.095|0.082|0.092|0.084|0.09|0.095|0.101|1.109|
|平均(result2.csv)|0.187|0.076|0.073|0.085|0.087|0.086|0.088|0.087|0.097|0.09|1.002|
|平均(result2.csv)|0.188|0.081|0.074|0.087|0.084|0.102|0.088|0.094|0.096|0.092|1.043|
|平均(result2.csv)|0.207|0.092|0.078|0.089|0.083|0.087|0.081|0.116|0.099|0.092|1.064|
|平均(result2.csv)|0.222|0.08|0.075|0.081|0.088|0.084|0.086|0.115|0.097|0.109|1.114|
|平均(result2.csv)|0.225|0.078|0.088|0.089|0.078|0.085|0.132|0.098|0.096|0.085|1.179|
|平均(result3.csv)|0.184|0.076|0.088|0.081|0.084|0.1|0.093|0.095|0.099|0.097|1.048|
|平均(result3.csv)|0.184|0.081|0.073|0.094|0.132|0.088|0.106|0.086|0.089|0.088|1.108|
|平均(result3.csv)|0.185|0.078|0.079|0.09|0.084|0.084|0.094|0.115|0.099|0.09|1.111|
|平均(result3.csv)|0.191|0.077|0.073|0.096|0.083|0.082|0.08|0.092|0.089|0.102|1.007|
|平均(result3.csv)|0.197|0.078|0.082|0.089|0.085|0.091|0.093|0.095|0.097|0.099|1.047|
|平均(result4.csv)|0.207|0.099|0.089|0.091|0.098|0.092|0.112|0.114|0.099|0.112|1.217|
|平均(result4.csv)|0.208|0.08|0.074|0.099|0.086|0.098|0.092|0.099|0.1|0.103|1.1|
|平均(result4.csv)|0.213|0.086|0.087|0.09|0.089|0.094|0.102|0.107|0.104|0.102|1.177|
|平均(result4.csv)|0.215|0.09|0.084|0.094|0.089|0.138|0.094|0.114|0.107|0.105|1.3|
|平均(result4.csv)|0.219|0.211|0.082|0.106|0.095|0.094|0.094|0.105|0.104|0.106|1.302|
|平均(result5.csv)|0.181|0.079|0.076|0.078|0.09|0.075|0.085|0.081|0.077|0.082|0.967|
|平均(result5.csv)|0.201|0.081|0.075|0.082|0.069|0.071|0.074|0.086|0.083|0.077|0.975|
|平均(result5.csv)|0.202|0.075|0.072|0.074|0.07|0.075|0.078|0.078|0.073|0.078|0.951|
|平均(result5.csv)|0.209|0.079|0.07|0.074|0.071|0.079|0.076|0.079|0.071|0.073|0.947|
|平均(result5.csv)|0.346|0.077|0.071|0.078|0.079|0.072|0.081|0.077|0.075|0.08|1.093|
|平均(result6.csv)|0.187|0.079|0.077|0.096|0.08|0.084|0.082|0.085|0.09|0.094|1.031|
|平均(result6.csv)|0.189|0.077|0.077|0.094|0.082|0.087|0.086|0.094|0.089|0.117|1.047|
|平均(result6.csv)|0.208|0.087|0.077|0.097|0.095|0.094|0.095|0.095|0.104|0.111|1.156|
|平均(result6.csv)|0.237|0.088|0.075|0.089|0.088|0.086|0.095|0.092|0.092|0.11|1.113|
|平均(result6.csv)|0.248|0.08|0.076|0.091|0.088|0.095|0.088|0.091|0.098|0.097|1.171|
|平均(result7.csv)|0.189|0.077|0.08|0.076|0.069|0.07|0.077|0.075|0.072|0.072|0.907|
|平均(result7.csv)|0.189|0.085|0.075|0.084|0.077|0.074|0.079|0.076|0.079|0.083|0.973|
|平均(result7.csv)|0.193|0.076|0.075|0.078|0.072|0.093|0.075|0.077|0.074|0.076|0.952|
|平均(result7.csv)|0.232|0.086|0.08|0.101|0.08|0.078|0.075|0.089|0.077|0.09|1.039|
|平均(result7.csv)|0.238|0.081|0.081|0.075|0.079|0.074|0.08|0.086|0.085|0.077|1.089|
|平均(result8.csv)|0.193|0.082|0.069|0.081|0.073|0.074|0.088|0.084|0.077|0.078|0.983|
|平均(result8.csv)|0.207|0.082|0.087|0.075|0.074|0.076|0.075|0.079|0.08|0.078|0.975|
|平均(result8.csv)|0.211|0.084|0.078|0.085|0.076|0.076|0.078|0.08|0.082|0.081|0.977|
|平均(result8.csv)|0.218|0.075|0.078|0.091|0.073|0.079|0.08|0.076|0.091|0.083|0.995|
|平均(result8.csv)|0.232|0.081|0.078|0.076|0.074|0.074|0.076|0.079|0.085|0.086|1.084|

#html(</div>)


トップ   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS