[[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.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>)