1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

s3fsで痛い目を見たので S3 Filesを調べ倒した

1
Posted at

過去の自分に読ませたかった

안녕하신게라!パナソニック コネクト株式会社クラウドソリューション部の加賀です。

実は昔、「とりあえずs3fsでマウントしてあれば良いよ」と言われそのまま本番に持ち込んだ結果、バッチ処理が二重起動してデータが壊れたことがあります。原因は flock が動作しないこと。「そりゃS3はオブジェクトストレージなんだからロックなんてないよ」と言われればそれまでですが、当時はその解像度がなく、ファイル利用用途まで踏み込まずにハマりました。

そんな折、「S3をNFSマウントできる上にPOSIX互換」という触れ込みのAmazon S3 Files(東京リージョン2026年4月GA)を色々調べたら、思った以上にスゴかったのでまとめます。
内部的にはEFS技術を基盤としたhigh-performance storage(以下HPS)を挟み、NFSv4.1/4.2でPOSIX互換のファイルアクセスセマンティクスを提供するもので、s3fsやgoofys、Mountpoint for Amazon S3とは設計思想が根本的に違います。

本記事は、前半で「なぜPOSIXが大事なのか」を整理した上で、FUSE系ツールとの設計上の違いを解説します。後半はS3 Filesの挙動と制約、ユースケース別の選定指針です。

FUSE系 vs S3 Files 比較

観点 FUSE系ツール(s3fs / goofys / Mountpoint for Amazon S3) Amazon S3 Files
アーキテクチャ FUSEでファイル操作をS3 APIに直接変換 HPS経由でS3にアクセス
プロトコル FUSE(ユーザランド) NFSv4.1/4.2(カーネル空間)
POSIX互換性 部分的。ロック不可。renameはs3fs/goofysで非アトミック(COPY+DELETE)、Mountpoint for S3は非対応 POSIX互換(ハードリンク・mandatory lockは非対応)
整合性 S3のstrong consistency(2020年12月〜)に依存 NFS内は強い整合性(read-after-write consistency)。NFS→S3エクスポートは書き込み非活動後、最大60秒で反映
読み取り特性 S3 API経由のみ 小ファイルは低レイテンシ、1 MiB以上のread操作はS3直接ストリーミング
料金モデル S3料金のみ S3料金 + HPS料金 + ファイルシステム操作料金
Lambda連携 マウント不可 マウント可(ただしVPC Lambda必須)

POSIXの何が嬉しいのか?

POSIX(Portable Operating System Interface)が策定された背景には、1980年代のUnix乱立問題があります。AT&T系、BSD系、各ベンダ独自のUnix派生が増え、同じ「ファイルを開く」操作でもOSごとに微妙に挙動が違う。ある環境で動いたプログラムが別の環境では壊れる。なかなか難儀な移植性の問題を解決するためにIEEEが標準化したのがPOSIXです。

POSIX準拠のファイルシステムでは、ファイルロック(flock/fcntl)による排他制御、アトミックなrenameによるクラッシュセーフな書き込み、パーミッション(chmod/chown)によるアクセス制御が保証されます。これらが「共通で当たり前に動く」ことの最大のメリットは、アプリケーション開発者がストレージの実体を意識しなくて済む点です。裏側がローカルディスクだろうがNFSだろうがEFSだろうが、open()write()close() のコードは変わりません。

S3 Filesはこの恩恵を活かすサービスです。ストレージの実体はS3なのに、アプリから見れば普通のファイルシステムとして振る舞う。既存のコードを1行も変えずにS3の上で動かせる。それがPOSIXの強みです。
ただし「互換」であって「準拠」ではない点には注意が必要です。S3 Filesはハードリンクやmandatory lockなど、一部のPOSIX機能をサポートしていません。大半のアプリケーションはそのまま動きますが、これらの機能を利用しているソフトウェアでは検証が必要です。

逆に、s3fsのようにPOSIX非準拠の環境では、開発者がオブジェクトストレージの制約を常に意識しなければなりません。「この操作はS3で安全か?」「ロックは効くか?」「renameは壊れないか?」を自分で判断する必要があります。会社の共有フォルダでExcelを開いたとき「○○さんが編集中です。読み取り専用で開きますか?」と表示されたことがあると思いますが、あれがまさにファイルロックです。s3fsにはこの仕組みがないため、2人が同時に保存すれば後勝ちでデータが静かに消えます。

排他制御(ファイルロック)

flock -n /mnt/efs/app.lock -c "./start_daemon.sh"

ほとんどのデーモンやバッチ処理は flock()fcntl() でロックファイルを作り、二重起動を防止しています。s3fsではこのロックが動作しないため、複数インスタンスでデーモンが同時起動し、データ競合やファイル破損が起こります。

