目次 †
s3 select の概要 †Amazon S3 Select を使用するとSQLライクにS3からデータを取得できる。 ここでは、S3に格納された10,000件のデータを持つ csv または json から任意の1件を取得する際のパフォーマンスを s3 select を使用する場合/使用しない場合で比較してみた。 csvファイルからのデータ取得 †ソース †csv データのパースは csv.reader を使用。(pandas 等は使用しない) s3_select_csv.py import argparse import boto3 import json import time import re import csv BUCKET_NAME = 'バケット名' OBJECT_KEY = 'test-s3-select.csv' def init(count): """ テストデータ作成. """ print(f"データ作成 件数:{count}") header = 'no,data1,data2,data3,data4,data5' items = '\n'.join([f'{i},data{i},data{i+1},data{i+2},data{i+3},data{i+4}' for i in range(count)]) bytes_items = f'{header}\n{items}'.encode('utf-8') client = boto3.client('s3') client.put_object( Bucket=BUCKET_NAME, Key=OBJECT_KEY, Body=bytes_items ) def normal_filter(): """ APIで全データ取得してロジックでフィルタを書ける場合. """ client = boto3.client('s3') stime = time.time() response = client.get_object( Bucket=BUCKET_NAME, Key=OBJECT_KEY ) target = [] if 'Body' in response: body = response['Body'].read() records_text = body.decode('utf-8') items = [x for x in csv.reader(records_text.strip().splitlines())] target = list(filter(lambda x: x[1] == 'data100', items)) etime = time.time() print('処理時間(通常): {}'.format(etime - stime)) print(target) def s3select_filter(): """ S3 select で条件指定してデータ取得する場合. """ client = boto3.client('s3') stime = time.time() response = client.select_object_content( Bucket=BUCKET_NAME, Key=OBJECT_KEY, ExpressionType='SQL', Expression="SELECT * FROM S3Object WHERE data1 = 'data100'", InputSerialization={'CompressionType': 'NONE', 'CSV': {'FileHeaderInfo': 'USE', 'RecordDelimiter': '\n', 'FieldDelimiter': ','}}, OutputSerialization={'CSV': {'RecordDelimiter': '\n', 'FieldDelimiter': ','}} ) target = [] if 'Payload' in response: # botocore.eventstream.EventStream からデータを取り出し for event_stream in response['Payload']: if 'Records' in event_stream and 'Payload' in event_stream['Records']: records_text = event_stream['Records']['Payload'].decode('utf-8') items = [x for x in csv.reader(records_text.strip().splitlines())] target = target + items etime = time.time() print('処理時間(s3-select): {}'.format(etime - stime)) print(target) if __name__ == '__main__': parser = argparse.ArgumentParser(description='s3 selectのパフォーマンス検証') parser.add_argument('--init', action='store_true', help='初期処理(データ作成)時に指定') parser.add_argument('--count', type=int, default=10, help='データ作成件数') args = parser.parse_args() if (args.init): init(args.count) else: print("") normal_filter() s3select_filter() データ投入 †python3 s3_select_csv --init --count 10000 動作確認 †python3 s3_select_csv 処理時間(通常): 0.909980058670044 [['100', 'data100', 'data101', 'data102', 'data103', 'data104']] 処理時間(s3-select): 0.396359920501709 [['100', 'data100', 'data101', 'data102', 'data103', 'data104']] jsonファイルからのデータ取得 †ソース †s3_select_json.py import argparse import boto3 import json import time import re BUCKET_NAME = 'バケット名' OBJECT_KEY = 'test-s3-select.json' def init(count): print(f"データ作成 件数:{count}") bytes_items = json.dumps({ 'items':[{'no': i, 'data1': f'data{i}', 'data2': f'data{i+1}', 'data3': f'data{i+2}', 'data4': f'data{i+3}', 'data5': f'data{i+4}' } for i in range(count)]}).encode('utf-8') client = boto3.client('s3') client.put_object( Bucket=BUCKET_NAME, Key=OBJECT_KEY, Body=bytes_items ) def normal_filter(): """ APIで全データ取得してロジックでフィルタを書ける場合. """ client = boto3.client('s3') stime = time.time() response = client.get_object( Bucket=BUCKET_NAME, Key=OBJECT_KEY ) target = [] if 'Body' in response: body = response['Body'].read() text = body.decode('utf-8') items = json.loads(text) items = items['items'] target = list(filter(lambda x: x['data1'] == 'data100', items)) etime = time.time() print('処理時間(通常): {}'.format(etime - stime)) print(target) def s3select_filter(): """ S3 select で条件指定してデータ取得する場合. """ client = boto3.client('s3') stime = time.time() response = client.select_object_content( Bucket=BUCKET_NAME, Key=OBJECT_KEY, ExpressionType='SQL', Expression="SELECT d.* FROM S3Object[*].items[*] d WHERE d.data1 = 'data100'", InputSerialization={'CompressionType': 'NONE', 'JSON': {'Type': 'DOCUMENT'}}, OutputSerialization={'JSON': {'RecordDelimiter': ','}} # json.loadsしやすいように区切りはカンマにしておく ) target = [] if 'Payload' in response: # botocore.eventstream.EventStream からデータを取り出し for event_stream in response['Payload']: if 'Records' in event_stream and 'Payload' in event_stream['Records']: records_text = event_stream['Records']['Payload'].decode('utf-8') records_text = "[" + re.sub(",$", "", records_text) + "]" # 最後尾のカンマを除去 rec = json.loads(records_text) target = target + rec etime = time.time() print('処理時間(s3-select): {}'.format(etime - stime)) print(target) if __name__ == '__main__': parser = argparse.ArgumentParser(description='s3 selectのパフォーマンス検証') parser.add_argument('--init', action='store_true', help='初期処理(データ作成)時に指定') parser.add_argument('--count', type=int, default=10, help='データ作成件数') args = parser.parse_args() if (args.init): init(args.count) else: print("") normal_filter() s3select_filter() データ投入 †python3 s3_select_json.py --init --count 10000 動作確認 †python3 s3_select_json.py 処理時間(通常): 1.464644193649292 [{'no': 100, 'data1': 'data100', 'data2': 'data101', 'data3': 'data102', 'data4': 'data103', 'data5': 'data104'}] 処理時間(s3-select): 0.32223010063171387 [{'no': 100, 'data1': 'data100', 'data2': 'data101', 'data3': 'data102', 'data4': 'data103', 'data5': 'data104'}] 所感 †データ件数が 5列 * 10000件 程度でも、そこそこの違いは出る模様。 |