- 追加された行はこの色です。
- 削除された行はこの色です。
#author("2019-05-03T09:49:56+00:00","","")
#mynavi(AWSメモ)
#setlinebreak(on);
* 目次 [#l57e910e]
#contents
- 参考
-- https://aws.amazon.com/jp/about-aws/whats-new/2018/09/amazon-s3-announces-new-features-for-s3-select/
-- https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/selecting-content-from-objects.html
-- https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/s3-glacier-select-sql-reference.html
-- https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#S3.Client.select_object_content
- 関連
-- [[AWSメモ]]
-- [[S3へのファイル登録のパフォーマンス比較]]
* s3 select の概要 [#m6cb864e]
#html(<div style="padding-left:10px">)
Amazon S3 Select を使用するとSQLライクにS3からデータを取得できる。
対応フォーマットは csv 及び jsonで、圧縮ファイルにも対応している。(GZIP または BZIP2)
ここでは、S3に格納された10,000件のデータを持つ csv または json から任意の1件を取得する際のパフォーマンスを s3 select を使用する場合/使用しない場合で比較してみた。
#html(</div>)
* csvファイルからのデータ取得 [#xbeb3ca1]
#html(<div style="padding-left:10px">)
** ソース [#y08f2d8d]
#html(<div style="padding-left:10px">)
csv データのパースは csv.reader を使用。(pandas 等は使用しない)
s3_select_csv.py
#mycode2(){{
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()
}}
#html(</div>)
** データ投入 [#v0e18518]
#html(<div style="padding-left:10px">)
#myterm2(){{
python3 s3_select_csv --init --count 10000
}}
#html(</div>)
** 動作確認 [#yb529e41]
#html(<div style="padding-left:10px">)
#myterm2(){{
python3 s3_select_csv
処理時間(通常): 0.909980058670044
[['100', 'data100', 'data101', 'data102', 'data103', 'data104']]
処理時間(s3-select): 0.396359920501709
[['100', 'data100', 'data101', 'data102', 'data103', 'data104']]
}}
#html(</div>)
#html(</div>)
* jsonファイルからのデータ取得 [#m30b07aa]
#html(<div style="padding-left:10px">)
** ソース [#j67b3404]
#html(<div style="padding-left:10px">)
s3_select_json.py
#mycode2(){{
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()
}}
#html(</div>)
** データ投入 [#x221a71f]
#html(<div style="padding-left:10px">)
#myterm2(){{
python3 s3_select_json.py --init --count 10000
}}
#html(</div>)
** 動作確認 [#r3ab0e39]
#html(<div style="padding-left:10px">)
#myterm2(){{
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'}]
}}
#html(</div>)
#html(</div>)
* 所感 [#yc33666b]
#html(<div style="padding-left:10px">)
データ件数が 5列 * 10000件 程度でも、そこそこの違いは出る模様。
特に jsonを扱う場合には、SQLの記述の仕方によって返却データのパースに考慮が必要になってくる為、注意が必要。
たぶん、素直に csv を使っておくほうが楽。
#html(</div>)