アトミック操作なrename

多くのアプリケーションは「安全な書き込み」を write-then-rename パターンで実装しています。

with open("/mnt/efs/data.tmp", "w") as f:
    f.write(new_content)
    f.flush()
    os.fsync(f.fileno())
os.rename("/mnt/efs/data.tmp", "/mnt/efs/data.json")  # アトミック!

POSIXでは rename() はアトミックです。途中で電源が落ちても「古いファイル」か「新しいファイル」のどちらかが必ず残ります。中途半端な状態にはなりません。s3fsでは rename = COPY + DELETE なので、COPY後DELETE前にクラッシュすれば2つのファイルが残り、COPY中に他プロセスが読み取れば不整合なデータを参照する可能性があります。操作が非アトミックであること自体がリスクです。

実際に動かないソフトウェアが出てくる

ソフトウェア 必要なPOSIX機能
SQLite ファイルロック(全モード。特にWALモードではshm共有メモリも必要)
WordPress(NFS共有時) flock、rename
Git(共有リポジトリ) アトミックrename(--localクローン時はハードリンクも利用)
rsync symlink、chmod(--link-dest使用時はハードリンクも利用)

「検証環境では動いたのに本番で壊れた」の典型パターンです。アクセスの少ない検証環境では偶然うまく動いていただけであり、同時アクセスが増えると容易に破綻します。

アーキテクチャの違い

S3はファイルシステムじゃない

S3(Simple Storage Service)はオブジェクトストレージです。図書館の書庫をイメージしてください。本(ファイル)を収める(PUT)、取り出す(GET)、蔵書一覧を見る(LIST)ことはできますが、書庫の中で本の内容を途中だけ書き直すことはできません。

s3fsやgoofys、そしてMountpoint for Amazon S3は、この書庫の前に立ち読みスペースを置くようなツールです。S3のAPI(PutObject、GetObject)をFUSE経由でPOSIXの open()/read()/write() に変換しています。ls を打てばListObjects APIが走り(遅い)、mv を打てばCopyObject + DeleteObjectが走り(壊れうる)、flock を打てど何も起こりません(ロックしない)。

問題の本質は、S3にはファイルシステムの根幹となる概念が存在しないことです。アトミックrenameがない(COPY + DELETEの2操作)。ファイルロックがない。ハードリンクもない。ディレクトリのアトミック操作もない(「プレフィックス」に過ぎない)。

AWS公式のMountpoint for S3ですら、ランダムライト不可、ファイルロック非対応で、POSIX準拠を目指していない旨を公式ドキュメントに明記しています。AWS公式が本気で作ってもPOSIX準拠にできないのが「S3マウント」の限界です。ツールの問題ではなく、S3というオブジェクトストレージの構造的限界です。

S3 Files「書庫の横に閲覧席を作ってみた」

S3 Filesの実体を一言で言えば「S3をSource of Truthとし、EFS技術を基盤としたHPS(アクティブデータを保持する高速ストレージ層)を挟んだ構成」です(公式ドキュメントでは「Built using Amazon EFS」と表現されています)。

s3fsが書庫の棚の上で無理やり作業するようなものだとすれば、S3 Filesは書庫の隣にちゃんとした閲覧席を設けて、必要な本だけ取り寄せて手元で作業できるようにした構成です。よく使う本は閲覧席に置きっぱなしにでき(HPS)、しばらく触らなかった本は自動で書庫に戻されます(デフォルト30日)。分厚い辞典は閲覧席に出さなくても書庫で直接読めます(1 MiB以上のreadはS3から直接ストリーミング)。

NFS(Network File System)は1984年にSun Microsystemsが開発したファイル共有プロトコルで、LinuxやmacOSには標準で組み込まれています。マウント後はローカルのフォルダと全く同じように使えます。

# S3 FilesをNFSでマウント(mount helperを使用する場合。TLS暗号化が有効になる)
sudo mount -t efs -o tls \
  fs-0123456789abcdef0 \
  /mnt/efs

# mount helperを使わない場合(TLS暗号化なし。検証用途向け)
sudo mount -t nfs4 -o nfsvers=4.1 \
  fs-0123456789abcdef0.efs.ap-northeast-1.amazonaws.com:/ \
  /mnt/efs

NFSクライアントから見ると、flock() / fcntl() によるファイルロックが正常動作し、rename() はファイルシステム上では即時完了(POSIX準拠)で、UID/GID/パーミッション(chmod, chown)が動作し、シンボリックリンクにも対応しています。

