目次

s3 select の概要

Amazon S3 Select を使用するとSQLライクにS3からデータを取得できる。
対応フォーマットは csv 及び jsonで、圧縮ファイルにも対応している。(GZIP または BZIP2)

ここでは、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件 程度でも、そこそこの違いは出る模様。
特に jsonを扱う場合には、SQLの記述の仕方によって返却データのパースに考慮が必要になってくる為、注意が必要。
たぶん、素直に csv を使っておくほうが楽。


トップ   差分 バックアップ リロード   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2019-05-03 (金) 19:19:49 (1817d)