이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
- Object Storage는 왜 VM disk 대신 정적 파일과 업로드 파일 저장소로 쓰이는가?
- 정적 파일 공개와 사용자 업로드 파일 보호는 어떻게 다르게 설계해야 하는가?
- Spring 백엔드가 Object Storage 장애를 분석할 때 무엇을 확인해야 하는가?
개요
Object Storage는 파일을 object key와 metadata로 저장하는 클라우드 저장소다.
이미지, 첨부 파일, 정적 asset, 백업 파일, export 파일처럼 애플리케이션 DB에 넣기 어려운 데이터를 저장한다.
VM disk에 파일을 저장하면 VM 교체, scale-out, 장애 복구 때 파일이 사라지거나 특정 VM에만 남을 수 있다.
Object Storage를 쓰면 애플리케이션 instance 수와 파일 저장 위치를 분리할 수 있다.
하지만 권한, cache, signed URL, CORS, lifecycle을 잘못 설정하면 장애와 보안 문제가 생긴다.
정적 파일과 업로드 파일
정적 파일과 사용자 업로드 파일은 운영 기준이 다르다.
- 정적 파일은 공개 cache와 CDN에 적합하다.
- 사용자 업로드 파일은 대부분 비공개 저장과 signed URL이 필요하다.
- 정적 파일은 버전이 바뀌면 새 key로 배포하는 것이 안전하다.
- 업로드 파일은 사용자 권한과 DB metadata가 함께 맞아야 한다.
- 정적 파일은 재배포로 복구할 수 있지만 사용자 원본 파일은 복구가 어렵다.
두 데이터를 같은 bucket에 두더라도 prefix, 권한, cache 정책은 분리하는 것이 좋다.
Spring 업로드 흐름
Spring 애플리케이션은 보통 파일 bytes를 Object Storage에 올리고 DB에는 metadata를 저장한다.
client
Spring upload API
validate size/type
put object
save DB metadata
return file id or signed URL이 흐름에는 실패 지점이 있다.
- object 저장은 성공했지만 DB metadata 저장이 실패한다.
- DB row는 있는데 object 저장이 timeout으로 실패한다.
- signed URL 만료 시간이 너무 짧아 사용자가 다운로드하지 못한다.
- CORS 설정이 없어 브라우저 업로드가 실패한다.
- public bucket 정책 때문에 비공개 파일이 노출된다.
백엔드 개발자는 object key 규칙과 DB metadata 구조를 함께 설명해야 한다.
권한 설계
Object Storage 권한은 공개 여부를 명확히 나눠야 한다.
- public asset: 누구나 읽을 수 있지만 쓰기는 배포 계정만 가능해야 한다.
- private upload: 서버 또는 제한된 signed URL로만 읽는다.
- admin export: 생성자와 제한된 운영자만 접근한다.
- temporary file: lifecycle로 자동 삭제하되 필요한 기간을 문서화한다.
Bucket 전체 공개는 피하고 prefix별 정책을 분리한다.
애플리케이션 계정에는 필요한 prefix에 대한 최소 권한만 준다.
Cache와 배포
정적 파일은 cache가 성능을 만든다.
하지만 cache 정책이 잘못되면 배포 후 오래된 파일이 계속 보일 수 있다.
운영에서는 다음 원칙이 유용하다.
- JS/CSS 같은 build asset은 content hash를 key에 포함한다.
- 변경 가능한 파일에는 긴 cache를 주지 않는다.
- 사용자 업로드 파일은 권한 검사를 거친 URL을 사용한다.
- CDN invalidation은 비용과 지연이 있으므로 남용하지 않는다.
- asset 배포와 애플리케이션 배포 순서를 맞춘다.
정적 파일 404는 백엔드 오류가 아니어도 사용자 기능 장애가 될 수 있다.
관찰 명령
aws s3 ls s3://app-assets-prod/static/
aws s3api head-object --bucket app-assets-prod --key static/app.abc123.js
aws s3api get-bucket-policy-status --bucket app-assets-prodSpring 로그에서는 object key, file id, request id, storage status를 함께 남기는 것이 좋다.
select id, object_key, owner_id, status, created_at
from file_assets
where id = :file_id;DB metadata와 Object Storage 상태를 같이 확인해야 실제 파일 접근 문제를 판단할 수 있다.
장애 신호
- 업로드 API는 성공했지만 다운로드 시 404가 난다.
- 특정 browser에서만 CORS 오류로 업로드가 실패한다.
- 정적 JS 파일이 오래된 cache 때문에 새 API와 맞지 않는다.
- private file이 public URL로 접근된다.
- lifecycle 정책 이후 오래된 첨부 파일이 사라졌다.
- signed URL 만료 시간이 너무 짧거나 너무 길다.
- CDN에는 403/404가 증가하지만 Spring 로그에는 요청이 없다.
이 신호는 요청이 Spring까지 오기 전 Object Storage나 CDN에서 끝났을 수 있음을 보여 준다.
안전한 완화 조치
- private file 노출이 의심되면 bucket public access와 policy를 즉시 확인한다.
- 정적 파일 404는 이전 asset을 다시 업로드하거나 배포 순서를 조정한다.
- CORS 오류는 필요한 origin, method, header만 제한적으로 연다.
- signed URL 문제는 만료 시간과 clock 차이를 함께 확인한다.
- lifecycle 문제는 대상 prefix의 삭제 정책을 멈추고 영향 object 목록을 보존한다.
- object key 불일치는 DB와 bucket을 비교해 범위를 확정한 뒤 보정한다.
권한 문제는 빠르게 막되, 무작정 전체 공개로 해결하지 않는다.
하지 말아야 할 대응
- 사용자 업로드 파일을 VM local disk에만 저장하지 않는다.
- private file 문제를 해결하려고 bucket 전체를 공개하지 않는다.
- cache 문제를 해결하려고 모든 파일에 무기한 no-cache를 적용하지 않는다.
- object key를 추측해 DB row를 수동 연결하지 않는다.
- CORS를
*로 열고 credential 요청까지 허용하지 않는다.
인프라 담당자와 공유할 자료
- bucket명, region, prefix, object key 샘플
- public/private 파일 구분
- bucket policy, CORS, lifecycle 변경 시각
- CDN domain과 cache 정책
- DB metadata row와 object 상태 비교 결과
- signed URL 생성 조건과 만료 시간
공유 자료는 파일이 어디에 있고 누가 읽을 수 있어야 하는지를 명확히 해야 한다.
운영 체크리스트
- 정적 파일과 사용자 업로드 파일의 prefix가 분리되어 있는가?
- public access와 signed URL 사용 기준이 문서화되어 있는가?
- CORS와 cache 정책이 기능별로 구분되어 있는가?
- DB metadata와 object key를 비교하는 검증 절차가 있는가?
- lifecycle 정책이 사용자 데이터 보존 기준과 맞는가?
- CDN 403/404와 Spring 로그를 함께 볼 수 있는가?
확인 질문
확인 질문
- VM disk 대신 Object Storage를 쓰는 이유는 무엇인가?
- VM 교체와 scale-out에 파일 저장 위치가 묶이지 않게 하기 위해서다.
- 정적 파일과 사용자 업로드 파일의 가장 큰 운영 차이는 무엇인가?
- 정적 파일은 공개 cache가 중요하고, 업로드 파일은 권한과 DB metadata 일관성이 중요하다.
- Object Storage 장애에서 DB도 함께 봐야 하는 이유는 무엇인가?
- 실제 파일은 object에 있고 소유자, 상태, 권한 의미는 DB metadata에 있기 때문이다.
리뷰 체크포인트
- public asset과 private upload 정책이 분리되어 있는가?
- signed URL 만료와 권한 검증 기준이 있는가?
- cache와 CDN 운영 기준이 설명되어 있는가?
- lifecycle 정책이 데이터 보존 요구와 맞는가?
- 파일 접근 장애를 Spring, CDN, Object Storage 경계로 나눠 볼 수 있는가?