S3 SELECTのパターンとLambda実行時間を調べてみた(CSV、JSON、GZIP圧縮の有無)

S3のオブジェクトに対し、簡単なSQLを発行してデータ取得できる「S3 SELECT」があります。 このS3 SELECTについて、いくつかのパターンとLambda実行時間を調べてみました。
2019.03.26

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

サーバーレス開発部の藤井元貴です。

S3のオブジェクトに対し、簡単なSQLを発行してデータ取得できる「S3 SELECT」があります。

このS3 SELECTについて、いくつかのパターンとLambda実行時間を調べてみました。

おすすめの方

  • S3 SELECTに興味がある
  • S3 SELECTを使用するLambdaの実行時間に興味がある

環境

項目 バージョン
macOS High Sierra 10.13.6
AWS CLI aws-cli/1.16.89 Python/3.6.1 Darwin/17.7.0 botocore/1.12.79
Python 3.6

使用データの例

CSV形式の例です。詳細は本記事の下部にあります。(JSONもそちらをどうぞ)

id,uuid
1,a70b64e7-b23d-4c10-9ad4-c90d27c9640f
2,1ce0c445-9586-4500-bfb3-23ea9908aed9

パターン&結果

No 形式 圧縮 ファイルサイズ(MB) Lambda実行時間(ms)
1 CSV 無し 213.5 4048.00
2 CSV GZIP 115.7 4021.60
3 JSON 無し 313.7 3687.14
4 JSON GZIP 122.2 3509.75
  • Lambdaのメモリ割り当ては、128MBです
  • Lambdaの実行時間は、5回の平均です
    • 1回目のコールドスタート含む
    • 内訳は本記事の下部に記載
  • レコード数は、500万件です
  • シンプルなデータ(項目2個)を使用します

コメント

GZIP圧縮の有無

実行時間は、ほぼ同じとなりました。

S3 SELECTは下記に課金されるため、スキャンされるデータ量を減らすために「圧縮あり」のほうが良さそうです。(料金的に)

  • S3 Select によって返されたデータ
  • S3 Select によってスキャンされたデータ

CSV形式かJSON形式か

今回の場合は、JSON形式のほうが実行速度は有利ですね。

注意!!!

項目数やネスト数(JSONの場合)など、対象データの内容次第で、Lambda実行時間は大きく変わると思います。 やはり「やってみる」のが大切ですね。

以下、やってみたメモ

事前準備

CSVファイルの準備

ヘッダー有りのCSVファイルを準備します。レコード数は500万とします。項目数は2個とシンプルです。

id,uuid
1,a70b64e7-b23d-4c10-9ad4-c90d27c9640f
2,1ce0c445-9586-4500-bfb3-23ea9908aed9
3,148cc0e2-17b4-4b4b-aebc-481c56f37c8f
4,78c32510-fa93-4e35-b07e-cb1b58d22437
5,15cc096b-abfe-4e60-9424-08e14957b694
(略)
4999996,e6fb90e1-d7d4-4642-b3a5-7a9392a47045
4999997,b3006400-1dac-4423-8b6c-e12a1e2bb27e
4999998,2ec7aeb3-e2c4-4dc6-a3d6-a382ee0ed898
4999999,ed00ad80-5912-447b-bac4-aa8928c68d70
5000000,c4ba17be-16cb-4ede-9e84-2483c3746848

次のコードを用いてCSVファイルを作成しました。

import uuid

with open('test.csv', 'w') as f:
    f.write('id,uuid\n')
    for index in range(5000000):
        f.write(f'{index+1},{uuid.uuid4()}\n')

JSONファイルの準備

S3 SELECTで利用できるJSON形式については、下記をご覧ください。

さきほど作成したCSVファイルをJSON Lines形式に変換しました。

{"id": "1", "uuid": "a70b64e7-b23d-4c10-9ad4-c90d27c9640f"}
{"id": "2", "uuid": "1ce0c445-9586-4500-bfb3-23ea9908aed9"}
{"id": "3", "uuid": "148cc0e2-17b4-4b4b-aebc-481c56f37c8f"}
{"id": "4", "uuid": "78c32510-fa93-4e35-b07e-cb1b58d22437"}
{"id": "5", "uuid": "15cc096b-abfe-4e60-9424-08e14957b694"}
(略)
{"id": "4999996", "uuid": "e6fb90e1-d7d4-4642-b3a5-7a9392a47045"}
{"id": "4999997", "uuid": "b3006400-1dac-4423-8b6c-e12a1e2bb27e"}
{"id": "4999998", "uuid": "2ec7aeb3-e2c4-4dc6-a3d6-a382ee0ed898"}
{"id": "4999999", "uuid": "ed00ad80-5912-447b-bac4-aa8928c68d70"}
{"id": "5000000", "uuid": "c4ba17be-16cb-4ede-9e84-2483c3746848"}

変換に使用したコードです。

import csv
import json

csv_file = open('test.csv', 'r')
json_file = open('test.json', 'w')

reader = csv.DictReader(csv_file)

