Serverless Operations, inc

>_cd /blog/id_qqh7_nye3e

title

AWS LambdaがAmazon S3 Filesの直接読み取り設定をサポートしたのでやってみた

2026年9月11日、AWS Lambda が Amazon S3 Files へのバケットへの直接読み取り設定をサポートしました。

AWS Lambda が Amazon S3 Files の直接読み取り設定のサポートを開始

なにがかわるのか?

このアップデートのうれしさを理解するためには従来の Lambda 関数が S3 Files を呼び出す際の仕組みをまず知る必要があります。

Lambda関数は従来からS3 filesと連携はしていました。

AWS Lambda関数からAmazon S3 Files にマウントする方法

この場合、s3.get_object() のように API を叩いてオブジェクトを取得する経路も引き続き利用可能ですが、S3 Filesのインターフェースを使う場合、(/mnt/...)に対して open()/read() でファイルを読むと、その裏側で S3 Files が「キャッシュ層から返すか、バケットから直接ストリーミングして返すか」を自動で振り分けます。

S3 Files はキャッシュレイヤとしてEFSを内蔵しています。小さいファイルの連続した読み書きはこのキャッシュ層をストレージエンドポイントとして利用可能になることでS3バケットを直接呼び出すより高速なアクセスが可能でした。

一方大きいファイル(オブジェクト)の読み込み等高いスループットが求められる場合は、キャッシュレイヤを経由せず直接S3バケットから読み込んだ方処理効率が良くなります。

しかしながら今回のアップデート以前ではLambda関数をS3 Filesと連携させる場合、関数に割り当てるメモリが512MB以上でないと直接S3バケットの読み取りが許可されませんでした。メモリが 512 MB 未満の Lambda 関数では、S3 バケット自体にはアクセスできるものの、大きいファイルも含めてすべての読み取りがキャッシュ層(高パフォーマンスストレージ)経由に固定され、バケット直読みによる高スループットを得られませんでした。

逆に、メモリが 512 MB 以上の Lambda 関数では直接読み取りが常に有効で、これを無効化することができませんでした。つまり「すべての読み取りをキャッシュ層経由(低レイテンシー優先)に寄せる」といった制御ができなかった、ということです。なお、直接読み取りが有効な場合でも 1 MB 未満の小さいファイルは引き続きキャッシュ層から配信されます。つまり、大きいファイルはS3バケットから直接、小さいファイルはキャッシュ層からという使い分けはできていましたが、意図的にすべての読み込みをキャッシュ層のみに絞る、という制御が行えませんでした。

それが今回のアップデートで自由に設定を行うことが可能となりました。図にすると以下の通りです。

従来(デフォルト)

今回以降

512 MB 未満

直接読み取り不可(キャッシュ層のみ)

直接読み取りを明示的にオン/オフ可能

512 MB 以上

直接読み取り有効(制御不可)

直接読み取りを明示的にオン/オフ可能

さっそくやってみる

では試してみます。メモリが512MB未満の小さい関数でキャッシュ層を介さずバケットへの直接読み取りを行ってみます。

1.テスト用バケットとテストファイルの準備

まずテスト用S3 Filesと大きいファイルを準備します。

# 変数(バケット名はグローバルで一意にする)
export REGION=ap-northeast-1
export BUCKET=s3files-directread-demo-$(date +%s)

# バケット作成(東京リージョンは LocationConstraint が必要)
aws s3api create-bucket \
  --bucket "$BUCKET" \
  --region "$REGION" \
  --create-bucket-configuration LocationConstraint="$REGION"

# 100MB のテストファイルを作成
head -c 100m /dev/urandom > testfile.bin
# (macOS で head の -c が使えない場合は: dd if=/dev/urandom of=testfile.bin bs=1m count=100)

# アップロード
aws s3 cp testfile.bin "s3://$BUCKET/testfile.bin"

# 確認
aws s3 ls "s3://$BUCKET/"
echo "BUCKET = $BUCKET"

S3 Filesは 対象ファイルが1MB以上かどうかで、キャッシュを利用するか直接バケットから読み込むかが動的に判断されるため1MB以上のファイルを作成してアップロードしておきます。

2. バケットに S3 Files ファイルシステムを作成する

S3 Files を有効化するためにはVersioningが有効化されている必要があります

export ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
# REGION, BUCKET は Step 1 で設定済みの想定

aws s3api put-bucket-versioning \
  --bucket "$BUCKET" \
  --versioning-configuration Status=Enabled

S3 Filesの動作に必要なポリシーを作成します。

cat > s3files-sync-trust.json <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowS3FilesAssumeRole",
      "Effect": "Allow",
      "Principal": { "Service": "elasticfilesystem.amazonaws.com" },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": { "aws:SourceAccount": "$ACCOUNT_ID" },
        "ArnLike": { "aws:SourceArn": "arn:aws:s3files:$REGION:$ACCOUNT_ID:file-system/*" }
      }
    }
  ]
}
EOF

cat > s3files-sync-perms.json <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "S3BucketPermissions",
      "Effect": "Allow",
      "Action": ["s3:ListBucket", "s3:ListBucketVersions"],
      "Resource": "arn:aws:s3:::$BUCKET",
      "Condition": { "StringEquals": { "aws:ResourceAccount": "$ACCOUNT_ID" } }
    },
    {
      "Sid": "S3ObjectPermissions",
      "Effect": "Allow",
      "Action": ["s3:AbortMultipartUpload", "s3:DeleteObject*", "s3:GetObject*", "s3:List*", "s3:PutObject*"],
      "Resource": "arn:aws:s3:::$BUCKET/*",
      "Condition": { "StringEquals": { "aws:ResourceAccount": "$ACCOUNT_ID" } }
    },
    {
      "Sid": "EventBridgeManage",
      "Effect": "Allow",
      "Action": ["events:DeleteRule", "events:DisableRule", "events:EnableRule", "events:PutRule", "events:PutTargets", "events:RemoveTargets"],
      "Condition": { "StringEquals": { "events:ManagedBy": "elasticfilesystem.amazonaws.com" } },
      "Resource": ["arn:aws:events:*:*:rule/DO-NOT-DELETE-S3-Files*"]
    },
    {
      "Sid": "EventBridgeRead",
      "Effect": "Allow",
      "Action": ["events:DescribeRule", "events:ListRuleNamesByTarget", "events:ListRules", "events:ListTargetsByRule"],
      "Resource": ["arn:aws:events:*:*:rule/*"]
    }
  ]
}
EOF

aws iam create-role \
  --role-name S3FilesSyncRole \
  --assume-role-policy-document file://s3files-sync-trust.json

aws iam put-role-policy \
  --role-name S3FilesSyncRole \
  --policy-name S3FilesSyncInline \
  --policy-document file://s3files-sync-perms.json

# このロール ARN を控える(create-file-system の --role-arn に使う)
export SYNC_ROLE_ARN=$(aws iam get-role --role-name S3FilesSyncRole --query 'Role.Arn' --output text)
echo "$SYNC_ROLE_ARN"

ポリシーが出来たらS3 Filesを作成します。

aws s3files create-file-system \
  --region "$REGION" \
  --bucket "arn:aws:s3:::$BUCKET" \
  --client-token "$(uuidgen)" \
  --role-arn "$SYNC_ROLE_ARN"
{
    "creationTime": "2026-09-19T19:49:40+09:00",
    "fileSystemArn": "arn:aws:s3files:ap-northeast-1:917561075114:file-system/fs-029c59ff78b16edc4",
    "fileSystemId": "fs-029c59ff78b16edc4",
    "bucket": "arn:aws:s3:::s3files-directread-demo-1789814481",
    "prefix": "",
    "clientToken": "ecd99d37-5168-4c8f-a651-5fdd5841fa1a",
    "status": "creating",
    "roleArn": "arn:aws:iam::917561075114:role/S3FilesSyncRole",
    "ownerId": "917561075114",
    "tags": []
}

出来上がったファイルシステムのIDを環境変数に入れておきます。値は皆さんごとに変わりますので書き換えて以下を実行します。

export FS_ID=fs-029c59ff78b16edc4

3. ネットワーク・マウントターゲット・アクセスポイント の設定

出来上がったS3 Filesを VPC経由でマウントできるようにします。

export VPC_ID=$(aws ec2 describe-vpcs --filters Name=isDefault,Values=true \
  --query 'Vpcs[0].VpcId' --output text --region "$REGION")
echo "VPC_ID=$VPC_ID"

