AWS Lambda と Python Boto3 を使って Amazon S3 Express One Zone の性能比較をしてみた

2023/12/22に公開

Amazon S3 Express One Zone について

Amazon S3 Express One Zone については、JAWS-UG 名古屋 2023年 AWS re:Inventの復習 - JAWS-UG名古屋 | Doorkeeper の LT 登壇資料でざっくり説明したので、以下をご確認ください!

Lambda からアップロード・ダウンロード速度を計測する

AWS Lambda からアクセスする速度を time を使って簡易計測してみました!

最新の boto3 を追加する

まず、Boto3 のバージョンを確認してみます。

import boto3

def lambda_handler(event, context):
    print(f'boto3 version: {boto3.__version__}')

出力結果:

boto3 version: 1.28.72

なお、以下のページで ランタイムのバージョンとそのSDKのバージョンが確認できます。
(少し更新が遅れていそうです)

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-runtimes.html

Amazon S3 Express One Zone を扱うためには、
Boto3 を最新のものにアップデートする必要があるので、
Lambda レイヤーで最新の boto3 を追加します。

CloudShell で以下のコマンド実行することで、レイヤーを追加できます。

mkdir python
pip3 install boto3 -t ./python
zip -r ./python.zip .
aws lambda publish-layer-version --layer-name python-layer --zip-file fileb://python.zip --compatible-runtimes python3.12
aws lambda update-function-configuration --function-name {バケット名} --layers arn:aws:lambda:ap-northeast-1:{アカウントID}:layer:python-layer:1

バージョンアップしたことを確認します。

出力結果:

boto3 version: 1.34.2

Python のソースコード

標準ライブラリ time で開始と終了時間の差分を出して、パフォーマンスを測っていきます。

import boto3
import json
import time

def upload_files(s3_client, count=10, is_standard=True, file_name="sample.csv"):
    # 開始時間
    start = time.time()
    file_path = f'./files/{file_name}'
    bucket_name = 'bucket-name'
    args = None

    if not is_standard:
        bucket_name = 'bucket-name-express-one-zone'
        args = {'StorageClass': 'EXPRESS_ONEZONE'}

    # ファイルアップロード
    for i in range(count):
        s3_client.upload_file(file_path, bucket_name, f'temp/dummy_{i}.csv', ExtraArgs=args)

    # 実行時間 = 終了時間 - 開始時間
    elapsed = time.time() - start

    return elapsed

def download_files(s3_client, count=10, is_standard=True):
    # 開始時間
    start = time.time()
    bucket_name = 'bucket-name'

    if not is_standard:
        bucket_name = 'bucket-name-express-one-zone'

    # ファイルアップロード
    for i in range(count):
        s3_client.download_file(bucket_name, f'temp/dummy_{i}.csv', f'/tmp/dummy.csv')

    # 実行時間 = 終了時間 - 開始時間
    elapsed = time.time() - start

    return elapsed

def lambda_handler(event, context):
    print(f'event: {event}')
    
    # S3クライアント
    s3_client = boto3.client('s3', region_name='ap-northeast-1')

    if event["method"] == "upload":
        # Standard Storage
        standard_elapsed = upload_files(s3_client, event["count"], True, event["file_name"])
        print(f'Standard: {standard_elapsed}')
    
        # Express One Zone Storage
        ex_elapsed = upload_files(s3_client, event["count"], False, event["file_name"])
        print(f'Express One Zone: {ex_elapsed}')
        
        performance = standard_elapsed/ex_elapsed*100
        print(f'Performance: {performance}%')

    if event["method"] == "download":
        # Standard Storage
        standard_elapsed = download_files(s3_client, event["count"], True)
        print(f'Standard: {standard_elapsed}')
    
        # Express One Zone Storage
        ex_elapsed = download_files(s3_client, event["count"], False)
        print(f'Express One Zone: {ex_elapsed}')
        
        performance = standard_elapsed/ex_elapsed*100
        print(f'Performance: {performance}%')

    return {
        'statusCode': 200,
        'body': json.dumps('Success!')
    }

テストのリクエストは以下のように設定します。

  • count: 実行回数
  • method: upload or download
  • file_name: ファイル名(files フォルダに格納しておく)
{
    "count": 1000,
    "method": "upload",
    "file_name": "sample.csv"
}

アップロード

アップロードでは 4 倍前後の性能差が出ました。

ファイルサイズ 標準 (s) Express One Zone (s) 性能差 (%)
1.4 KB 433.41064834594727 112.82194685935974 384.1545553953463
30.2 KB 531.4169392585754 97.15754437446594 546.9641525833351
2.3 MB 334.6522886753082 93.27737593650818 358.7711224885856

2.3 MB のファイルでは 15 分でタイムアウトしたため、3000 回で実行しています。

ダウンロード

ダウンロードでも、4 倍前後の性能差が出ました。

ファイルサイズ 標準 (s) Express One Zone (s) 性能差 (%)
1.4 KB 532.2136123180389 133.81951093673706 397.71002643227564
30.2 KB 589.7837207317352 132.4846966266632 445.171205240197
2.3 MB 411.7004597187042 122.26232695579529 336.735337834325

2.3 MB のファイルでは 15 分でタイムアウトしたため、3000 回で実行しています。

性能差が出なかったケース

最初、メモリが最小の 128 MB で実行していたとき、性能差がほとんど出ませんでした笑

以下は 1000 回実行時の結果です。

ファイルサイズ 標準 (s) Express One Zone (s) 性能差 (%)
2.3 MB 507.78030037879944 364.30071902275085 139.38492950025943

マシンスペックに依存するのも、当然と言えば当然の結果かと思われます。

Rust は S3 Express One Zone に非対応?

AWS SDK for Rust は、まだ S3 Express One Zone に非対応のようでした。

サポートしてほしいという issue が上がっていました。

https://github.com/awslabs/aws-sdk-rust/issues/992

PutObject を試してみた

AWS SDK for Rust で PutObject だけ試してみました。

(本当は、ここまで実装した後に Express One Zone に非対応って知りましたw)

Cargo.toml
[dependencies]
dotenvx = "0.0.2"
tokio = { version = "1.35.0", features = ["full"] }
aws-config= { version = "1.0.3", features = ["behavior-version-latest"] }
aws-sdk-s3= { version = "1.5.0", features = ["rt-tokio"] }
main.rs
use aws_config::meta::region::RegionProviderChain;
use aws_sdk_s3::config::Region;
use aws_sdk_s3::primitives::ByteStream;
use aws_sdk_s3::Client;
use std::fs;

#[tokio::main]
async fn main() {
    let region = Some("ap-northeast-1");
    let region_provider = RegionProviderChain::first_try(region.map(Region::new))
        .or_default_provider()
        .or_else(Region::new("ap-northeast-1"));
    let shared_config = aws_config::from_env().region(region_provider).load().await;
    let client = Client::new(&shared_config);

    let file_data = fs::read("./files/dummy.csv").expect("Failed to read 'dummy.csv'.");
    let body = ByteStream::from(file_data);

    let request = client
        .put_object()
        .bucket("bucket-name")
        .key("dummy.csv")
        .body(body)
        .send()
        .await;

    match request {
        Ok(r) => {
            println!("{:?}", r);
        }
        Err(e) => {
            eprintln!("{:?}", e)
        }
    }
}

まとめ

10 倍の性能は出せませんでした!

精進します🔥🔥🔥

参考

コラボスタイル Developers

Discussion