🐈

S3 Files 性能評価

に公開

はじめに

Zenn 読者のみなさん、こんにちは。e-dash VPoE の伊藤です。

2026年4月、AWS から S3 Files がリリースされました。
S3 バケットをファイルシステムとしてマウントして使える、という非常にインパクトのある機能で、ユースケース次第では大幅なストレージコスト削減やアーキテクチャ簡素化が期待できそうです。一方で、「実際どの程度の性能が出るの?」「EBS や EFS の代替としてどの程度使えるの?」という疑問もあります。

そこで今回は、S3 Files の性能を実機で評価し、他のストレージ構成(エフェメラルストレージ・EBS・EFS)と比較してみたので、その結果を共有します。

なお、当初は比較対象に Mountpoint for S3 も含める予定でした。しかし実機で検証した結果、fio による汎用ベンチマークが構造的に成立しないことがわかったため、今回は比較対象から外しています。詳細は後述の「Mountpoint for S3 を比較対象から除外した理由」を参照してください。

またお時間がある方は、S3 Files 開発チームの Andy Warfield 記事S3 Files and the changing face of S3 をぜひ読んでみてください。

時間がない人向けのサマリ

  • S3 Files の性能を EC2 からマウントして fio によって実測
  • 比較対象として、エフェメラルストレージ / EBS(gp3, io2, st1)/ EFS を同一条件で計測
  • Mountpoint for S3 は比較対象から除外(fio による汎用ベンチマークが Mountpoint の設計制約により成立しないため)
  • 結果
    • シーケンシャルアクセスでは EFS と同等以上の高スループット(numjobs=64 で read 約 33.5 GB/s, write 約 5.5 GB/s)を確認
    • 一方、低並列度(numjobs=1, iodepth=1)での 4KiB ランダム IOPS はネットワーク FS としての特性が出ており、エフェメラルストレージには大きく劣る
  • 結論
    • 大きめのファイルをシーケンシャルに読み書きする用途(学習データセット配布・分析・アーカイブ)では非常に強力な選択肢
    • 一方で小さなファイルへのランダムアクセスが支配的なワークロードでは EBS / ephemeral のほうが有利

S3 Files とは

S3 Files は、S3 バケットに対して POSIX 互換のファイルシステムインタフェースを提供する AWS のマネージドサービスです。従来、S3 をファイルシステム的に扱うには Mountpoint for S3 等のクライアント側マウントを使う必要がありました。S3 Files ではマネージドなファイルシステム層を経由することで、より高い性能・より広い POSIX 互換性を実現しているのが特徴です。EC2 だけでなく、Lambda、ECS/EKS など多くのコンピューティングサービスに対応しています。

料金体系

S3 Files の料金は、おおまかに以下の要素で構成されます(公式料金ページ )。

  • ファイルシステム容量
  • リクエスト料金(読み取り・書き込み)
  • データ転送料金(同一リージョン内は通常無料)
  • S3 バケット本体の通常のストレージ料金(S3 Files 経由でアクセスしても、データは S3 に格納される)

素のS3をそのまま使っているわけではなく「高性能ストレージ」が透過的に提供されるサービスになりますので、その分の追加料金が掛かってくる点に注意が必要です。

東京リージョンの S3 Files の費用は以下のようになっています。

分類 料金
ファイルシステム容量 USD 0.36/GB-月
高性能ストレージからのファイルの読み取り USD 0.04/GB
S3 バケットからの直接的なファイルの読み取り 無料
ファイルの書き込み USD 0.07/GB

仮に 100GB のデータを「ファイルシステム容量として常時保持しつつ、毎月 100GB の読み取り/100GB の書き込みを行う」ケースを試算すると以下のようになります。

サービス 単価 内訳 月額 (USD)
S3(標準) 0.025 USD/GB-月 容量 100 GB 2.5
S3 Files 0.36 USD/GB-月 高性能ストレージ容量 100 GB 36.0
S3 Files 0.04 USD/GB 読み取り 100 GB 4.0
S3 Files 0.07 USD/GB 書き込み 100 GB 7.0
合計 49.5

S3 Files の実体は S3 バケットなので、上記のように S3 の容量料金が同時に発生する 点に注意が必要です。特に高性能ストレージの容量単価(USD 0.36/GB-月)は S3 Standard(USD 0.025/GB-月)の約 14 倍 です。保存期間が長いコールドデータをそのまま S3 Files に置き続けると割高になります。導入前には「どれくらいの期間、どの程度のデータを高性能ストレージ層に置くべきか」の費用対効果を十分にチェックしましょう。

性能仕様

公式ドキュメント に記載されている性能仕様は以下の通りです。

項目 仕様
ファイルシステムあたりの集約リードスループット 最大 数 TB/s
ファイルシステムあたりの集約ライトスループット 1〜5 GiB/s
S3 バケットあたりのリード IOPS 上限 無制限(同一バケットに複数ファイルシステムをアタッチ可能)
S3 バケットあたりのライト IOPS 上限 無制限(同一バケットに複数ファイルシステムをアタッチ可能)
ファイルシステムあたりのリード IOPS 上限 250,000
ファイルシステムあたりのライト IOPS 上限 50,000
クライアントあたりのリードスループット上限 3 GiB/s

EBS のスペックと比較してみましょう。

タイプ gp3 io2
最大 IOPS 80,000 256,000
最大スループット 2,000 MiB/秒 4,000 MiB/秒

スペック上は io2 に匹敵する性能が出ることになっており、これが本当に出るのかどうかを確認していきましょう。

ユースケース