for row in reader:
    json.dump(row, json_file)
    json_file.write("\n")

json_file.close()
csv_file.close()

GZIP圧縮

作成した2つのファイル(CSVとJSON)をGZIP圧縮します。

$ gzip -c test.csv > test.csv.gz
$ gzip -c test.json > test.json.gz

S3にアップロード

バケット作成します。ある場合はSkipです。

$ aws s3 mb s3://cm-fujii.genki-test
make_bucket: cm-fujii.genki-test

アップロードします。

$ aws s3 cp test.csv s3://cm-fujii.genki-test/s3-select/test.csv
$ aws s3 cp test.json s3://cm-fujii.genki-test/s3-select/test.json
$ aws s3 cp test.csv.gz s3://cm-fujii.genki-test/s3-select/test.csv.gz
$ aws s3 cp test.json.gz s3://cm-fujii.genki-test/s3-select/test.json.gz

Lambdaの準備

AWS SAMなどを使わず、直接作成しました。

Lambda実行時のロールにAmazonS3ReadOnlyAccessポリシーをアタッチします。

こちらによると、GetObjectがあればOKです。

CSV形式、圧縮なし
import boto3

BUCKET_NAME = 'cm-fujii.genki-test'
OBJECT_KEY = 's3-select/test.csv'
COMPRESSION_TYPE = 'NONE'


def lambda_handler(event, context):

    query = 'SELECT * FROM S3Object s WHERE s.id=\'3\' or s.id=\'4999998\''

    s3 = boto3.client('s3')

    # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#S3.Client.select_object_content
    response = s3.select_object_content(
        Bucket=BUCKET_NAME,
        Key=OBJECT_KEY,
        ExpressionType='SQL',
        Expression=query,
        InputSerialization={
            'CSV': {
                'FileHeaderInfo': 'USE',
                'RecordDelimiter': '\n',
                'FieldDelimiter': ',',
            },
            'CompressionType': COMPRESSION_TYPE,
        },
        OutputSerialization={
            'JSON': {
                'RecordDelimiter': '\n'
            }
        }
    )

    # 結果を表示
    # S3 SELECTの戻り値は EventStream のためループで取り出す
    # https://botocore.amazonaws.com/v1/documentation/api/latest/reference/eventstream.html
    for payload in response['Payload']:
        if 'Records' in payload:
            records = payload['Records']['Payload'].decode('utf-8')
            print(records)
CSV形式、圧縮あり

設定の変更のみです。

BUCKET_NAME = 'cm-fujii.genki-test'
OBJECT_KEY = 's3-select/test.csv.gz'
COMPRESSION_TYPE = 'GZIP'

JSON形式、圧縮なし

InputSerializationをJSONに合わせて変更しています。

import boto3

BUCKET_NAME = 'cm-fujii.genki-test'
OBJECT_KEY = 's3-select/test.json'
COMPRESSION_TYPE = 'NONE'


def lambda_handler(event, context):

    query = 'SELECT * FROM S3Object s WHERE s.id=\'3\' or s.id=\'4999998\''

    s3 = boto3.client('s3')

    # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#S3.Client.select_object_content
    response = s3.select_object_content(
        Bucket=BUCKET_NAME,
        Key=OBJECT_KEY,
        ExpressionType='SQL',
        Expression=query,
        InputSerialization={
            'JSON': {
                'Type': 'Lines',
            },
            'CompressionType': COMPRESSION_TYPE,
        },
        OutputSerialization={
            'JSON': {
                'RecordDelimiter': '\n'
            }
        }
    )

    # 結果を表示
    # S3 SELECTの戻り値は EventStream のためループで取り出す
    # https://botocore.amazonaws.com/v1/documentation/api/latest/reference/eventstream.html
    for payload in response['Payload']:
        if 'Records' in payload:
            records = payload['Records']['Payload'].decode('utf-8')
            print(records)
JSON形式、圧縮あり

設定の変更のみです。

BUCKET_NAME = 'cm-fujii.genki-test'
OBJECT_KEY = 's3-select/test.json.gz'
COMPRESSION_TYPE = 'GZIP'

Lambda実行結果

結果は共通なので、これだけ載せておきます。バッチリ取得できてますね!

{"id":"3","uuid":"148cc0e2-17b4-4b4b-aebc-481c56f37c8f"}

{"id":"4999998","uuid":"2ec7aeb3-e2c4-4dc6-a3d6-a382ee0ed898"}

Lambda実行時間(CSV形式)

回数 圧縮なし(ms) 圧縮あり(ms)
1 5179.60 5056.94
2 4175.78 4293.95
3 3806.32 3560.01
4 3199.92 3468.49
5 3878.37 3728.59
平均 4048.00 4021.60

Lambda実行時間(JSON形式)

回数 圧縮なし(ms) 圧縮あり(ms)
1 6049.82 5204.98
2 3688.56 3383.22
3 2906.65 3001.53
4 2829.20 3208.97
5 2961.49 2750.11
平均 3687.14 3509.76

参考