ただしハードリンクは非対応です(公式のUnsupported featuresに明記)。各ファイルが1つのS3オブジェクトキーに対応する設計上、複数パスから同一inodeを参照するハードリンクは実現できません。また、ロックはすべてadvisory lockであり、mandatory lockingには対応していません。つまりロックの取得・確認はアプリケーション側の責任であり、ロックを無視してファイルにアクセスすること自体はカーネルによって阻止されません。

結局どこが違うのか

ここまでの話を整理すると、FUSE系とS3 Filesの差はアプローチそのものです。

FUSE系は、S3のAPIをファイル操作に翻訳する「通訳」です。通訳である以上、S3側に存在しない概念(ロック、アトミックrename)は訳しようがありません。どれだけ優秀な通訳でも、原文にない言葉は出てこない。

S3 Filesは、S3とは別にPOSIX互換のファイルシステム(HPS)を持っていて、そこにデータを同期する構成です。ファイルシステム操作(ロック、rename、パーミッション等)はこのHPS上で完結するので、普通に動きます。S3は「永続保存先」として後ろに控えているだけです。

FUSE系はアプリとS3の間に通訳を挟んでいるだけなので、S3の制約がそのまま透けて見えます。S3 Filesはアプリとの間に本物のファイルシステムがあるので、POSIX互換の操作がそのまま動く。この構造の違いが、冒頭で挙げたロック・rename・パーミッションの対応差になります。

知っておくべき挙動と制約

次に、実際に触ってみて気になった挙動や制約についてです。
ここまで持ち上げておいて言うのもなんですが、S3 Filesは万能ではありません。使い所を考える必要があります。

料金体系とコスト特性

S3 Filesの料金体系はEFS Standardとは異なる独自のものです(最新の単価はS3 Files Pricingを参照)。大きく3つの課金軸があります。

課金軸 内容
S3ストレージ Source of Truthとしてのオブジェクト保管(通常のS3料金)
HPS ファイルシステム上に保持されたアクティブデータ量に応じた課金
ファイルシステム操作 読み書き操作(最小32 KiB単位)+ メタデータ操作(最小4 KiB単位)

いいね!と思ったポイントは「全データがHPSに載るわけではない」ことです。1 MiB以上の読み取りはS3から直接ストリーミングされるためファイルシステム操作料金が発生しません。また、アクセスされなくなったデータは設定期間(デフォルト30日)後に自動的にHPSから退避されます。大容量データをPOSIXで扱いたいがEFSに全量置くとコストが爆発してしまう、というケースにこそS3 Filesは有効です。実質的に「ホットデータだけ高速ストレージに載り、コールドデータはS3の安価なストレージに留まる」自動階層化が働くためです。

最小課金サイズに注意。HPSの最小保存サイズは10 KiB、各read/write操作の最小課金サイズは32 KiBなので、1 KiBのファイルを100万個書き込むと、ストレージ課金は実データ約1 GiBに対して10 GiB分、I/O課金は約32 GiB分に膨らみます。S3側でも「リクエスト数 × 単価」の課金があるため、小ファイルの大量処理はコスト効率が悪い。AWS公式のベストプラクティスでも「1 MiB以上の大きなIOサイズを使うことで、per-operationオーバーヘッドを償却できコスト効率が良い」と推奨されています。

S3バケットにS3 Versioningの有効化が必須なので、変更のたびに旧バージョンが保存されます。ライフサイクルルールで非最新バージョンを定期削除する運用を検討しましょう。

少額とはいえ見落としがちなのがCross-AZのデータ転送コストです。S3 FilesのマウントはDNS名で行い、DNSが同一AZのマウントターゲットIPへ自動解決してくれるため、利用する全AZでマウントターゲットを作っていれば問題は起きません。転送コストが発生するパターンは、あるAZでマウントターゲットを作り忘れた場合、そのAZのインスタンスは別AZのマウントターゲットへ接続するため、AZ間転送料金(各方向$0.01/GB)が静かに積み上がります。公式でも「コンピューティングリソースが動く各AZにマウントターゲットを作る」ことを推奨しています。

NFS↔S3の同期とlost+found

ファイルシステムとS3バケット間の同期には方向ごとに異なる特性があります。

NFS → S3(エクスポート)方向では、ファイルへの書き込みが非活動になると、S3 Filesは最大60秒以内に変更をS3バケットへエクスポートします。この間に発生した連続する変更は1回のS3 PUTリクエストにまとめられます(公式ドキュメント)。ログファイルに50回appendしても1回のS3 PUTで済むバッチ処理です。裏を返せば、NFS側で書き込んだデータがS3バケットに反映されるまでには最大60秒の遅延があるため、S3 API経由で即座に読み取りたいワークロードでは注意が必要です。