S3 Files が有効なユースケースは以下のようなものが考えられます。

  • データレイク/分析基盤からの読み出し
  • 機械学習の学習データセット配布
  • ログアーカイブ処理
  • バックアップ処理

大量のデータを S3 から読み書きするようなケースで恩恵を受けやすいでしょう。そうでないケースでも、実装がシンプルになったり API のエラーハンドリングが不要になったりして、ちょっとした S3 アクセスでもメリットを得られる可能性があります。あとはハイパフォーマンスなので、その点でも恩恵を受けられます。

Lambda からもマウントできるため、Lambda で大容量のデータを扱いやすくなったというのもあります。こちらも EC2 などでやらざるを得なかったワークロードについて、Lambda 化によるコスト削減などを検討する余地があります。

制限事項

一方で、利用上注意すべき制限事項もあります。

  • データ整合性として、S3 Files 経由の書き込みと S3 API 経由の書き込みが混在した場合、整合性保証の範囲に注意が必要
  • POSIX 互換といっても一部のシステムコールや権限モデルには制限あり
  • スモールファイル大量生成のようなワークロードは、S3 の特性上あまり向かない可能性がある
    • S3 Files は POSIX 互換のファイルシステム層を提供するが、その裏側のオブジェクト永続化は依然として S3 の PutObject / GetObject の世界である。1 ファイル=最低 1 オブジェクトが基本となるため、KB オーダーの小ファイルを毎秒数千〜数万件生成するようなワークロードでは、リクエスト数の単価による影響が強く出る。読み取り USD 0.04/GB、書き込み USD 0.07/GB はバイト課金だが、S3 側の API 呼び出しコストや prefix あたりのスループット制限が効いてくる
    • また、メタデータ操作(list / stat / open)はファイルシステム層で吸収される。とはいえ新規ファイル作成のたびに S3 側の create / commit が走るため、「並列度を上げてもスループットが頭打ちになりやすい」傾向がある
    • 同様のワークロードに対しては、ローカル EBS / ephemeral にバッチ単位で書き出したうえ、定期的に大きめのオブジェクト(tar / Parquet など)にまとめてから S3 / S3 Files へ転送する方式が、コスト・性能の両面で有利になることが多い
  • ファイルシステム/クライアントあたりの性能上限(前述)

詳細は S3 Files のユーザーガイド と、性能・整合性の前提が記載された パフォーマンスに関するドキュメント を参照してください。

Mountpoint for S3 等の従来の S3 マウントツールとの違い

S3 Files は POSIX 互換ですが、Mountpoint for S3 等、s3fs、goofys は POSIX 互換のファイルシステムではありません。同じ様に S3 を マウントできるということで一見同じ機能を提供してくれるように見えますが、実態としては異なります。
S3 Files は EFS のような「マネージドなファイルシステム層」を提供します。一方、Mountpoint for S3 は FUSE を介して S3 API をファイル操作に見せかけているだけの薄いラッパーです。API をラッピングしているだけのため、アーキテクチャとして明確に異なります。

POSIX 互換とは

POSIX(Portable Operating System Interface)はファイル操作に関する標準仕様です。Linux / macOS などの UNIX 系 OS が共通して提供するインタフェースに該当します。一般的なアプリケーションが暗黙に前提としている「ファイルシステムらしい挙動」のことだと考えてもらえれば概ね正しいです。具体的には以下のような操作が含まれます。

  • 任意オフセットへの read / writepread / pwrite、ファイルの一部だけを書き換える)
  • O_RDWR での openO_APPEND / O_DIRECT などのフラグ
  • fallocate / ftruncate によるサイズの事前確保や切り詰め
  • ファイルロックflockfcntl(F_SETLK)
  • uid/gid/permission に基づく権限モデル、chmod / chown
  • atomic な rename、ハードリンク・シンボリックリンク
  • 空ディレクトリ を含むディレクトリツリーの実体としての存在

S3 Files はこれらをマネージドなファイルシステム層で提供するため、既存のアプリケーションをほぼ無改修で乗せ換えられる、というのが POSIX 互換の意味です。

Mountpoint for S3 の弱点

これに対して Mountpoint for S3 は POSIX 互換ではないため、以下のような「ファイルシステムでは当たり前」の操作が原理的にサポートされていません。

  • 任意オフセットへの書き込み(部分書き換え) ができない(S3 PutObject がオブジェクト全体を atomic に置き換える API のため)
  • 一度 close したファイルを再 open して書き直す ことができない(truncate 後に先頭からシーケンシャルに書く場合のみ上書き可)
  • O_RDWR で開けない(読み取り専用 or 書き込み専用のいずれかでしか open できない)
  • O_DIRECT など、ブロックデバイス前提のフラグが非対応
  • fallocate / ftruncate による事前サイズ確保ができない
  • 同一ファイルへの並列書き込み が意味を持たない(最後に close したクライアントの内容で全置換される)
  • 空ディレクトリは実体として存在せず、扱いに不整合が出るケースもある

これらは Mountpoint for S3 の設計上の制約であり、本記事後半の Mountpoint for S3 を比較対象から除外した理由 で実機検証の結果と合わせて詳述しています。

性能評価

S3 Files のカタログスペックは魅力的ですが、実際にその性能が出るのか実機で試してみます。

前提

EC2 からマウントされた複数種類のストレージに対して、同一のベンチマークを実行し性能を比較します。評価対象とする構成は以下の通りです。

# 構成 用途イメージ
1 EC2 + エフェメラルストレージ(インスタンスストア) 性能の上限値としての比較基準
2 EC2 + EBS(gp3: 汎用SSD) 一般的な汎用用途の比較基準
3 EC2 + EBS(io2: プロビジョンドIOPS SSD) IOPS重視のワークロード
4 EC2 + EBS(st1: スループット最適化HDD) シーケンシャルIO重視のワークロード
5 EFS 共有ファイルシステムの既存選択肢
6 EC2 + S3 Files 今回の主役
7 EC2 + Mountpoint for S3 従来の選択肢。今回は事情により評価せず(後述)