# サブネット(AZ付き)を確認
aws ec2 describe-subnets --filters Name=vpc-id,Values=$VPC_ID \
  --query 'Subnets[].{Subnet:SubnetId,AZ:AvailabilityZone}' --output table --region "$REGION"

# 変数にも入れておく
export SUBNETS=$(aws ec2 describe-subnets --filters Name=vpc-id,Values=$VPC_ID \
  --query 'Subnets[].SubnetId' --output text --region "$REGION")
echo "SUBNETS=$SUBNETS"

Lambda とマウントターゲットに同じ SG を付与し、その SG 内での NFS 通信を許可します。

export SG_ID=$(aws ec2 create-security-group \
  --group-name s3files-lambda-sg \
  --description "S3 Files + Lambda NFS 2049" \
  --vpc-id "$VPC_ID" \
  --query 'GroupId' --output text --region "$REGION")
echo "SG_ID=$SG_ID"

# 同じSGを送信元にして 2049 を許可(Lambda ↔ マウントターゲット)
aws ec2 authorize-security-group-ingress \
  --group-id "$SG_ID" \
  --protocol tcp --port 2049 \
  --source-group "$SG_ID" \
  --region "$REGION"

では設定したVPC/サブネット、セキュリティグループを使う形でマウントターゲットを作成します。

for SUBNET in $SUBNETS; do
  echo "creating mount target in $SUBNET ..."
  aws s3files create-mount-target \
    --region "$REGION" \
    --file-system-id "$FS_ID" \
    --subnet-id "$SUBNET" \
    --security-groups "$SG_ID"
done

マウントターゲットはAZごとに一つ作成されます。このため、一つのAZに複数のサブネットがある場合、初回のみ成功し、残りのサブネットは作成が重複エラーとなりますが気にしなくて大丈夫です。

作成にはある程度時間がかかりますので数分待ちます。マウントターゲットの作成が行われたサブネットIDを環境変数にセットします。Subnet IDは皆さんごとに異なりますので必要に応じて書き換えてください。

# マウントターゲットを作れた3サブネット(各AZ代表)
export LAMBDA_SUBNETS="subnet-087f4979069a4ee80 subnet-0ac057c4b85f84183 subnet-0fceed4e94f07fc04"
echo "$LAMBDA_SUBNETS"

ではアクセスポイントを作成します。

 export AP_ARN=$(aws s3files create-access-point \
  --region "$REGION" \
  --file-system-id "$FS_ID" \
  --posix-user uid=1000,gid=1000 \
  --client-token "$(uuidgen)" \
  --query 'accessPointArn' --output text)

echo "AP_ARN=$AP_ARN"

最後にLambda関数がキャッシュレイヤを介さず直接S3バケットと読み書きを行う際のS3用VPC エンドポイントを作成します。

export RTB_IDS=$(aws ec2 describe-route-tables --filters Name=vpc-id,Values=$VPC_ID \
  --query 'RouteTables[].RouteTableId' --output text --region "$REGION")
echo "RTB_IDS=$RTB_IDS"

aws ec2 create-vpc-endpoint \
  --vpc-id "$VPC_ID" \
  --service-name com.amazonaws.$REGION.s3 \
  --vpc-endpoint-type Gateway \
  --route-table-ids $RTB_IDS \
  --region "$REGION"

4. 実行ロールと Lambda 関数(128 MB)の作成

ではLambda関数を作成していきます。まず関数にアタッチするロールを作成します。

# 信頼ポリシー(lambda.amazonaws.com)
cat > lambda-trust.json <<'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    { "Effect": "Allow", "Principal": {"Service": "lambda.amazonaws.com"}, "Action": "sts:AssumeRole" }
  ]
}
EOF

aws iam create-role \
  --role-name S3FilesLambdaExecRole \
  --assume-role-policy-document file://lambda-trust.json

# VPC アクセス(ENI 作成/削除)+ ログ
aws iam attach-role-policy --role-name S3FilesLambdaExecRole \
  --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole

# S3 Files のマウント権限(クライアントアクセス)
aws iam attach-role-policy --role-name S3FilesLambdaExecRole \
  --policy-arn arn:aws:iam::aws:policy/AmazonS3FilesClientReadWriteAccess