S3 → NFS(インポート)方向では、別のアプリケーションがS3 APIでオブジェクトを追加・変更・削除すると、S3 FilesがS3 Event Notifications経由で検知し、HPSに載っているデータを自動更新します。ただし、HPSから期限切れで退避済みのファイルは、次にアクセスされるまで更新されません。

NFS同士の操作は即時反映です。同期遅延が問題になるのは「S3 API ↔ NFS」のプロトコル境界を跨ぐときだけで、NFS内で完結する限り通常のファイルシステムと同じ強い整合性(read-after-write consistency)が得られます。

典型的な罠として、S3イベント通知でLambdaを起動し、そのLambda内でNFSマウント経由でファイルを読もうとするケースがあります。S3 APIとNFSを混在利用する設計では、同期タイミングを意識したアーキテクチャが必要です。AWS公式のベストプラクティスでも「S3 APIとファイルシステムのどちらかをprimary writerに指定する」ことが推奨されています。

また、NFS側とS3 API側で同一ファイルを同時に変更してコンフリクトが発生した場合、S3バケットがSource of Truthとして優先され、NFS側の変更はファイルシステムのルートにある.s3files-lost+found-<file-system-id>ディレクトリに退避されます。lost+foundに移動されたファイルは自動では削除されず、ストレージコストにも計上されるため、定期的な確認・削除が必要です。

ディレクトリrenameの落とし穴

ファイル単位のrename()と同様に、ディレクトリのrename/moveもファイルシステム上では即時完了します。ただしバックグラウンドでは、S3側で全オブジェクトのCOPY+DELETEが必要になるため、数百万ファイルを含むディレクトリの場合はS3同期に数時間かかる場合があります。同期完了までの間、S3バケット上には旧・新両方のプレフィックスが一時的に存在します。S3リクエストコストもオブジェクト数に比例して発生します(公式では約1,200万オブジェクトを警告閾値としており、--AcceptBucketWarningで回避可能)。ファイルシステムのスコープを最小限のプレフィックスに絞ることが、公式ベストプラクティスでも推奨されています。

読み取りパスの二層構造

S3 Filesの読み取りは、ファイルサイズと読み取りサイズによって経路が変わります。

まずインポート閾値(デフォルト128 KiB未満、変更可)より小さいファイルは、ディレクトリへの初回アクセス時にメタデータごとHPSへ自動インポートされます。以降はサブミリ秒〜1桁ミリ秒で読めます。

一方、1回のread操作が1 MiB以上になると、HPSにデータがあってもS3バケットから直接ストリーミングされます。S3のほうがスループットに優れるため、大きな読み取りではこちらが効率的です。

機械学習のモデルファイル、画像・動画のバッチ処理、科学計算の中間ファイルのように1ファイルが大きく、シーケンシャルに読むワークロードがS3 Filesの最も輝く場面です。設定ファイルやメタデータ参照のような小さいファイルの読み書きもHPSの低レイテンシで快適に捌けますが、数KiBのファイルを数百万個単位で扱うようなケースでは最小課金サイズのためコスト効率が悪化します。そういった大量の小ファイルI/OにはElastiCacheやDynamoDBのほうが向いています。

ネットワーク設計とセキュリティ

LambdaでS3 Filesを使う場合、LambdaをVPC内に配置する必要があります。これはマウントターゲットがVPC内のENI経由でしかアクセスできないためです。VPC Lambdaになることで、NAT Gatewayなしではインターネットアクセスできない、サブネットのIPアドレス枯渇に注意が必要、セキュリティグループの設計が必須、といった制約が加わります。つまり「LambdaでS3 Filesを使う=ネットワーク設計が必要」ということです。手軽さと引き換えに、POSIX互換のファイルシステムが得られます。

S3 Filesは転送中のデータをTLSで、保存時のデータをAWS KMSキーで暗号化します(デフォルトはAWS所有キー、カスタマーマネージドキーも指定可能)。なお、TLS暗号化を有効にするにはmount helper(efs-utils)経由でマウントする必要があります。NFSアクセスにはマウントターゲットのセキュリティグループでNFSポート(2049/TCP)を許可する必要があります。EC2・Lambda問わず、セキュリティグループの設計は必須です。

NFSv4のKerberosベースセキュリティには非対応です。エンタープライズ環境でNFS認証にKerberosを前提としている場合は設計の見直しが必要になります。

既存バケットに被せる前に