評価方法

S3 Files の性能仕様をベースに、各種構成パターンで性能を測定します。

環境

  • インスタンスは EC2(m7g.xlarge / m7gd.xlarge)
  • OS は Amazon Linux 2023
  • ベンチマークツールは fio

測定項目

  • シーケンシャルリードスループット(MB/s)
  • シーケンシャルライトスループット(MB/s)
  • ランダムリード IOPS
  • ランダムライト IOPS
  • レイテンシ(avg / p99)

測定条件

  • ブロックサイズ:4KiB(IOPS測定)/ 1MiB(スループット測定)
  • ジョブ並列度:1, 4, 16, 64(スケーラビリティ確認のため複数パターン)
  • ファイルサイズ:10GiB(キャッシュヒットの影響を最小化するため)
  • fio オプション
    • ネットワーク FS(EFS / S3 Files)
      • --ioengine=psync --direct=0 --iodepth=1
    • ローカルブロックデバイス(EBS / エフェメラルストレージ)
      • --ioengine=libaio --direct=1 --iodepth=32

ネットワーク FS とローカルブロックデバイスで fio のオプションを変えているのは、それぞれの I/O スタックでは、意味のある計測のための設定が異なるためです。

オプション ネットワーク FS(EFS / S3 Files) ローカルブロックデバイス(EBS / ephemeral)
--ioengine psync(同期 pread / pwrite)。NFS / S3 Files は I/O ごとに RPC が発生するため、非同期 submit のメリットが小さく、psync がアプリケーションの実利用に近い形 libaio(Linux AIO)。ブロックデバイスは複数 I/O を同時に in-flight できる前提で設計されているため、AIO で並列 submit するのが本来の性能を引き出す形
--direct 0(buffered I/O)。NFS / S3 Files は クライアント側で O_DIRECT を十分には実装しておらず、指定しても buffered にフォールバックするか EINVAL になる。ページキャッシュ込みの値こそが実アプリケーションでの体感に近い 1(O_DIRECT)。ページキャッシュをバイパスし、ブロックデバイスの素の性能を測る。同一インスタンスメモリ内のキャッシュヒットによる過大評価を防ぐ
--iodepth 1psync は同期 I/O なので iodepth>1 を指定しても意味が無く、並列度は numjobs で与える 32。NVMe / EBS は内部で多数のリクエストをキューイングできるため、iodepth を大きく取らないと本来の IOPS が出ない

ネットワーク FS 側を --direct=1 --ioengine=libaio で測ろうとすると、そもそも O_DIRECT が効かずページキャッシュ経由になります。もしくはエラーで弾かれるため、意味のある比較になりません。一方ローカルブロックデバイス側を --direct=0 --iodepth=1 で測ると、ページキャッシュがすべてを吸収してしまい NVMe / EBS の実力が見えなくなります。両者の 「本来の使い方」 に合わせたオプションを選んでいる、というのがここでの方針です。

実行コマンド例

手動で再現確認したいときの参考用に、各 EC2 上で実際に流している fio コマンドを掲載しておきます。S3 Files / EFS はいずれもネットワーク FS なので fio のエンジンオプションは共通で、違いはマウントコマンドだけです。

EC2 を作成する。

ベンチマーク対象の EC2 を起動します。事前準備として以下を用意しておきます。

  • SSH キーペア(自分用に作成)
  • セキュリティグループ(自分の IP からの SSH (TCP 22) inbound を許可しておく。EFS や S3 Files を使う場合は、同 SG 内通信で NFS (TCP 2049) が許可されるよう自己参照ルールも入れておく)
  • IAM instance profile(S3 Files をマウントする場合に必須。AmazonS3FilesClientFullAccess 相当の権限を持つロールを attach しておく)

SSH キーペアは下記コマンドで作成できます。

aws ec2 create-key-pair --key-name my-benchmark-key \
    --query KeyMaterial --output text > my-benchmark-key.pem
chmod 400 my-benchmark-key.pem
REGION=ap-northeast-1
SUBNET_ID=subnet-XXXXXXXX
SECURITY_GROUP_ID=sg-XXXXXXXX
KEY_NAME=my-benchmark-key                  # 事前作成した EC2 キーペア
IAM_INSTANCE_PROFILE=bench-s3files-client  # S3 Files マウント時に必須
INSTANCE_TYPE=m7g.xlarge                   # ephemeral を使う場合は m7gd.xlarge など NVMe 付き

# 最新の Amazon Linux 2023 (arm64) AMI ID を SSM Parameter Store から取得
AMI_ID=$(aws ssm get-parameter \
    --name "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64" \
    --query 'Parameter.Value' --output text)
echo "AMI_ID=$AMI_ID"

# EC2 を起動
INSTANCE_ID=$(aws ec2 run-instances \
    --image-id "$AMI_ID" \
    --count 1 \
    --instance-type "$INSTANCE_TYPE" \
    --key-name "$KEY_NAME" \
    --security-group-ids "$SECURITY_GROUP_ID" \
    --subnet-id "$SUBNET_ID" \
    --iam-instance-profile "Name=${IAM_INSTANCE_PROFILE}" \
    --tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=bench-storage}]" \
    --query 'Instances[0].InstanceId' --output text)
echo "INSTANCE_ID=$INSTANCE_ID"

# running になるまで待機
aws ec2 wait instance-running --instance-ids "$INSTANCE_ID"

