AWS LambdaでS3 Selectをした際のパフォーマンス比較

こんにちは。サービスグループの武田です。Rust + AWS CLIとPythonとJavaScriptでS3 Selectの速度比較をしてみました。
2021.02.26

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

こんにちは。サービスグループの武田です。

先日、頑張ってAWS LambdaのコンテナでRustアプリケーションからS3 Selectを実行してみました。

最後に 予想より実行時間が長い結果となりました と書いたのですが、じゃあどれくらいなら満足できるのか。というわけで、Lambdaで採用されることが多いであろうPythonとJavaScriptで同様の処理を書いて比較してみました。

Rust(+ AWS CLI) vs Python vs JavaScript

実行したソースコードはそれぞれ次のものです。なおRustは先述のエントリそのままですので省略します。

handler.py

import boto3
import json

s3 = boto3.client("s3")


def lambda_handler(event, context):
    params = {
        "Bucket": "testdata-xxxx",
        "Key": "test_data.json",
        "InputSerialization": {
            "JSON": {
                "Type": "LINES",
            }
        },
        "OutputSerialization": {
            "JSON": {
                "RecordDelimiter": "\n",
            }
        },
        "ExpressionType": "SQL",
        "Expression": "SELECT * FROM s3object s",
    }

    res = s3.select_object_content(**params)

    text = ""
    for event in res["Payload"]:
        if "Records" in event:
            raw = event["Records"]["Payload"].decode("UTF-8")
            text += raw

    for line in text.splitlines():
        print(json.loads(line))

handler.js

const AWS = require('aws-sdk');
const S3 = new AWS.S3();

exports.handler = async (event) => {
    const params = {
        Bucket: 'testdata-xxxx',
        Key: 'test_data.json',
        InputSerialization: {
            JSON: {
                Type: 'LINES',
            }
        },
        OutputSerialization: {
            JSON: {
                RecordDelimiter: '\n',
            }
        },
        ExpressionType: 'SQL',
        Expression: 'SELECT * FROM s3object s',
    };

    const res = await S3.selectObjectContent(params).promise();

    let text = '';
    const events = res.Payload;

    for await (const event of events) {
        if (event.Records) {
            text += event.Records.Payload.toString();
        }
    }

    for (line of text.split('\n').filter(line => line !== '')) {
        console.log(JSON.parse(line));
    }
};

実行結果は次のようになりました。なおいずれも2回目〜6回目の結果を採用し、イニシャルコストは含まれていません。またJavaScriptについてはKeepAliveがデフォルトで無効化されているため、有効化しました。

Rust + AWS CLIの結果。

START RequestId: c66a065d-6673-469d-9db9-d1e251e1e4fe Version: $LATEST
[2021-02-26T02:23:29Z DEBUG rust_lambda_call_aws_cli] handler start
[2021-02-26T02:23:31Z DEBUG rust_lambda_call_aws_cli] Output { status: ExitStatus(ExitStatus(0)), stdout: "", stderr: "" }
[2021-02-26T02:23:31Z INFO  rust_lambda_call_aws_cli] TestData { name: "Test", code: 1939, tags: Some("Dev"), lang: Some("ja") }
[2021-02-26T02:23:31Z INFO  rust_lambda_call_aws_cli] TestData { name: "IT Division", code: 1, tags: Some("Prod"), lang: Some("ja") }
[2021-02-26T02:23:31Z INFO  rust_lambda_call_aws_cli] TestData { name: "Sample", code: 31, tags: None, lang: Some("en") }
[2021-02-26T02:23:31Z INFO  rust_lambda_call_aws_cli] TestData { name: "Classmethod", code: 2, tags: None, lang: Some("en") }
[2021-02-26T02:23:31Z INFO  rust_lambda_call_aws_cli] TestData { name: "Classmethod2", code: 19, tags: None, lang: None }
END RequestId: c66a065d-6673-469d-9db9-d1e251e1e4fe
REPORT RequestId: 8e9f49c0-6b91-40e3-81f3-f7636abb8546	Duration: 2459.43 ms	Billed Duration: 2460 ms	Memory Size: 512 MB	Max Memory Used: 46 MB
REPORT RequestId: 848f0aa9-fca2-46e7-8942-d7a37bd2433d	Duration: 2487.36 ms	Billed Duration: 2488 ms	Memory Size: 512 MB	Max Memory Used: 47 MB
REPORT RequestId: 2b5831ad-c1a3-4df3-b50b-db600eed5af9	Duration: 2463.11 ms	Billed Duration: 2464 ms	Memory Size: 512 MB	Max Memory Used: 47 MB
REPORT RequestId: 34fd1cdb-b7cb-4e55-8c9a-5b6c136e3b53	Duration: 2483.41 ms	Billed Duration: 2484 ms	Memory Size: 512 MB	Max Memory Used: 47 MB
REPORT RequestId: 99f64d9c-7acb-481d-b617-e3fc9314f595	Duration: 2501.80 ms	Billed Duration: 2502 ms	Memory Size: 512 MB	Max Memory Used: 47 MB

