背景
S3 Files は 2026 年 4 月に正式 GA したサービスです。S3 バケットをバックエンドとして NFS ファイルシステムインターフェースを提供しており、ECS コンテナは標準的なファイル操作(open・read・write)でアクセスできます。S3 API を呼び出す必要はなく、マウント方式も EFS とまったく同じ(Task Definition で EFSVolumeConfiguration を使う)です。
EFS との最大の違いはバックエンドストレージにあります。EFS のデータは AWS 独自の NFS クラスターに格納されますが、S3 Files のデータは S3 バケットに格納され、S3 API 経由でも同じデータに同時アクセスできます。「1つのストレージ、2つのアクセスプロトコル」というのが S3 Files の特徴になります。
アーキテクチャの変遷
S3 サービスに新しいアクセス方式が加わったことで、既存のアーキテクチャ設計にどのような変化が生まれるでしょうか。また実際にストレージサービスを選定する時、何を注意すべきでしょうか。そこで代表的なECSサービスとストレージサービスの常用アーキテクチャを対象としてパフォーマンステストを実施してみました。
代表的なアーキテクチャパターン
ECSサービスとストレージサービスの主な利用パターンは以下の4つになります。ここに4番目のパターンが今回の重要評価対象パターンです。
- Case 1: Standard S3 →
/tmpにダウンロード → ローカル読み書き - Case 2: S3 Express One Zone →
/tmpにダウンロード → ローカル読み書き - Case 3: EFS → NFS マウント → 直接
open/read/write - Case 4: S3 Files → NFS マウント → 直接
open/read/write
4つのアーキテクチャパターンの比較
上記の4つパターンについて、本質的な違いは実際のファイルアクセス時のデータ格納先にあります。
- Case 1/2 は、S3 からファイルをECSのコンテナ
/tmp(tmpfs メモリ)にダウンロードしてから読み書きします。ローカル操作は非常に高速ですが、毎回ダウンロード時間コストがかかります。(100MB 約 390ms、10GB 約 72 秒) - Case 3 は、ECSのタスクからNFS プロトコルでファイルシステムを直接マウントし、ダウンロードステップを省略します。I/O のたびにネットワーク経由でストレージバックエンドに直接アクセスします。
- Case 4(S3 Files)と Case 3(EFS)のマウント方法は同じ(Task Definition で
EFSVolumeConfigurationを使用)ですが、バックエンドが異なります。EFS は AWS の NFS クラスターにデータを格納し、S3 Files は S3 バケットにデータを格納して NFS + S3 API のデュアルプロトコルでアクセスできます。
Case 1: ECS Task → GetObject → /tmp(メモリ) → アプリ R/W
↑ 390ms(100MB)/ 72s(10GB)
Case 2: ECS Task → GetObject → /tmp(メモリ) → アプリ R/W
↑ Express、やや速い(333ms / 71s)
Case 3: ECS Task ←→ NFS(TCP 2049) ←→ EFS ストレージクラスター
直接 open/read/write、ダウンロード不要
Case 4: ECS Task ←→ NFS(TCP 2049) ←→ S3 Files キャッシュ層 ←→ S3 Bucket
直接 open/read/write
同一データを S3 API(Athena/Spark)からも参照可能
検証結果
ファイルサイズ 100MB のテスト結果
Case1 Standard-S3 → /tmp 読取 6823 MB/s 書込 351 MB/s P95読 17ms P95書 362ms DL 390ms
Case2 S3-Express → /tmp 読取 6942 MB/s 書込 299 MB/s P95読 15ms P95書 420ms DL 333ms
Case3 EFS 直接 読取 738 MB/s 書込 232 MB/s P95読 893ms P95書 475ms
Case4 S3 Files 直接 読取 90 MB/s 書込 106 MB/s P95読1134ms P95書 955ms
ファイルサイズ 1GB のテスト結果
Case1 Standard-S3 → /tmp 読取 7108 MB/s 書込 128 MB/s P95読 145ms P95書 8048ms DL 3747ms
Case2 S3-Express → /tmp 読取 7072 MB/s 書込 128 MB/s P95読 146ms P95書 8029ms DL 3377ms
Case3 EFS 直接 読取 1198 MB/s 書込 289 MB/s P95読 1874ms P95書 3648ms
Case4 S3 Files 直接 読取 87 MB/s 書込 110 MB/s P95読12869ms P95書 9378ms
ファイルサイズ 10GB のテスト結果
Case1 Standard-S3 → /tmp 読取 115 MB/s 書込 128 MB/s P95読89319ms P95書79956ms DL 71962ms
Case2 S3-Express → /tmp 読取 115 MB/s 書込 128 MB/s P95読89397ms P95書79956ms DL 71238ms
Case3 EFS 直接 読取 130 MB/s 書込 277 MB/s P95読79528ms P95書37646ms
Case4 S3 Files 直接 読取 83 MB/s 書込 64 MB/s P95読129337ms P95書161533ms
数値を読む前に押さえておくべき落とし穴
テスト結果を見ると、Case 1/2 のファイル読み書き速度が非常に速く見えます。しかし実際には、Case 1/2 の読み取り速度——100MB 時 6823/6942 MB/s、1GB 時 7108/7072 MB/s——はネットワーク速度ではなく、Fargate コンテナ内の tmpfs(インメモリファイルシステム)のローカル読み取り速度です。ダウンロードが完了した後は、S3 とは無関係にメモリを読んでいるだけです。
これらのアーキテクチャの実際のパフォーマンスを正しく評価するには、実効速度で見る必要があります。ダウンロード時間を折算し、リクエスト開始から 1MB のデータが読み取り可能になるまでの時間で比較します。
実効読み取り速度(DL + ローカル読み取り 折算):
100MB: Case1 247 MB/s Case2 288 MB/s Case3 738 MB/s Case4 90 MB/s
1GB: Case1 263 MB/s Case2 291 MB/s Case3 1198 MB/s Case4 87 MB/s
10GB: Case1 64 MB/s Case2 64 MB/s Case3 130 MB/s Case4 83 MB/s
4つ発見
パフォーマンステストの結果からみると、新サービスの S3 Files と既存の EFS について、パフォーマンス面で以下の課題が発見しました。
発見1:S3 Files はすべてのファイルサイズで読み取りが最遅
3つのファイルサイズすべてで、Case 4 の読み取りスループットは 83〜90 MB/s で推移し、ファイルサイズが変わっても大きな変化は見られませんでした。
この結果は、S3 Files の読み取りパフォーマンスがバックエンドアーキテクチャに制約されていることを示しています。S3 Files のデータパスは「ECS コンテナ → NFS → S3 Files キャッシュ層 → S3 オブジェクトストレージ」となり、EFS(NFS → EFS ストレージクラスター)と比較して S3 へのバックエンドアクセスのレイテンシが1層多く加わります。キャッシュヒット時は速くなりますが、今回のテストでは毎回異なるファイル内容で上書きしたため、キャッシュを有効活用できませんでした。
読み取り速度は最速ではないものの、S3 Files の読み取りにおける真の優位性は生スループットではなく「同一データを NFS でも S3 API でも読める」という特性にあります。アーキテクチャ上 ECS でファイルを処理しつつ、Athena/Spark でも同じデータを分析する必要がある場合、S3 Files によってデータの複製・移行にかかるコストと時間を省けます。
発見2:S3 Files の書き込みは 10GB 時に著しく劣化
オブジェクトファイルが大きくなるにつれ、S3 Files の書き込み速度は低下します。
書き込みスループット:
100MB: EFS 232 MB/s vs S3 Files 106 MB/s
1GB: EFS 289 MB/s vs S3 Files 110 MB/s
10GB: EFS 277 MB/s vs S3 Files 64 MB/s ← P95書き込み 161 秒
10GB 時の S3 Files 書き込み速度は EFS の4分の1以下で、P95 書き込みレイテンシは 161 秒に達しました。S3 Files へのファイル書き込みはキャッシュ層を突き抜けて S3 に永続化する必要があるため、オブジェクトが大きいほどその貫通コストが顕著になります。
発見3:EFS の書き込み速度はファイルサイズに影響されない
テストを通じて、ファイルサイズが変わっても EFS の書き込み速度は常に安定していることが分かりました。
EFS 書き込み vs Case1/2 ローカル書き込み:
100MB: EFS 232 MB/s < Case1 351 MB/s (EFS 負け)
1GB: EFS 289 MB/s > Case1 128 MB/s (EFS が 2.3 倍)
10GB: EFS 277 MB/s > Case1 128 MB/s (EFS が 2.2 倍)
Case 1/2 の 1GB 以上のローカル書き込みが 128 MB/s に落ち込む原因は、Fargate の ephemeral storage(ローカル一時ディスク)の書き込み速度に上限があり、大ファイルではローカルの一時キャッシュがボトルネックになるからです。EFS はネットワーク経由でストレージに直接書き込むため、ローカルディスクの制限を回避できます。
大量の書き込みが必要なタスク(1GB 以上)では、EFS は「/tmp にダウンロードして処理する」方式の2倍以上高速です。
発見4:EFS の読み取りは 10GB 時に崩壊する
また、テストデータから EFS が大ファイルを読み取る際に読み取り速度が著しく低下することも判明しました。
EFS 読み取り:
1GB: 1198 MB/s
10GB: 130 MB/s ← 89% 低下
これは EFS Elastic Throughput モードの burst credit 機構によるものです。連続した大量読み取りで credit が枯渇すると、ファイルシステムのストレージ量に連動したベーススループットまで落ち込みます。今回のテスト環境ではファイルシステムが小さく credit も少量だったため、10GB の連続読み取りですぐに使い果たしてしまいました。
実際の本番環境で EFS に TB 単位のデータが存在する場合は状況が改善されます。ただし、読み取りに厳格な SLA 要件がある場合は Provisioned Throughput モードを採用することで上記の問題を回避できます。
1回の処理における端から端の所要時間
ダウンロード・読み取り・書き込みをすべて含めた「ファイルを1回処理する」際の総所要時間(平均値)で4パターンを比較します。
100MB 1GB 10GB
Case1 1s 12s 241s(4分)
Case2 1s 12s 240s(4分)
Case3 EFS 1s 4s 115s(2分)
Case4 S3Files 2s 21s 285s(約5分)
この分析結果から、ファイルの読み書き処理において総合性能は EFS が最速(10GB で 115 秒)、Case 1/2 が次点(241 秒)、S3 Files が最遅(285 秒)という結論が得られます。
選定の判断基準
では、S3 Files という新サービスをいつ使えばよいのでしょうか。
各種プロジェクトでの実務経験をもとに、簡単な選定基準をまとめます。実際の選定の参考にしてください。
Standard S3 → /tmp(Case 1/2)を選ぶとき:
- 一度処理したらそのファイルは不要
- 複数コンテナでファイルを共有する必要がない
- コスト最優先で、シンプルなアーキテクチャを維持したい
100MB 以内のシナリオでは総合的なバランスが最も良く、コストも最低です。
EFS(Case 3)を選ぶとき:
- 複数コンテナが同一ディレクトリを共有する必要がある
- POSIX ファイルシステムのセマンティクス(追記書き込み、ファイルロック)が必要
- 大ファイルの書き込みが頻繁(1GB 以上)で、EFS の書き込みはローカル書き込みの2倍高速
- Provisioned Throughput の予算があり、安定した読み取りパフォーマンスが必要
注意:Elastic モードでは大ファイルの連続読み取り時に burst credit 制限が発動します。高 SLA 要件のシナリオでは Provisioned Throughput を使用してください。
S3 Files(Case 4)を選ぶとき:
- 同一データが ECS コンテナによる処理と S3 エコシステム(Athena・Spark・S3 API)によるアクセスの両方を必要とする
- EFS より低い読み書きスループットを許容し、「1つのデータ、2つのアクセスプロトコル」によるアーキテクチャのシンプル化を優先できる
- 書き込み量が少ない(100MB 以下)、または書き込み頻度が低い
S3 Files が適さないシナリオ:
- 大ファイル(1GB 以上)の高頻度書き込み(P95 書き込みレイテンシが 10 秒以上になる)
- 読み取りレイテンシに厳格な SLA 要件がある(読み取りスループットが EFS の 3〜10 分の1)
- ファイルロックや追記書き込みが必要(S3 Files は現時点で一部の POSIX セマンティクスのサポートに制限あり)
CDK でテスト環境を構築する
今回のベンチマーク環境は AWS CDK(Python)で構築しました。EFS や S3 のような既存サービスは CDK で問題なく定義できますが、S3 Files については 2026 年 4 月時点で CDK の L2/L1 Construct が未提供のため、デプロイを2フェーズに分ける構成にしています。
2フェーズデプロイの全体像
Phase 1: cdk deploy
VPC / S3 / EFS / ECS タスク定義 / IAM ロールを一括プロビジョニング
→ 以下の値を Outputs に出力する
・S3FilesServiceRoleArn(S3 Files がバケットへの読み書きに使う IAM ロール)
・S3FilesSgId(マウントターゲット用 SG)
・SubnetId(マウントターゲットを置くサブネット)
・StandardBucketName(S3 Files のバッキングバケット)
Phase 2: scripts/setup_s3files.sh
Phase 1 の Outputs を受け取り、CLI で S3 Files リソースを作成する
① aws s3files create-file-system --bucket-name ... --role-arn ...
② aws s3files create-mount-target --file-system-id fs-xxxx ...
③ cdk deploy --context s3files_fs_id=fs-xxxx
→ FS ID をコンテキストに渡して ECS タスク定義を更新
CDK で L2 Construct がないリソースを扱う際の典型的な回避策として、「CDK でインフラを作り、CLI で残りを補い、コンテキスト経由で ID を返す」パターンは覚えておくと応用が効きます。
AZ ID をカスタムリソースで解決する
もう一つ工夫が必要だったのが、S3 Express の Directory Bucket 命名規則です。バケット名は <basename>--<az-id>--x-s3 の形式でなければならず、az-id は AZ 名(例:ap-northeast-1a)ではなく AZ ID(例:apne1-az1)を使います。
CDK のデプロイ時点では AZ ID を静的に取得できないため、Lambda カスタムリソースで ec2:DescribeAvailabilityZones を呼び出して解決しています。
# AZ名 → AZ ID を解決するカスタムリソース
az_lookup_fn = lambda_.Function(
self, "AzIdLookupFn",
runtime=lambda_.Runtime.PYTHON_3_12,
handler="index.handler",
code=lambda_.Code.from_inline("""
import boto3
def handler(event, context):
if event['RequestType'] in ('Delete', 'Update'):
return {'PhysicalResourceId': event.get('PhysicalResourceId', 'az-id-lookup'),
'Data': {'AzId': ''}}
az_name = event['ResourceProperties']['AzName']
region = event['ResourceProperties']['Region']
resp = boto3.client('ec2', region_name=region).describe_availability_zones(
Filters=[{'Name': 'zone-name', 'Values': [az_name]}]
)
az_id = resp['AvailabilityZones'][0]['ZoneId']
return {'PhysicalResourceId': f'az-id-{az_name}',
'Data': {'AzId': az_id}}
"""),
)
az_cr = CustomResource(
self, "AzIdCr",
service_token=cr.Provider(self, "AzProvider",
on_event_handler=az_lookup_fn).service_token,
properties={"AzName": vpc.availability_zones[0], "Region": self.region},
)
az_id = az_cr.get_att_string("AzId") # e.g. "apne1-az1"
# Directory Bucket 名に動的解決した AZ ID を使う
directory_bucket_name = f"bench-s3express--{az_id}--x-s3"
s3express.CfnDirectoryBucket(
self, "ExpressDirectoryBucket",
bucket_name=directory_bucket_name,
data_redundancy="SingleAvailabilityZone",
location_name=az_id,
)
S3 Files 用 IAM ロールをあらかじめ CDK で作る
S3 Files のファイルシステムは CLI で作成しますが、「S3 Files が S3 バケットを読み書きするための IAM ロール」は CDK で先に定義できます。CLI でファイルシステムを作成するときに --role-arn として渡すだけです。
# S3 Files サービスロール(CDK で先行作成)
s3_files_service_role = iam.Role(
self, "S3FilesServiceRole",
assumed_by=iam.ServicePrincipal("elasticfilesystem.amazonaws.com"),
)
standard_bucket.grant_read_write(s3_files_service_role)
# CLI で使う値を Outputs に出しておく
CfnOutput(self, "S3FilesServiceRoleArn",
value=s3_files_service_role.role_arn,
description="Pass as --role-arn to: aws s3files create-file-system")
CfnOutput(self, "S3FilesSgId",
value=s3files_sg.security_group_id,
description="Pass as --security-group to: aws s3files create-mount-target")
S3 バケットのバージョニングは必須
S3 Files のバッキングバケットにはバージョニングの有効化が必須です(S3 Files はバージョニングを内部で利用しています)。CDK では versioned=True を忘れないようにしてください。これを設定し忘れるとファイルシステム作成時にエラーになります。
standard_bucket = s3.Bucket(
self, "StandardBucket",
versioned=True, # S3 Files の必須要件
removal_policy=RemovalPolicy.DESTROY,
auto_delete_objects=True,
block_public_access=s3.BlockPublicAccess.BLOCK_ALL,
encryption=s3.BucketEncryption.S3_MANAGED,
)
デプロイ手順まとめ
# Phase 1: 基本インフラをデプロイ
cdk deploy
# Phase 2: Outputs の値を使って S3 Files を作成
BUCKET=$(aws cloudformation describe-stacks \
--stack-name S3FilesBenchmarkStack \
--query "Stacks[0].Outputs[?OutputKey=='StandardBucketName'].OutputValue" \
--output text)
ROLE_ARN=$(aws cloudformation describe-stacks \
--stack-name S3FilesBenchmarkStack \
--query "Stacks[0].Outputs[?OutputKey=='S3FilesServiceRoleArn'].OutputValue" \
--output text)
SUBNET=$(aws cloudformation describe-stacks \
--stack-name S3FilesBenchmarkStack \
--query "Stacks[0].Outputs[?OutputKey=='SubnetId'].OutputValue" \
--output text)
SG=$(aws cloudformation describe-stacks \
--stack-name S3FilesBenchmarkStack \
--query "Stacks[0].Outputs[?OutputKey=='S3FilesSgId'].OutputValue" \
--output text)
# S3 Files ファイルシステムを作成
FS_ID=$(aws s3files create-file-system \
--bucket-name "$BUCKET" \
--role-arn "$ROLE_ARN" \
--query "FileSystemId" --output text)
# マウントターゲットを作成
aws s3files create-mount-target \
--file-system-id "$FS_ID" \
--subnet-id "$SUBNET" \
--security-group-ids "$SG"
# FS ID を CDK に渡して ECS タスク定義を更新
cdk deploy --context s3files_fs_id="$FS_ID"
なぜこんなに手間がかかるのか
S3 Files が GA したのは 2026 年 4 月で、CDK の Construct ライブラリへの反映は数ヶ月遅れるのが通常です。EFS と同じく EFSVolumeConfiguration を使ってマウントできるとはいえ、「ファイルシステム自体の作成」という部分が CDK に未実装なため、今回のような2フェーズ構成が必要になります。
L2 Construct が追加されれば、将来的には EFS と同じ感覚で数行書くだけで済むはずです。それまでは CLI + コンテキスト渡しというワークアラウンドが現実的な選択肢です。
S3 Files の真の価値はパフォーマンスにあらず
3組のデータを測定し終えて、S3 Files の位置づけがより明確になりました。
競合相手は EFS ではなく、「EFS + S3 同期」という組み合わせです。多くのチームが現在実施している「EFS でコンテナにファイルシステムを提供しつつ、データ分析用に S3 にデータを同期する」という運用パターンです。この同期自体が運用負荷になります。同期スクリプトの作成・整合性の処理・2つのコピーのストレージコスト管理が必要です。
S3 Files はこの2つの問題を1つに統合します。データは S3 に1つだけ存在し、ECS の NFS マウント経由でも、S3 API 経由でも、どちらでもアクセスできます。この2つのアクセスモードが必要なワークフローであれば、S3 Files によってアーキテクチャの複雑さをかなり軽減できます。
ただし、ECS コンテナ用の共有ファイルシステムが単純に必要なだけであれば、特に大ファイルのシナリオでは EFS の方がパフォーマンス面で依然として優れています。
今回測定できなかったシナリオ
今回のテストの制約として、単一ファイルの直列読み書きのみを計測し、並列処理はテストしていません。S3 Files は S3 ベースのキャッシュ層により、多並列での小ファイルアクセス時により良いパフォーマンスを発揮する可能性があります(S3 Express 自体、高並列・小オブジェクトアクセスに強みがあります)。
また、S3 Files のキャッシュウォームアップ効果も測定できていません。同一ファイルを繰り返し読み取る場合(訓練データの反復イテレーションなど)、キャッシュヒット後の速度はより高くなるはずです。これらのシナリオについては、別途テストする機会を設けたいと思います。
実測:2026年4月、S3 Files GA 後。ECS Fargate ap-northeast-1、2vCPU/4GB(10GB テストは 8vCPU/16GB)、各5イテレーション。