# パブリック IP を取得 (ssh 接続用)
PUBLIC_IP=$(aws ec2 describe-instances \
    --instance-ids "$INSTANCE_ID" \
    --query 'Reservations[0].Instances[0].PublicIpAddress' --output text)
echo "PUBLIC_IP=$PUBLIC_IP"

# sshd が立ち上がるまで待機
until ssh -i "${KEY_NAME}.pem" \
    -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
    -o LogLevel=ERROR -o ConnectTimeout=5 \
    "ec2-user@${PUBLIC_IP}" "echo ready" >/dev/null 2>&1; do
    echo "SSH 接続待機中..."
    sleep 10
done
echo "SSH 接続確立: $PUBLIC_IP"

EC2 が起動したら、ベンチマークに必要なパッケージを入れておきます。

ssh -i "${KEY_NAME}.pem" \
    -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
    "ec2-user@${PUBLIC_IP}" \
    'sudo dnf install -y fio jq xfsprogs nfs-utils amazon-efs-utils'

amazon-efs-utils は EFS マウント用の mount.efs と、S3 Files マウント用の mount.s3files の両方を含んでいます。xfsprogs は EBS / エフェメラルストレージを XFS でフォーマットする際に必要です。

EFS を作成する。

EC2 と同じ VPC のサブネット・セキュリティグループ(NFS の TCP 2049 を許可しておくこと)を控えておきます。

SUBNET_ID=subnet-XXXXXXXX           # EC2 と同じサブネット
SECURITY_GROUP_ID=sg-XXXXXXXX       # NFS (TCP 2049) を許可した SG

# ファイルシステムを作成
EFS_ID=$(aws efs create-file-system \
    --performance-mode generalPurpose \
    --throughput-mode elastic \
    --tags "Key=Name,Value=bench-efs" \
    --query 'FileSystemId' --output text)
echo "EFS_ID=$EFS_ID"

# ファイルシステムが available になるまで待つ
while :; do
    state=$(aws efs describe-file-systems --file-system-id "$EFS_ID" \
        --query 'FileSystems[0].LifeCycleState' --output text)
    [[ "$state" == "available" ]] && break
    echo "file-system state: $state"
    sleep 5
done

# マウントターゲット(クライアントが NFS で繋ぐ ENI)を作成
EFS_MT_ID=$(aws efs create-mount-target \
    --file-system-id "$EFS_ID" \
    --subnet-id "$SUBNET_ID" \
    --security-groups "$SECURITY_GROUP_ID" \
    --query 'MountTargetId' --output text)
echo "EFS_MT_ID=$EFS_MT_ID"

# マウントターゲットが available になるまで待つ
while :; do
    state=$(aws efs describe-mount-targets --mount-target-id "$EFS_MT_ID" \
        --query 'MountTargets[0].LifeCycleState' --output text)
    [[ "$state" == "available" ]] && break
    echo "mount-target state: $state"
    sleep 5
done

--throughput-mode elastic を指定すると、使った分だけ自動でスケールします。突発的な高スループットを要求するベンチマーク用途のようなワークロードに向いています。固定スループットが必要な場合は --throughput-mode provisioned --provisioned-throughput-in-mibps <値> で指定できます。

S3 Files を作成する。

事前準備として、S3 Files が S3 バケットへ読み書きするための IAM ロール が必要です。AssumeRole の信頼ポリシーに S3 Files サービスプリンシパル(s3files.amazonaws.com)を含めます。さらに AmazonS3FilesServiceRolePolicy 相当の権限を付与したロールを事前に作成し、その ARN を控えておきます。

REGION=ap-northeast-1
SUBNET_ID=subnet-XXXXXXXX           # EC2 と同じサブネット
SECURITY_GROUP_ID=sg-XXXXXXXX       # マウントターゲット用 SG
S3FILES_ROLE_ARN=arn:aws:iam::<ACCOUNT_ID>:role/<ROLE_NAME>   # 事前に作成した IAM role

# 1. S3 バケットを作成 (S3 Files のデータ実体)
S3_BUCKET=bench-s3files-$(date +%Y%m%d-%H%M%S)-$(openssl rand -hex 4)
aws s3api create-bucket \
    --bucket "$S3_BUCKET" \
    --create-bucket-configuration "LocationConstraint=${REGION}"

# 2. バージョニングを有効化 (S3 Files の必須要件)
aws s3api put-bucket-versioning \
    --bucket "$S3_BUCKET" \
    --versioning-configuration Status=Enabled

# 3. バケットポリシーで S3 Files 用 IAM ロールにアクセス権を付与
cat > /tmp/bucket-policy.json <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowS3FilesRoleAccess",
      "Effect": "Allow",
      "Principal": { "AWS": "${S3FILES_ROLE_ARN}" },
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject",
        "s3:GetObjectVersion",
        "s3:DeleteObjectVersion",
        "s3:ListBucket",
        "s3:ListBucketVersions",
        "s3:GetBucketLocation",
        "s3:GetBucketVersioning"
      ],
      "Resource": [
        "arn:aws:s3:::${S3_BUCKET}",
        "arn:aws:s3:::${S3_BUCKET}/*"
      ]
    }
  ]
}
EOF
aws s3api put-bucket-policy \
    --bucket "$S3_BUCKET" \
    --policy "file:///tmp/bucket-policy.json"

# 4. S3 Files ファイルシステムを作成 (--bucket には ARN を渡す)
S3FILES_FS_ID=$(aws s3files create-file-system \
    --region "$REGION" \
    --bucket "arn:aws:s3:::${S3_BUCKET}" \
    --role-arn "$S3FILES_ROLE_ARN" \
    --query 'fileSystemId' --output text)