NFS経由でファイルを変更すると元のS3オブジェクトに設定されていたS3 ACLとカスタムユーザー定義メタデータが消えます(Unsupported S3 featuresに明記)。既存のS3データでACLやカスタムメタデータに依存している場合は、S3 Filesを被せる前に影響を確認しておかないと泣きます。

S3キー名にも注意が必要です。S3はオブジェクトキーをただの文字列として扱うので、foo/./barfoo/../bar、空パスコンポーネント(foo//bar)、nullバイトを含むキーも普通に作れます。しかしPOSIXでは . はカレントディレクトリ、.. は親ディレクトリとしてカーネルが予約しているため、これらのキーはファイルパスとして解釈できません。S3 FilesはPOSIX互換だからこそ、こうしたPOSIX非互換なキー名のオブジェクトにはアクセスできない仕様になっています。既存バケットにS3 Filesを被せる場合は、対象キーにこれらのパターンが含まれていないか事前に確認しておきましょう。

運用のポイント

S3 Filesは同期の健全性をCloudWatchメトリクスで公開しています。特に見ておきたいのは PendingExports(まだS3に書き戻せていない変更の数)と ExportFailures(書き戻しに失敗した数)の2つ。PendingExports が右肩上がりならワークロードが同期レートを超えているサインだし、ExportFailures が1でも立ったらパーミッションかKMSキーの問題を疑ったほうがいい。アラーム設定は公式ベストプラクティスでも推奨されているので、デプロイ時にセットで入れておきましょう。

地味に怖いのがEventBridgeルールの扱いです。S3 Filesは DO-NOT-DELETE-S3-Files というプレフィックスのEventBridgeルールを自動作成して、S3バケット側の変更を検知しています。名前からして「消すなよ」と全力で主張しているわけですが、TerraformやCloudFormationでEventBridgeルールを一括管理していると、うっかり巻き込んで消してしまうことがあり得ます。もし消えるとS3側の変更がファイルシステムに反映されなくなるので、IaCのスコープから除外しておくと良いでしょう。

だいたいは超えませんが、クォータも把握しておきましょう。最大ファイルサイズ48 TiB、ディレクトリ深度の上限1,000階層、S3オブジェクトキー長の上限1,024バイト、1ファイルあたりのロック数上限512(全インスタンス合計)あたりは設計に響くので、詳細は公式のQuotasを一読しておくと安心です。

ユースケース別の選定指針

ユースケース 推奨 理由
静的ファイルの配信 S3 + CloudFront NFSマウント不要。S3に置いてCDNで配るだけ
大量データのシーケンシャル読み取り(POSIX不要) Mountpoint for S3 AWS公式、高スループット。POSIX非準拠
複数EC2でのPOSIX共有ワークスペース(S3連携不要) EFS S3同期のオーバーヘッドなし。NFS完結で構成がシンプル
複数EC2でのPOSIX共有ワークスペース(S3連携あり) S3 Files POSIX互換、同時アクセス安全、S3の耐久性とコスト効率を両立
S3 APIとNFSの両方からアクセス S3 Files 同期特性を理解した上で使う
HPC・大規模MLでS3連携が必要 Amazon FSx for Lustre データリポジトリ連携でS3と自動同期。高スループット
LambdaでS3のファイルを参照(POSIX不要) S3 API(boto3等) VPC不要でシンプル
LambdaでS3のファイルを参照(POSIX必要) S3 Files + VPC Lambda flock等が必要なら選択肢はこれ。VPC設計が前提になる
小さいファイルを大量に捌きたい ElastiCache / DynamoDB ファイル操作のコスト割高
「とりあえず」S3をマウントしたい(検証用途) s3fs / goofys 本番非推奨、あくまで検証用途

おわりに

冒頭で書いた「s3fsでflockが動かなくてデータが壊れた」事故は、S3 Filesがあの時点で存在していれば防げていました。もちろんS3 Filesも万能ではなく、同期タイミングの癖や小ファイル大量処理のコスト非効率、ハードリンク非対応といった制約はあります。でも少なくとも「POSIXが必要なのにs3fsでごまかしごまかし妥協する」という選択をしなくて良い時代となりました。

「S3をマウントしたい」と言われたとき、まず確認すべきは「POSIXが必要か?」「ファイルサイズは?」「S3 APIとの併用はあるか?」の3点です。条件が合えばS3 Files、シーケンシャル読み取り専用ならMountpoint、検証だけならs3fs。この判断ができるだけで設計の質が変わることでしょう。

過去の自分に読ませたかった。誰かの未来に役立ちますように。


お断り
記事内容は個人の見解であり、所属組織の立場や戦略・意見を代表するものではありません。
あくまでエンジニアとしての経験や考えを発信していますので、ご了承ください。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?