# 直接読み取り用の S3 権限(インライン)
cat > lambda-directread.json <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    { "Sid":"S3ObjectReadAccess","Effect":"Allow","Action":["s3:GetObject","s3:GetObjectVersion"],"Resource":"arn:aws:s3:::$BUCKET/*" },
    { "Sid":"S3BucketListAccess","Effect":"Allow","Action":"s3:ListBucket","Resource":"arn:aws:s3:::$BUCKET" }
  ]
}
EOF

aws iam put-role-policy --role-name S3FilesLambdaExecRole \
  --policy-name S3DirectReadInline \
  --policy-document file://lambda-directread.json

export EXEC_ROLE_ARN=$(aws iam get-role --role-name S3FilesLambdaExecRole --query 'Role.Arn' --output text)
echo "EXEC_ROLE_ARN=$EXEC_ROLE_ARN"

関数のコードを準備します。

mkdir -p fn
cat > fn/app.py <<'PY'
import os, time

MOUNT  = os.environ.get("MOUNT_PATH", "/mnt/s3files")
TARGET = os.environ.get("TARGET_FILE", "testfile.bin")

def handler(event, context):
    path = os.path.join(MOUNT, TARGET)
    start = time.time()
    total = 0
    with open(path, "rb") as f:
        while True:
            chunk = f.read(8 * 1024 * 1024)  # 8MB ずつ
            if not chunk:
                break
            total += len(chunk)
    elapsed = time.time() - start
    mb = total / (1024 * 1024)
    result = {
        "file": path,
        "MB": round(mb, 2),
        "elapsed_sec": round(elapsed, 3),
        "throughput_MB_s": round(mb / elapsed, 2) if elapsed > 0 else None,
        "memory_limit_MB": context.memory_limit_in_mb,
    }
    print(result)
    return result
PY

(cd fn && zip -r ../function.zip . >/dev/null) && echo "zipped"

関数を作成します。

aws lambda create-function \
  --function-name s3files-directread-demo \
  --runtime python3.13 \
  --handler app.handler \
  --role "$EXEC_ROLE_ARN" \
  --zip-file fileb://function.zip \
  --memory-size 128 \
  --timeout 120 \
  --environment "Variables={MOUNT_PATH=/mnt/s3files,TARGET_FILE=testfile.bin}" \
  --vpc-config "SubnetIds=$(echo $LAMBDA_SUBNETS | tr ' ' ','),SecurityGroupIds=$SG_ID" \
  --region "$REGION"

5. S3 Files をアタッチして DirectS3Read を実行

ENABLED、つまり直接S3バケットからの読み込みを可能としLambda関数を作成します。

aws lambda update-function-configuration \
  --function-name s3files-directread-demo \
  --file-system-configs "Arn=$AP_ARN,LocalMountPath=/mnt/s3files,S3FilesConfig={DirectS3Read=ENABLED}" \
  --region "$REGION"

aws lambda wait function-updated-v2 --function-name s3files-directread-demo --region "$REGION" && echo "ready (ENABLED)"

計測します。

invoke   # 設定変更後の再ウォームアップ(数値は無視でOK)
echo "--- ENABLED 計測 ---"
for i in 1 2 3; do invoke; done
echo "--- ENABLED 計測 ---"
for i in 1 2 3; do invoke; done
{"file": "/mnt/s3files/testfile.bin", "MB": 100.0, "elapsed_sec": 6.561, "throughput_MB_s": 15.24, "memory_limit_MB": "128"}
--- ENABLED 計測 ---
{"file": "/mnt/s3files/testfile.bin", "MB": 100.0, "elapsed_sec": 6.353, "throughput_MB_s": 15.74, "memory_limit_MB": "128"}
{"file": "/mnt/s3files/testfile.bin", "MB": 100.0, "elapsed_sec": 6.431, "throughput_MB_s": 15.55, "memory_limit_MB": "128"}
{"file": "/mnt/s3files/testfile.bin", "MB": 100.0, "elapsed_sec": 6.312, "throughput_MB_s": 15.84, "memory_limit_MB": "128"}

Lambda関数作成時の S3FilesConfig={DirectS3Read=ENABLED} が有効化されています。

Written by
編集部

亀田 治伸

Kameda Harunobu

  • Facebook->
  • X->
  • GitHub->

Share

Facebook->X->
Back
to list
<-