echo "S3FILES_FS_ID=$S3FILES_FS_ID"

# ファイルシステムが available になるまで待つ (数分〜最大 20 分程度)
while :; do
    state=$(aws s3files get-file-system \
        --region "$REGION" \
        --file-system-id "$S3FILES_FS_ID" \
        --query 'status' --output text)
    [[ "$state" == "available" ]] && break
    echo "file-system status: $state"
    sleep 10
done

# 5. マウントターゲットを作成 (EC2 と同じサブネット・SG に配置)
S3FILES_MT_ID=$(aws s3files create-mount-target \
    --region "$REGION" \
    --file-system-id "$S3FILES_FS_ID" \
    --subnet-id "$SUBNET_ID" \
    --security-groups "$SECURITY_GROUP_ID" \
    --query 'mountTargetId' --output text)
echo "S3FILES_MT_ID=$S3FILES_MT_ID"

# マウントターゲットが available になるまで待つ
while :; do
    state=$(aws s3files get-mount-target \
        --region "$REGION" \
        --mount-target-id "$S3FILES_MT_ID" \
        --query 'status' --output text)
    [[ "$state" == "available" ]] && break
    echo "mount-target status: $state"
    sleep 10
done

ハマりどころとして、aws s3files create-file-system--bucket 引数の指定方法に注意が必要です。バケット名ではなく ARN(arn:aws:s3:::<bucket-name>)を要求する点が落とし穴になります。また、ファイルシステムが available になるまで実測で数分〜十数分かかることがあるため、待機ループのタイムアウトはやや長めに取っておくと安全です。

EFS をマウントする。

本ベンチでは マウントターゲットのプライベート IP を直接指定する方式 でマウントします。

EFS_IP=$(aws efs describe-mount-targets --mount-target-id "$EFS_MT_ID" \
    --query 'MountTargets[0].IpAddress' --output text)

sudo mkdir -p /mnt/bench
sudo mount -t nfs4 \
    -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport \
    ${EFS_IP}:/ /mnt/bench
S3 Files をマウントする。

事前準備として、amazon-efs-utils 由来の mount.s3files ヘルパーが必要です。Amazon Linux 2023 なら sudo dnf install -y amazon-efs-utils で導入できます。マウント先 EC2 には AmazonS3FilesClientFullAccess 相当の権限を持つ IAM instance profile をアタッチしておきます。マウント時の認証は、この instance profile 経由で透過的に行われます。

sudo mkdir -p /mnt/bench
sudo mount -t s3files ${S3FILES_FS_ID}:/ /mnt/bench

fio ウォームアップ(10GiB の事前確保)

読み込み系テストの初回で「ファイルが無い」エラーになるのを防ぐため、先に fio.dat を 10GiB 分書き込んで実体化させておきます。

cd /mnt/bench
sudo fio --name=warmup \
    --filename=/mnt/bench/fio.dat \
    --rw=write \
    --bs=1M \
    --size=10G \
    --numjobs=1 \
    --ioengine=psync --direct=0 --iodepth=1 \
    --create_only=1 \
    --output-format=normal
fio ジョブを実行する。

シーケンシャルリード / シーケンシャルライト / ランダムリード / ランダムライトの 4 パターンを実行します。それぞれ並列度 numjobs = 1, 4, 16, 64 の 4 段階で測定するため、合計 16 ジョブとなります。下記コマンドの <pattern> <rw> <bs> <numjobs> を表に従って差し替えて実行します。

パターン --rw --bs
seq-read(シーケンシャルリード) read 1M
seq-write(シーケンシャルライト) write 1M
rand-read(ランダムリード) randread 4k
rand-write(ランダムライト) randwrite 4k
cd /mnt/bench
sudo fio --name=<pattern> \
    --filename=/mnt/bench/fio.dat \
    --rw=<rw> \
    --bs=<bs> \
    --size=10G \
    --numjobs=<numjobs> \
    --runtime=30 \
    --time_based \
    --group_reporting \
    --ioengine=psync --direct=0 --iodepth=1 \
    --lat_percentiles=1 \
    --output-format=json

たとえばシーケンシャルリードを並列度 4 で実行する場合は次のようになります。

sudo fio --name=seq-read \
    --filename=/mnt/bench/fio.dat \
    --rw=read --bs=1M --size=10G --numjobs=4 \
    --runtime=30 --time_based --group_reporting \
    --ioengine=psync --direct=0 --iodepth=1 \
    --lat_percentiles=1 --output-format=json

エンジンオプション(--ioengine=psync --direct=0 --iodepth=1)は EFS / S3 Files のような ネットワーク FS 用 の設定です。O_DIRECTlibaio がサポートされていないため、psync + buffered I/O で測定しています。EBS / エフェメラルストレージ(ブロックデバイス)の場合は --ioengine=libaio --direct=1 --iodepth=32 のほうがハードウェア性能を引き出せます。

前提条件

公平に比較するため、以下のルールで実施します。

  • 5回程度試行し、平均を取る
  • AZ は試行のたびにランダムで選択する
  • 試行のたびにインスタンスを作成・削除する
    • 同じインスタンスタイプを指定しても、物理的に性能の異なるマシンで起動する可能性があるため(いわゆる インスタンスガチャ 対策)
  • ストレージも試行ごとに新規作成し、ウォームアップ手順を統一する

5 試行の平均値を iodepth=1, ファイルサイズ 10 GiB の条件で計測しました。numjobs1 / 4 / 16 / 64 の 4 段階で振った結果を、各表でカラム別に並べています。並列度に対するスケーラビリティの議論は「考察」で触れます。

