AWS S3는 이미지, 문서, 동영상 같은 파일을 저장하는 객체 스토리지이다.
Spring Boot 서버의 디스크에 파일을 직접 저장하지 않고 S3에 저장하면, 서버가 재시작되거나 EC2가 교체되어도 파일이 사라지지 않고, 여러 서버가 같은 파일 저장소를 함께 사용할 수 있다. AWS 공식 문서에서도 S3는 확장성, 데이터 가용성, 보안, 성능을 제공하는 객체 스토리지라고 설명한다. (AWS Docs)
일반적인 파일 업로드 구조는 다음과 같다.
사용자
↓
Spring Boot API 서버
↓
AWS S3
↓
파일 저장
예를 들어 사용자가 프로필 이미지를 업로드하면 Spring Boot 서버는 파일을 받아서 S3 버킷에 저장한다.
1. 사용자가 MultipartFile로 이미지 업로드
2. Spring Boot 서버가 파일 검증
3. 파일명을 UUID 등으로 변경
4. S3에 파일 업로드
5. DB에는 S3 파일 key 또는 URL 저장
여기서 중요한 점은 DB에 파일 자체를 저장하지 않는 것이다.
DB에는 파일의 위치 정보만 저장한다.
예시로 DB에는 다음과 같은 정보가 들어간다.
id: 1
user_id: 10
original_file_name: profile.png
stored_file_name: users/10/profile/uuid-profile.png
content_type: image/png
file_size: 123456
s3_key: users/10/profile/uuid-profile.png
S3에는 실제 파일이 저장되고, RDS에는 파일을 찾기 위한 메타데이터만 저장된다.
Spring Boot에서는 AWS SDK를 사용해 S3에 파일을 업로드한다.
흐름은 다음과 같다.
Controller
↓
Service
↓
S3 Client
↓
S3 Bucket
예시 코드 흐름은 다음과 같다.
public String uploadFile(MultipartFile file) {
String key = "profiles/" + UUID.randomUUID() + "-" + file.getOriginalFilename();
PutObjectRequest request = PutObjectRequest.builder()
.bucket(bucketName)
.key(key)
.contentType(file.getContentType())
.build();
s3Client.putObject(
request,
RequestBody.fromInputStream(file.getInputStream(), file.getSize())
);
return key;
}
이때 key는 S3 안에서 파일을 구분하는 경로 역할을 한다.
예를 들어 다음과 같다.
profiles/1/abc-profile.png
posts/10/images/def-image.png
운영에서는 원본 파일명 그대로 저장하지 않고, UUID나 날짜 경로를 조합해서 저장하는 것이 좋다.
profile/2026/05/20/uuid.png
이렇게 하면 파일명이 중복되는 문제를 피할 수 있다.
운영 환경에서는 AWS Access Key를 코드나 설정 파일에 직접 넣지 않는 것이 좋다.
대신 EC2에 IAM Role을 연결해서 S3 접근 권한을 부여한다.
구조는 다음과 같다.
EC2
↓
IAM Role
↓
S3 PutObject / GetObject 권한
↓
S3 Bucket 접근
예를 들어 EC2에 연결된 IAM Role에 다음 권한을 준다.
s3:PutObject
s3:GetObject
s3:DeleteObject
그리고 권한 범위는 전체 S3가 아니라 특정 버킷으로 제한하는 것이 안전하다.
arn:aws:s3:::my-service-bucket/*
즉, Spring Boot 서버는 IAM Role을 통해 S3에 접근하고, 사용자는 직접 S3 권한을 가지지 않는다.
S3에 저장된 파일을 사용자에게 보여주는 방법은 크게 세 가지가 있다.
파일을 공개로 열어두고 URL로 접근하게 하는 방식이다.
https://bucket-name.s3.ap-northeast-2.amazonaws.com/profile.png
하지만 이 방식은 누구나 URL만 알면 접근할 수 있기 때문에 프로필 이미지, 게시글 이미지처럼 공개되어도 되는 파일에만 제한적으로 사용해야 한다.
비공개 버킷을 유지하면서, 특정 시간 동안만 접근 가능한 임시 URL을 발급하는 방식이다.
사용자
↓
Spring Boot 서버에 파일 조회 요청
↓
Spring Boot가 Presigned URL 생성
↓
사용자는 임시 URL로 S3 파일 접근
AWS 공식 문서에 따르면 Presigned URL은 버킷 정책을 바꾸지 않고도 S3 객체에 대해 시간 제한이 있는 접근 권한을 줄 수 있으며, 다운로드뿐 아니라 업로드에도 사용할 수 있다. (AWS Docs)
예를 들어 URL 유효 시간을 5분으로 설정할 수 있다.
https://s3.../profile.png?X-Amz-Signature=...
이 URL은 만료 시간이 지나면 사용할 수 없다.
이미지나 정적 파일을 대량으로 제공해야 한다면 S3 앞에 CloudFront를 붙일 수 있다.
사용자
↓
CloudFront
↓
S3
CloudFront를 사용하면 캐싱을 통해 응답 속도를 높이고, S3를 직접 공개하지 않으면서 파일을 제공할 수 있다.
S3 파일 공개 여부는 다음 방식으로 제어할 수 있다.
가장 기본은 S3 버킷의 모든 퍼블릭 액세스 차단을 켜는 것이다.
AWS는 S3의 퍼블릭 접근이 ACL이나 Bucket Policy를 통해 부여될 수 있으며, 이를 막기 위해 계정 또는 버킷 단위에서 Block Public Access를 설정할 수 있다고 설명한다. (Amazon Web Services, Inc.)
운영 서비스에서는 보통 다음처럼 설정한다.
S3 Bucket
- Block all public access: ON
- ACL 사용하지 않음
- Bucket Policy로 전체 공개하지 않음
이렇게 하면 외부 사용자가 S3 URL을 직접 알아도 파일에 접근할 수 없다.
Bucket Policy는 버킷 단위로 접근 권한을 설정하는 정책이다.
예를 들어 정적 웹사이트처럼 모든 파일을 공개해야 한다면 s3:GetObject를 전체 사용자에게 허용하는 정책을 사용할 수 있다. AWS 문서에서도 버킷을 공개 읽기 가능하게 만들려면 Block Public Access 설정을 조정하고, 모든 사용자에게 s3:GetObject 권한을 주는 Bucket Policy를 작성해야 한다고 설명한다. (AWS Docs)
예시는 다음과 같다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-public-bucket/*"
}
]
}
하지만 일반적인 사용자 업로드 파일에는 이렇게 전체 공개 정책을 적용하지 않는 것이 좋다.
더 안전한 방식은 다음과 같다.
- 버킷은 Private 유지
- EC2 IAM Role만 S3 접근 허용
- 사용자는 Presigned URL로 제한적 접근
Presigned URL은 비공개 파일을 일정 시간 동안만 접근 가능하게 하는 임시 URL이다.
예를 들어 사용자가 본인의 프로필 이미지를 조회할 때 서버가 5분짜리 Presigned URL을 만들어준다.
사용자 요청
↓
Spring Boot 서버에서 권한 확인
↓
Presigned URL 생성
↓
사용자에게 임시 URL 응답
↓
사용자는 해당 URL로 S3 파일 다운로드
Presigned URL은 URL을 생성한 IAM 주체의 권한 범위 안에서 동작하며, 업로드용 Presigned URL도 생성할 수 있다. 즉, 사용자가 AWS 자격 증명을 직접 가지지 않아도 특정 객체에 대해 제한된 업로드나 다운로드를 수행할 수 있다. (AWS Docs)
예를 들면 다음 상황에서 유용하다.
- 회원만 볼 수 있는 프로필 이미지
- 결제한 사용자만 다운로드 가능한 파일
- 일정 시간 동안만 접근 가능한 첨부파일
- 서버를 거치지 않고 브라우저에서 S3로 직접 업로드
실제 서비스에서는 공개 파일과 비공개 파일을 구분하는 것이 좋다.
public/
└── 게시글 썸네일, 공개 이미지
private/
└── 사용자 첨부파일, 인증이 필요한 파일
또는 버킷 자체를 나누기도 한다.
my-service-public-bucket
my-service-private-bucket
정리하면 다음과 같다.
| 방식 | 설명 | 사용 상황 |
|---|---|---|
| Block Public Access | 퍼블릭 접근 전체 차단 | 기본 권장 설정 |
| Bucket Policy | 버킷 단위 접근 정책 | 정적 웹사이트, 공개 파일 |
| IAM Role | EC2, ECS 같은 AWS 리소스에 권한 부여 | 서버에서 S3 접근 |
| Presigned URL | 일정 시간만 접근 가능한 임시 URL | 비공개 파일 다운로드/업로드 |
| CloudFront | CDN을 통한 파일 제공 | 이미지, 정적 파일 대량 제공 |
EC2에 MySQL을 직접 설치해서 사용할 수도 있다.
하지만 운영 환경에서는 보통 RDS를 사용한다.
가장 큰 이유는 데이터베이스 운영 부담을 AWS에 맡길 수 있기 때문이다.
AWS 공식 문서에 따르면 RDS는 AWS 클라우드에서 관계형 데이터베이스를 더 쉽게 설정, 운영, 확장할 수 있게 해주는 서비스이며, 일반적인 데이터베이스 관리 작업을 처리해준다. (AWS Docs)
EC2에 직접 DB를 설치하면 백업 스크립트, 백업 저장 위치, 복구 방법을 직접 관리해야 한다.
반면 RDS는 자동 백업, 스냅샷, 특정 시점 복구 기능을 제공한다. RDS 문서에서도 DB 스냅샷, 특정 시점 복구, 자동 또는 수동 백업을 사용할 수 있다고 설명한다. (AWS Docs)
운영에서 DB 백업은 매우 중요하다.
애플리케이션 서버는 다시 만들 수 있지만, DB 데이터는 사라지면 복구가 어렵다.
EC2에 직접 MySQL을 설치하면 다음을 직접 해야 한다.
- MySQL 설치
- 버전 업그레이드
- 보안 패치
- 백업 설정
- 장애 감지
- 복구 작업
- 디스크 용량 관리
RDS는 이러한 반복적인 관리 작업을 줄여준다. AWS는 RDS가 프로비저닝, 설정, 백업, 패치 같은 데이터베이스 관리 작업을 자동화한다고 설명한다. (Amazon Web Services, Inc.)
운영 DB는 한 대만 두면 장애에 취약하다.
EC2에 직접 DB를 설치하면 복제 구성, 장애 전환, 백업 서버 구성을 직접 해야 한다.
RDS는 Multi-AZ 같은 옵션을 통해 운영 DB의 가용성을 높이기 쉽다.
구조는 다음과 같다.
Application Server
↓
RDS Primary DB
↓
Standby DB
장애가 발생했을 때 직접 MySQL 서버를 새로 구성하는 것보다, RDS의 관리형 기능을 사용하는 편이 운영 부담이 훨씬 적다.
RDS는 VPC 안의 Private Subnet에 배치할 수 있다.
일반적인 구조는 다음과 같다.
Public Subnet
└── ALB
Private Subnet
├── EC2 Application Server
└── RDS Database
그리고 Security Group을 통해 애플리케이션 서버에서만 DB에 접근하도록 제한한다.
RDS Security Group
- Inbound 3306
- Source: EC2 Security Group
이렇게 하면 외부 인터넷에서 RDS에 직접 접근하지 못하게 막을 수 있다.
RDS는 인스턴스 크기 변경, 스토리지 확장, 모니터링, 성능 지표 확인이 EC2 직접 설치 방식보다 편하다.
운영 중에는 다음 지표를 계속 봐야 한다.
- CPU 사용률
- DB Connection 수
- Free Storage
- Read/Write IOPS
- Slow Query
RDS는 CloudWatch와 연동되어 이런 지표를 확인하기 쉽다.
| 구분 | EC2에 직접 DB 설치 | RDS 사용 |
|---|---|---|
| 설치 | 직접 설치 | 콘솔에서 생성 |
| 백업 | 직접 구성 | 자동 백업, 스냅샷 |
| 패치 | 직접 수행 | 관리형 패치 지원 |
| 장애 대응 | 직접 복구 | 관리형 기능 활용 |
| 확장 | 직접 설정 | 인스턴스/스토리지 확장 용이 |
| 보안 | 직접 설정 | VPC, SG, 암호화 연동 용이 |
| 운영 부담 | 큼 | 상대적으로 작음 |
AWS S3를 이용한 파일 저장은 사용자가 업로드한 파일을 Spring Boot 서버가 받아 S3 버킷에 저장하고, DB에는 실제 파일이 아니라 S3 object key, 원본 파일명, 파일 크기, content type 같은 메타데이터만 저장하는 방식입니다. 이렇게 하면 EC2 서버 디스크에 파일을 저장하지 않아도 되고, 서버가 여러 대로 늘어나도 같은 S3 저장소를 사용할 수 있습니다.
S3 파일의 외부 공개 여부는 Block Public Access, Bucket Policy, IAM Role, Presigned URL 등으로 제어합니다. 운영에서는 보통 버킷을 private으로 유지하고, EC2에는 IAM Role로 S3 접근 권한을 부여합니다. 사용자가 비공개 파일을 조회해야 할 때는 서버가 권한을 확인한 뒤 일정 시간만 유효한 Presigned URL을 발급합니다. 공개 파일이 필요한 경우에는 Bucket Policy로 s3:GetObject를 허용할 수 있지만, 사용자 업로드 파일 전체를 공개하는 것은 위험합니다.
EC2에 직접 DB를 설치하지 않고 RDS를 사용하는 이유는 운영 부담을 줄이기 위해서입니다. EC2에 직접 DB를 설치하면 백업, 복구, 패치, 장애 대응, 모니터링, 확장 작업을 직접 해야 합니다. 반면 RDS는 관계형 데이터베이스를 쉽게 설정, 운영, 확장할 수 있는 관리형 서비스이며, 자동 백업, 스냅샷, 패치, 모니터링 같은 기능을 제공하므로 운영 환경에서 더 안정적으로 DB를 관리할 수 있습니다.