Pythonの結果。

START RequestId: 16797d8f-6abf-47f2-ab02-7414e89bf5d2 Version: $LATEST
{'name': 'Test', 'code': 1939, 'tags': 'Dev', 'lang': 'ja'}
{'name': 'IT Division', 'code': 1, 'tags': 'Prod', 'lang': 'ja'}
{'name': 'Sample', 'code': 31, 'lang': 'en'}
{'name': 'Classmethod', 'code': 2, 'lang': 'en'}
{'name': 'Classmethod2', 'code': 19}
END RequestId: 16797d8f-6abf-47f2-ab02-7414e89bf5d2
REPORT RequestId: cb8cb604-6153-4f51-884f-4b782122119c	Duration: 179.49 ms	Billed Duration: 180 ms	Memory Size: 128 MB	Max Memory Used: 80 MB
REPORT RequestId: 715763e4-f6dd-4bb0-8c0a-0173ccf63d2b	Duration: 24.90 ms	Billed Duration: 25 ms	Memory Size: 128 MB	Max Memory Used: 81 MB
REPORT RequestId: 3baf17be-2a6b-4e6f-b522-4aee942c43cb	Duration: 37.96 ms	Billed Duration: 38 ms	Memory Size: 128 MB	Max Memory Used: 80 MB
REPORT RequestId: 3c5aae3a-2575-4364-a7b9-927e6554d446	Duration: 42.81 ms	Billed Duration: 43 ms	Memory Size: 128 MB	Max Memory Used: 80 MB
REPORT RequestId: 51f995a3-20a7-4251-8713-bd8f64aea187	Duration: 40.54 ms	Billed Duration: 41 ms	Memory Size: 128 MB	Max Memory Used: 80 MB

JavaScriptの結果。

START RequestId: e75761b2-4583-4cb1-8c11-d3f2287b65aa Version: $LATEST
2021-02-26T02:37:22.180Z	e75761b2-4583-4cb1-8c11-d3f2287b65aa	INFO	{ name: 'Test', code: 1939, tags: 'Dev', lang: 'ja' }
2021-02-26T02:37:22.187Z	e75761b2-4583-4cb1-8c11-d3f2287b65aa	INFO	{ name: 'IT Division', code: 1, tags: 'Prod', lang: 'ja' }
2021-02-26T02:37:22.207Z	e75761b2-4583-4cb1-8c11-d3f2287b65aa	INFO	{ name: 'Sample', code: 31, lang: 'en' }
2021-02-26T02:37:22.207Z	e75761b2-4583-4cb1-8c11-d3f2287b65aa	INFO	{ name: 'Classmethod', code: 2, lang: 'en' }
2021-02-26T02:37:22.207Z	e75761b2-4583-4cb1-8c11-d3f2287b65aa	INFO	{ name: 'Classmethod2', code: 19 }
END RequestId: e75761b2-4583-4cb1-8c11-d3f2287b65aa
REPORT RequestId: 74d14063-63f4-4416-a991-4baf4eec444d	Duration: 70.48 ms	Billed Duration: 71 ms	Memory Size: 128 MB	Max Memory Used: 89 MB	
REPORT RequestId: e4cf838f-d085-403b-952f-5f7a10e9c89e	Duration: 532.37 ms	Billed Duration: 533 ms	Memory Size: 128 MB	Max Memory Used: 90 MB	
REPORT RequestId: 27896427-f6f8-4672-945a-371dfe271b73	Duration: 44.37 ms	Billed Duration: 45 ms	Memory Size: 128 MB	Max Memory Used: 90 MB	
REPORT RequestId: 8abc9ab1-6917-4e7d-8ace-681e9276ab60	Duration: 33.97 ms	Billed Duration: 34 ms	Memory Size: 128 MB	Max Memory Used: 90 MB	
REPORT RequestId: 4edaa2e8-8cbb-4bfe-b547-6837e87f731b	Duration: 33.67 ms	Billed Duration: 34 ms	Memory Size: 128 MB	Max Memory Used: 90 MB

PythonとJavaScriptは早いですね。計測回数が少ないので外れ値が目立ちますが、おそらく回数を増やせばいい具合に収束すると思われます。

一番問題なのは、Rust + AWS CLIはメモリ割り当てを512MBに上げてこの遅さなんですよね。外部コマンド呼び出しが悪いのか、一度ファイルに書き出しているのが遅さの原因なのか。そこまで調べきれていませんが、PythonやJavaScriptの最高速と比べて2桁違うのは結構な違いかと思われます。

まとめ

結構頑張ってRustからS3 Selectをやってみたものの、比較してみると採用はしにくそうです。これであれば、AWS CLI経由ではなくて、 S3 Selectを実行するPython Lambda関数 をRustから呼び出した方が早そうですね。