シーケンシャルリードスループット(MB/s)

構成 numjobs=1 numjobs=4 numjobs=16 numjobs=64 備考
エフェメラル 319 315 319 58,139 numjobs=64 でページキャッシュが効き始め一気に伸びる
EBS (gp3) 126 129 126 129 デフォルトの 125 MB/s ベースラインに張り付き
EBS (io2) 1,197 1,232 1,232 1,232 プロビジョンド帯域に律速。numjobs を増やしても頭打ち
EBS (st1) 130 71 66 72 HDD バースト消化後はベースラインに張り付く
EFS 515 1,946 8,065 32,155 numjobs に対してほぼ線形にスケール
S3 Files 896 2,169 9,182 33,546 numjobs=64 で 33 GB/s。クライアント上限 3 GiB/s を超えるのはページキャッシュ寄与

シーケンシャルライトスループット(MB/s)

構成 numjobs=1 numjobs=4 numjobs=16 numjobs=64 備考
エフェメラル 148 148 148 151 デバイス側のスループットに律速。numjobs に依存しない
EBS (gp3) 129 129 129 129 ベースラインに張り付き
EBS (io2) 1,229 1,223 1,226 1,232 プロビジョンド帯域。numjobs に依存しない
EBS (st1) 131 90 76 72 numjobs を増やすとシーク競合で逆に低下
EFS 592 2,129 4,950 5,703 numjobs=64 で公式仕様(1〜5 GiB/s)の上限に到達
S3 Files 940 3,728 4,972 5,506 numjobs=4 で公式仕様レンジに到達。numjobs=16 以降は飽和

ランダムリード IOPS(4KiB)

構成 numjobs=1 numjobs=4 numjobs=16 numjobs=64 備考
エフェメラル 70,697 70,802 70,761 515,741 numjobs=64 で NVMe の並列性が顕在化し 7 倍以上に
EBS (gp3) 3,100 3,099 3,100 3,100 ベースライン IOPS(3,000)に張り付き
EBS (io2) 10,333 10,333 10,333 10,333 プロビジョンド IOPS に張り付き
EBS (st1) 134 133 134 134 HDD ランダム IO は壊滅的
EFS 308 1,247 5,158 17,086 numjobs に対してほぼ線形
S3 Files 261 1,082 4,596 20,583 numjobs=64 で EFS を超え 20K IOPS 到達

ランダムライト IOPS(4KiB)

構成 numjobs=1 numjobs=4 numjobs=16 numjobs=64 備考
エフェメラル 18,197 23,658 35,116 15,805 numjobs=16 でピーク、64 は競合で低下
EBS (gp3) 3,087 3,081 3,088 3,100 ベースライン IOPS に張り付き
EBS (io2) 10,288 10,284 10,276 10,271 プロビジョンド IOPS に張り付き
EBS (st1) 133 134 133 133 HDD は実用にならない
EFS 27,475 13,909 11,062 10,631 numjobs=1 がピーク。write-back キャッシュが単一ライターで最も効率的
S3 Files 27,138 11,975 14,829 13,296 EFS と同様、numjobs=1 がピーク

レイテンシ

単位はすべて µs(マイクロ秒)(素データはナノ秒で、1,000 で割って表示)。

シーケンシャルリード(bs=1MiB)

avg (µs)

構成 numjobs=1 numjobs=4 numjobs=16 numjobs=64
エフェメラル 97.2 393.7 1,550.8 200.3
EBS (gp3) 246.8 957.7 3,715.6 11,732.9
EBS (io2) 25.9 100.6 402.1 1,598.4
EBS (st1) 237.7 1,750.6 6,801.4 16,112.9
EFS 1.9 1.9 1.9 1.9
S3 Files 1.1 2.0 1.8 2.0

p99 (µs)

構成 numjobs=1 numjobs=4 numjobs=16 numjobs=64
エフェメラル 112.8 454.2 2,216.3 351.7
EBS (gp3) 255.2 1,367.3 5,872.0 17,112.8
EBS (io2) 27.5 145.8 640.1 2,332.0
EBS (st1) 259.2 2,741.4 10,831.4 17,112.8
EFS 33.3 30.6 31.0 43.5
S3 Files 23.2 32.1 32.1 45.5

シーケンシャルライト(bs=1MiB)

avg (µs)

構成 numjobs=1 numjobs=4 numjobs=16 numjobs=64
エフェメラル 208.9 835.6 3,258.8 821.1
EBS (gp3) 240.2 962.1 3,690.8 11,656.9
EBS (io2) 25.2 101.3 404.1 1,595.1
EBS (st1) 236.1 1,371.5 6,002.4 15,880.4
EFS 1.6 1.8 3.1 11.0
S3 Files 1.0 0.9 3.1 11.3

p99 (µs)

構成 numjobs=1 numjobs=4 numjobs=16 numjobs=64
エフェメラル 224.6 1,016.7 4,519.8 1,104.8
EBS (gp3) 327.2 1,998.2 6,744.4 17,112.8
EBS (io2) 31.6 217.9 757.5 2,365.6
EBS (st1) 304.3 3,191.0 12,442.0 17,112.8
EFS 26.3 43.3 40.5 230.9
S3 Files 24.7 12.5 43.6 231.7

ランダムリード(bs=4KiB)

avg (µs)

構成 numjobs=1 numjobs=4 numjobs=16 numjobs=64
エフェメラル 0.5 1.8 7.2 0.6
EBS (gp3) 10.3 40.7 160.0 639.6
EBS (io2) 3.1 12.2 48.0 191.9
EBS (st1) 238.3 942.0 3,544.6 11,388.0
EFS 3.2 3.1 3.1 3.8
S3 Files 3.8 3.6 3.4 3.1

