オンプレの帳票処理の課題
オンプレ環境での帳票処理は、NASを中心に以下のような構成が多い。
【アップロード】
クライアント → Apache → Tomcat(マルチパートを受信)→ NAS に格納
【帳票生成】
リクエスト → Java(JasperReports / iText で生成)→ NASの /tmp に一時保存 → レスポンス
【ダウンロード】
クライアント → Apache → NASのファイルをストリームして返す
この構成の主な課題:
- APサーバーがボトルネック:大きなファイルのアップロード・ダウンロードがTomcatのスレッドを占有する
- NASの容量・可用性管理:物理ストレージの増設・バックアップ・障害対応が運用負荷になる
- 大容量ファイルのタイムアウト:APサーバーを経由するためHTTPタイムアウトの影響を受けやすい
- スケールアウト時の共有ストレージ問題:複数APサーバーがNASを共有するため、NASがSPOFになりやすい
presigned URL とは
S3の presigned URL(署名付きURL)は、一時的に有効なS3への直接アクセス権をURLに埋め込む仕組みだ。JavaアプリがS3 SDKで生成し、クライアントに返す。クライアントはそのURLを使ってAPサーバーを経由せずS3に直接アクセスできる。
S3Presigner presigner = S3Presigner.create();
// アップロード用 presigned URL(PUT)
PutObjectRequest putReq = PutObjectRequest.builder()
.bucket("my-documents")
.key("uploads/" + userId + "/" + fileId)
.contentType("application/pdf")
.build();
PresignedPutObjectRequest presignedPut = presigner.presignPutObject(
PutObjectPresignRequest.builder()
.signatureDuration(Duration.ofMinutes(15)) // 15分有効
.putObjectRequest(putReq)
.build()
);
String uploadUrl = presignedPut.url().toString();
// ダウンロード用 presigned URL(GET)
GetObjectRequest getReq = GetObjectRequest.builder()
.bucket("my-documents")
.key("reports/" + reportId + "/output.pdf")
.build();
PresignedGetObjectRequest presignedGet = presigner.presignGetObject(
GetObjectPresignRequest.builder()
.signatureDuration(Duration.ofMinutes(30)) // 30分有効
.getObjectRequest(getReq)
.build()
);
String downloadUrl = presignedGet.url().toString();
アップロード設計(2パターン)
【パターンA:Java経由アップロード(シンプル構成)】
クライアント → ALB → Java → S3.putObject()
適用:小〜中規模ファイル(数MB以下)、認可チェックが複雑なケース
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
【パターンB:S3直接アップロード(大規模向け・推奨)】
① クライアント → Java:presigned PUT URL の発行を依頼
② Java → S3 SDK:presigned URL を生成してクライアントに返す
③ クライアント → S3:presigned URL を使って直接 PUT
APサーバーのメモリ・スレッド消費ゼロ
大容量ファイル(数百MB)でもタイムアウト不要
ECS コンテナの帯域を消費しない
| 観点 | パターンA(Java経由) | パターンB(S3直接) |
|---|---|---|
| 実装の簡易さ | シンプル | フロントエンド側の実装が必要 |
| APサーバー負荷 | 高い(ファイルをメモリに展開) | ゼロ |
| 大容量ファイル | タイムアウトリスクあり | 問題なし |
| 認可チェック | Java側で自由に実装可 | presigned URL発行前にJavaで実施 |
| 推奨規模 | 数MB以下・社内ツール | 本番システム・大規模 |
帳票生成設計(同期 vs 非同期)
【同期生成:即時応答(小規模帳票向け)】
リクエスト → Java(JasperReports で生成)→ S3 に格納
→ presigned URL をレスポンスで返す
適用:数秒以内で生成完了する単純な帳票
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
【非同期生成:大規模帳票・時間がかかる処理向け】
① リクエスト → Java → SQS に「帳票生成ジョブ」を登録
② Java → クライアントへ「受付完了(jobId)」を即時応答
③ (バックグラウンド)SQS → ECS Worker が受信
④ ECS Worker が帳票生成 → S3 に格納
⑤ 完了通知:SNS → メール or WebSocket → クライアント
適用:数十秒以上かかる大量データの帳票・複数帳票の一括生成
💡 非同期生成のメリット
HTTPレスポンスタイムアウト(通常60秒〜数分)の制約から解放される。帳票生成専用のECS Workerをスケールアウトすることで、リクエスト処理サーバーとワーカーを独立してスケールできる。SQSは帳票ジョブをキューで保持するため、Workerが落ちてもジョブが失われない。
ダウンロード設計(2パターン)
| 観点 | パターンA(Java経由) | パターンB(presigned URL) |
|---|---|---|
| APサーバー負荷 | 高い(ファイル全体を中継) | ゼロ |
| 認可チェック | ダウンロード毎にJavaで実施 | presigned URL発行前にJavaで実施 |
| URLの有効期限 | なし(セッション次第) | 設定可(例:15分・30分) |
| ダウンロード速度 | APサーバー帯域依存 | S3の高スループット直接配信 |
| 大容量ファイル | タイムアウトリスクあり | 問題なし(S3マルチパート) |
S3バケット設計
s3://myapp-documents/
├── uploads/
│ └── {userId}/{yyyy-MM}/{fileId}/original.pdf
│ ← ユーザーアップロードファイル
│
├── reports/
│ └── {jobId}/{timestamp}/output.pdf
│ ← 生成済み帳票
│
└── temp/
└── {uuid}/work.pdf
← 処理中の一時ファイル(Lifecycle: 1日で自動削除)
バケットポリシー:
・パブリックアクセス:完全にブロック
・ECSタスクロール(IAM)からのみ操作許可
・presigned URL経由のアクセスのみ外部に公開
ライフサイクルルール:
・temp/ : 1日後に自動削除
・reports/ : 90日後にGlacierへ移行
・uploads/ : 1年後に自動削除(要件に応じて調整)
対比表
| 処理 | オンプレ | AWS |
|---|---|---|
| アップロード受付 | Tomcatが受信 → NASに保存 | presigned PUT URL → S3直接PUT |
| 帳票生成(同期) | Java生成 → NAS一時保存 → レスポンス | Java生成 → S3格納 → presigned URLを返却 |
| 帳票生成(非同期) | 独自キュー or バッチスケジューラ | SQS → ECS Worker → S3 → SNS通知 |
| ダウンロード | Apache / Tomcatがストリーム返却 | S3 presigned GET URL(直接取得) |
| ストレージ管理 | NAS容量管理・増設・バックアップ | S3(容量無制限・99.999999999%耐久性) |
| 一時ファイル削除 | cronジョブで定期削除 | S3 Lifecycle ルールで自動削除 |
ハマりポイント
⚠️ presigned URLの有効期限とCORS設定
フロントエンドからS3に直接PUTする場合、S3バケットにCORSポリシーを設定する必要がある。設定漏れがあると「ブロックされた」エラーになりがち。また有効期限(signatureDuration)は短すぎると大容量ファイルのアップロード中に期限切れが発生するため、ファイルサイズを考慮して設定すること。
⚠️ S3バケットはリージョン固定
presigned URLを生成するJavaコード内でリージョンを明示しないと、デフォルトリージョン(us-east-1)でURLが生成され、実際のバケットリージョンと一致せずアクセスエラーになる。S3Presigner.builder().region(Region.AP_NORTHEAST_1)のようにリージョンを明示すること。
✅ 次の記事では…
PART 05 では JavaからのDB接続設計を扱う。RDS Proxyが接続数爆発問題をどう解決するか、AuroraのWriter/Reader Endpoint分離の実装方法を解説する。