1. はじめに
ユーザーがアップロードした高解像度画像を、Webサイト表示用にサムネイル化(リサイズ)する仕組みを構築しました。
「サーバー管理の手間を省きたい」「低コストで運用したい」という要件から、S3イベント通知 + Lambda (Java 17) を組み合わせたイベント駆動型の構成を採用しています。
JavaによるLambda実装は、SDKの型安全な恩恵を受けられるため、エンタープライズな案件や複雑なビジネスロジックを伴う画像処理に向いています。
2. システム構成
無限ループ(再帰呼び出し)を防止するため、バケットを入力用と出力用で分離しています。
- Upload Bucket: ユーザーがオリジナル画像を保存
- Lambda (Java): オブジェクト作成イベントを検知し、リサイズ処理を実行
- Resized Bucket: 加工済み画像を保存
3. 実装コード (Java 17 + AWS SDK v2)
画像処理ライブラリには、軽量で実績のある Thumbnailator を使用しました。
pom.xml (依存関係)
<dependencies>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-core</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-events</artifactId>
<version>3.11.0</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<version>2.20.0</version>
</dependency>
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.19</version>
</dependency>
</dependencies>
LambdaHandler.java
public class S3ResizeHandler implements RequestHandler<S3Event, String> {
private final S3Client s3Client = S3Client.builder().build();
@Override
public String handleRequest(S3Event s3Event, Context context) {
S3EventNotificationRecord record = s3Event.getRecords().get(0);
String srcBucket = record.getS3().getBucket().getName();
String srcKey = record.getS3().getObject().getUrlDecodedKey();
// 出力先バケット名は環境変数などで管理するのが推奨
String dstBucket = System.getenv("DEST_BUCKET");
try (ResponseInputStream<GetObjectResponse> s3Object = s3Client.getObject(r -> r.bucket(srcBucket).key(srcKey))) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// 200x200にリサイズしてJPG出力
Thumbnails.of(s3Object)
.size(200, 200)
.outputFormat("jpg")
.toOutputStream(outputStream);
// 出力先バケットへ保存
s3Client.putObject(r -> r.bucket(dstBucket).key("resized-" + srcKey),
RequestBody.fromBytes(outputStream.toByteArray()));
return "Success";
} catch (Exception e) {
context.getLogger().log("Error processing S3 event: " + e.getMessage());
return "Error";
}
}
}
- 実績から学んだ「ハマりポイント」と対策
① 再帰呼び出しによる「課金死」の回避
もし入力と出力に同じバケットを使っていた場合、**「リサイズ画像を保存 → その保存をトリガーにまたLambdaが起動」**という無限ループが発生し、クラウド破産を招く恐れがあります。
対策: 必ず入力バケットと出力バケットを分けるか、プレフィックス(フォルダ)でフィルタリングを行うことが鉄則です。
② Java特有のメモリ設計
Java Runtimeは起動(コールドスタート)時にメモリを消費しやすいため、デフォルトの128MB設定では OutOfMemoryError や処理の遅延が目立ちました。
対策: メモリを 512MB以上 に割り当てることで、JVMの動作が安定し、結果的に処理時間が短縮されコストも最適化されました。
③ IAMポリシーの最小権限
Lambdaには必要最小限の権限を与えます。
s3:GetObject (入力バケット)
s3:PutObject (出力バケット)
logs:CreateLogGroup / CreateLogStream / PutLogEvents (CloudWatch Logs用)
5. まとめ
Javaで構築するサーバーレス画像処理は、一度型が決まれば非常に堅牢に動作します。今後はさらなる高速化のために GraalVMを用いたNative Image化 や、大量リクエストに備えた Provisioned Concurrency の導入なども検討の余地がありそうです。