p99 (µs)

構成 numjobs=1 numjobs=4 numjobs=16 numjobs=64
エフェメラル 0.5 2.9 8.8 2.9
EBS (gp3) 11.0 87.6 371.2 1,082.1
EBS (io2) 3.5 16.8 107.9 417.3
EBS (st1) 254.8 1,922.7 8,435.6 17,112.8
EFS 5.5 5.4 5.6 7.7
S3 Files 5.7 5.5 5.0 6.0

ランダムライト(bs=4KiB)

考察

S3 Files の性能はおおむねスペック通り

  • seq-write は numjobs=4 で 3.7 GB/s、numjobs=64 で 5.5 GB/s と、公式仕様(1〜5 GiB/s)の上限近くまで素直に伸びる
  • seq-read は numjobs=64 で 33.5 GB/s。公式の「クライアントあたり 3 GiB/s」を桁違いに上回るが、これは後述のとおり --direct=0 のページキャッシュ寄与であり、生の帯域ではないと思われる
  • rand-read IOPS は numjobs=64 で 20,583 IOPS。並列度に対しほぼ線形にスケールし、さらに numjobs を上げれば公称 250,000 IOPS に近付く余地あり
  • rand-write IOPS は numjobs=1 で 27,138 IOPS とピーク。これは write-back キャッシュが単一ライターで最も効率的に動く効果で、numjobs を上げると競合で下がる(実 I/O 性能とは別物)

numjobs スケーラビリティの傾向

  • S3 Files / EFS は seq-read / rand-read で並列度に対しほぼ線形にスケールする。たとえば n=1 → n=64 で、seq-read は約 37 倍、rand-read は約 79 倍まで伸びた。よって「クライアントあたり 3 GiB/s」は 単一ジョブからの帯域上限と解釈するのが妥当だ
  • EBS(gp3 / io2)は低並列度からベースラインに張り付き、numjobs に依存しない。プロビジョン値にて飽和していると考えられる
  • エフェメラル seq-read だけは numjobs=16 まで頭打ちだったのが numjobs=64 で 58 GB/s に跳ねる。並列度の閾値を超えるとページキャッシュ局所性が変化する効果と推測される

fio オプション差について

本評価ではネットワーク FS とブロックデバイスで fio オプションを変えているため、ストレージ種別を跨いだ数値の単純比較には注意が必要です。

オプション差 ネットワーク FS(--direct=0, psync, iodepth=1 ブロックデバイス(--direct=1, libaio, iodepth=32
ページキャッシュ 経由する(10 GiB ファイルが 16 GiB RAM に収まり大半がキャッシュヒット) バイパス
seq-read 数値への影響 過大に出やすい(cache hit 寄与で 10+ GB/s) 公平
rand-read 数値への影響 過小に出やすい(iodepth=1 で 1 RPC ずつ直列、低並列度では RTT 律速) iodepth=32 で device の並列性をフル活用
永続化セマンティクス write-back で syscall は即時返却(書き込み完了とは言えない デバイスへの書き出し完了を待つ

つまり今回の数値は、EFS / S3 Files は seq-read で実力以上に「高く」、rand-read / rand-write で実力以上に「低く」出ている可能性が高いということです。実際に EFS / S3 Files にもブロックデバイスと同じ --ioengine=libaio --direct=1 --iodepth=32 でやってみたところ、以下のような結果になりました。

  • seq-read は 33 GB/s → 最大で 1.3 GB/s 程度まで低下
  • rand-read IOPS(numjobs=1)は 261 → 14,000 程度まで増加
  • rand-write IOPS(numjobs=1)は 27,138 → 4,000 程度まで低下
  • avg レイテンシは 1〜2 µs → 数 µs〜 数十 ms まで低下

このため、結果として EBS よりも高性能に見えますが、実際にそういうわけではない点に注意が必要です。Python の open().read() や Pandas の Parquet ロード等、実アプリケーションの多くは --direct=0(buffered I/O)でアクセスします。よって実利用との整合性で言えば、「アプリケーションから見た体感性能」としては妥当なのかなと考えます。

用途別の棲み分け

  • 大きめファイルのシーケンシャル read / write が中心となる用途(学習データセット配布、Parquet 等の分析、ログのストリーミング書き出し、バックアップ)では S3 Files / EFS が有力。POSIX 互換で既存アプリをほぼ無改修で乗せられる
  • 小さなファイルへの大量ランダムアクセスを単一プロセスで叩く用途(メタデータ系の中間ストア、OLTP 系)では、依然として EBS / ephemeral のほうが有利
  • 高並列度を確保できるランダムアクセスでは、S3 Files は numjobs=64 で EBS io2 の約 2 倍の rand-read IOPS を出すため、十分視野に入る
  • S3 Files と EFS を比較した場合、本評価では性能差は小さい。よって、選択基準は性能よりも「データの実体が S3 にあるか」「コスト構造(高性能ストレージ単価は S3 Standard の約 14 倍)」が中心になる

Mountpoint for S3 を比較対象から除外した理由

当初は、クライアントマウント方式との比較として EC2 + Mountpoint for S3 も同一条件(fio によるベンチマーク)で測定する計画でした。しかし実機で検証したところ、Mountpoint for S3 は fio による汎用ベンチマークが構造的に成立しないことが判明したため、今回の比較対象からは外しています。S3 Files の位置付けを理解するうえでも重要な点でもあるため、改めてここで整理しておきます。

実機で何が起きたか

Amazon Linux 2023 / m7g.xlarge 上で Mountpoint for S3 をマウントし、他ストレージと同じ fio シナリオを流してみました。マウント時のオプションは mount-s3 --allow-delete --allow-overwrite です。結果は次の通りでした。

  • write 系パターン(seq-write / rand-write)は全て即座に失敗
    • fio: io_u error on file /mnt/bench/fio.dat: Input/output error
    • func=write, error=Input/output error
    • func=open, error=No such file or directory
  • read 系パターン(seq-read / rand-read)も throughput 0 MiB/s(有効データが読み出せていない)
  • ウォームアップ(--create_only=1 による 10GiB の事前書き込み)の段階から完走しない

エンジンオプションを Mountpoint 用に --ioengine=psync --direct=0 --iodepth=1 へ変更してもこの結果でした。設定チューニングで救える類の問題ではないことがわかりました。

なぜ fio が動かないのか: Mountpoint for S3 の設計制約

Mountpoint for S3 は、POSIX 互換のファイルシステムではありません。実態は 「S3 のオブジェクト特性(イミュータブル、atomic PUT)にクライアント側を寄せた軽量な翻訳層」です。FUSE を介して S3 API をファイル操作に見せかけているだけの薄いラッパーであるとも表現できます。

この設計上、fio が前提にしている多くの操作が原理的にサポートされていません。

fio が要求する操作 Mountpoint の挙動 背景にある制約
--rw=randwrite / --rw=randrw(任意オフセットへの書き込み) ❌ 非対応 S3 PutObject はオブジェクト全体を atomic に置き換える API。任意オフセットへの部分書き込みが原理的に存在しない
既に close 済みファイルを 再 open して書き込み ❌ 非対応 --allow-overwrite を付けても、一度書き終えて close したファイルを再び open し途中から書き直すことは不可。truncate 後に先頭からシーケンシャルに書く場合のみ上書きが許容される
--direct=1(O_DIRECT) ❌ 非対応 S3 側にブロックデバイス的なダイレクト I/O の概念が存在しない
同一ファイルを 複数ジョブで並列書き込み ❌ 非対応 最後に close したクライアントの内容でオブジェクト全体が atomic 置換されるため、並列書き込みは意味を持たない
fallocate / ftruncate による事前サイズ確保 ❌ 非対応 S3 にプリアロケーションに相当する API が存在しない
O_RDWR で開く ❌ 非対応 書き込み用または読み込み用のいずれかでしか open できない

さらに厄介なのは、fio のベンチループが 「同じ fio.dat に対して seq-write → seq-read → rand-read → rand-write …」とパターンを切り替えていく 構造になっている点です。Mountpoint では最初の write が通った後、次パターンでは close 済みになったファイルを再 open して書こうとして即座に失敗します。それ以降のパターンも芋づる式に崩れていきます。ウォームアップで一度書き終えた fio.dat を別パターンで再利用する、という fio のごく標準的な使い方自体が通らないということです。

Mountpoint 向けに fio の設定をチューニングして検証できないか?

fio の設定を Mountpoint 用に設定をカスタマイズして動かすことは一応可能です。

  • write ジョブと read ジョブを 明確に分離(同じファイルを跨がない)
  • write 系は 毎回ファイル名を変える--filename_format=bench.$jobnum.$filenum.dat など)
  • numjobs=1 を固定、ランダム書き込みは行わない
  • --direct=0、事前 allocate しない

ここまでやってしまうと、Mountpoint が適用可能なワークロードのみを対象とした性能評価 となってしまい、今回の評価からは省いています。

Mountpoint for S3 の性能を適切に評価したい場合は、s5cmd bench / warp / s3-benchmark のような、S3 オブジェクトアクセスの実ワークロードに即したベンチマークツールを使うのが妥当です。こちらも別途評価をしても面白いでしょう。

まとめ

評価結果

numjobs = 1 / 4 / 16 / 64 で振ったうえでの、各ストレージのピーク値は以下の通りです。

指標 S3 Files EFS EBS (io2) EBS (gp3)
シーケンシャル read (MB/s) 33,546 32,155 1,232 129
シーケンシャル write (MB/s) 5,506 5,703 1,232 129
ランダム read IOPS (4KiB) 20,583 17,086 10,333 3,100
ランダム write IOPS (4KiB) 27,138 27,475 10,288 3,100

S3 Files は EFS と同水準の性能特性を持ち、numjobs を上げることで素直にスケールすることを確認しました。ただし先述 fio オプション差について の通り、ネットワーク FS(S3 Files / EFS)とブロックデバイス(EBS / エフェメラル)では fio のオプションを変えています。そのため、純粋なファイルシステム・ストレージの性能比較ではない点にご注意ください。

所感

  • これまで S3 を利用する上では、オブジェクトかファイルシステムかのトレードオフを選択しなければならなかった。S3 Files の登場によって、ファイルシステムとしての S3 がアーキテクチャ検討における有効な選択肢の 1つになったといえる
  • POSIX 互換のおかげで、既存アプリの移行コストが低くなったと考えられる。これは Mountpoint for S3 では実現できなかった大きな差別化ポイントだ
  • ただしローカルのファイルシステムと同等の挙動ではない点に注意したい。ファイルの作成や更新は高性能ストレージを経由して 60秒に1度 PUT される。そのため、ローカルのファイルシステムでは起き得なかった不整合が発生するケースもある
  • EFS と比較した場合、S3 Files を選ぶ動機は「データの実体が S3 にあること」のケースが多いと思われる。S3 Files の高性能ストレージ単価は S3 Standard の約 14 倍であり、データ量と保存期間によっては EFS のほうが割安になる可能性もある
  • 何回か繰り返したということもあり、EFS と S3 で結構お金がかかった

Discussion