Home > Backend Development > πŸ“š[Backend Development] πŸš€ 이미지 URL μ €μž₯ μ „λž΅: S3 직접 μ €μž₯ vs CDN ν™œμš©

πŸ“š[Backend Development] πŸš€ 이미지 URL μ €μž₯ μ „λž΅: S3 직접 μ €μž₯ vs CDN ν™œμš©
Backend Development Server Java AWS S3 CDN

πŸš€ 이미지 URL μ €μž₯ μ „λž΅: S3 직접 μ €μž₯ vs CDN ν™œμš©

πŸ€” 고민의 μ‹œμž‘

κ²Œμž„μ΄λ‚˜ 컀머슀 μ„œλΉ„μŠ€λ₯Ό κ°œλ°œν•˜λ‹€ 보면 이미지 νŒŒμΌμ„ μ–΄λ–»κ²Œ 관리할지 κ³ λ―Όν•˜κ²Œ λ©λ‹ˆλ‹€.
특히 λͺ¬μŠ€ν„°, μ•„μ΄ν…œ, μƒν’ˆ μ΄λ―Έμ§€μ²˜λŸΌ 수백~수천 개의 이미지λ₯Ό 닀뀄야 ν•œλ‹€λ©΄?

μ˜€λŠ˜μ€ μ‹€λ¬΄μ—μ„œ 자주 λ§ˆμ£ΌμΉ˜λŠ” 이미지 μ €μž₯ μ „λž΅μ— λŒ€ν•΄ μ•Œμ•„λ³΄κ² μŠ΅λ‹ˆλ‹€.


πŸ“¦ κΈ°λ³Έ 섀계: Entity ꡬ쑰

λ¨Όμ € λͺ¬μŠ€ν„° 정보λ₯Ό μ €μž₯ν•˜λŠ” Entityλ₯Ό μ‚΄νŽ΄λ΄…μ‹œλ‹€.

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "monster_species")
public class MonsterSpecies {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String name;

    @Enumerated(EnumType.STRING)
    private MonsterStage stage;

    @Enumerated(EnumType.STRING)
    private MonsterAttribute attribute;

    private int baseHp;
    private int basePower;

    // 🎯 핡심: μ΄λ―Έμ§€λŠ” URL λ¬Έμžμ—΄λ‘œ μ €μž₯
    private String idleImageUrl;     // λŒ€κΈ° 이미지 URL
    private String attackImageUrl;   // 곡격 이미지 URL
}

μ£Όλͺ©ν•  점은 이미지 파일 자체(Binary)κ°€ μ•„λ‹Œ URL(String) 을 μ €μž₯ν•œλ‹€λŠ” κ²ƒμž…λ‹ˆλ‹€.


βœ… S3 URL 직접 μ €μž₯ λ°©μ‹μ˜ μž₯점

1️⃣ ν”„λ‘ νŠΈμ—”λ“œ 연동이 λ„ˆλ¬΄ κ°„λ‹¨ν•©λ‹ˆλ‹€

API 응닡을 받은 ν”„λ‘ νŠΈμ—”λ“œλŠ” 아무 가곡 없이 λ°”λ‘œ μ‚¬μš© κ°€λŠ₯ν•©λ‹ˆλ‹€.

// API 응닡
{
  "name": "ν‚ΉνŽ­κ·„",
  "idleImageUrl": "https://tibumon-bucket.s3.ap-northeast-2.amazonaws.com/monsters/king-penguin-idle.png"
}

// Reactμ—μ„œ λ°”λ‘œ μ‚¬μš©
<img src={monster.idleImageUrl} alt={monster.name} />

λ³„λ„μ˜ 이미지 μ„œλ²„λ‚˜ 파일 λ‹€μš΄λ‘œλ“œ 둜직이 ν•„μš” μ—†μŠ΅λ‹ˆλ‹€. πŸŽ‰

2️⃣ DB μ„±λŠ₯ 뢀담이 μ „ν˜€ μ—†μŠ΅λ‹ˆλ‹€

  • 잘λͺ»λœ 방식: 이미지 Binaryλ₯Ό DB BLOB으둜 μ €μž₯ β†’ DB μš©λŸ‰ 폭발 πŸ’₯
  • μ˜¬λ°”λ₯Έ 방식: κ°€λ²Όμš΄ URL λ¬Έμžμ—΄λ§Œ μ €μž₯ β†’ DBλŠ” λΉ λ₯΄κ³  κ°€λ³κ²Œ μœ μ§€ ⚑
-- μ‹€μ œ μ €μž₯λ˜λŠ” λ°μ΄ν„°λŠ” μ΄λ ‡κ²Œ κ°€λ³μŠ΅λ‹ˆλ‹€
INSERT INTO monster_species (name, idle_image_url) 
VALUES ('ν‚ΉνŽ­κ·„', 'https://tibumon-bucket.s3.../king-penguin.png');

3️⃣ 이미지 μ—…λ°μ΄νŠΈκ°€ μœ μ—°ν•©λ‹ˆλ‹€

이미지λ₯Ό κ΅μ²΄ν•˜κ³  μ‹Άλ‹€λ©΄?

  • S3에 μƒˆ 이미지 μ—…λ‘œλ“œ
  • DB의 URL만 λ³€κ²½

μ„œλ²„ 재배포 없이 μ¦‰μ‹œ λ°˜μ˜λ©λ‹ˆλ‹€!


πŸš€ λ‹€μŒ 단계: CDN(CloudFront) λ„μž…

νŠΈλž˜ν”½μ΄ μ¦κ°€ν•˜λ©΄ 속도와 λΉ„μš© λ¬Έμ œκ°€ μƒκΉλ‹ˆλ‹€.

문제 상황

μ‚¬μš©μž(μ„œμšΈ) β†’ S3(μ„œμšΈ) βœ… 빠름 (50ms)
μ‚¬μš©μž(λ―Έκ΅­) β†’ S3(μ„œμšΈ) ❌ 느림 (300ms)

ν•΄κ²°μ±…: CloudFront CDN ν™œμš©

CDN은 μ „ 세계에 λΆ„μ‚°λœ μΊμ‹œ μ„œλ²„λ‘œ, μ‚¬μš©μžμ™€ κ°€κΉŒμš΄ κ³³μ—μ„œ 이미지λ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€.

μ‚¬μš©μž(λ―Έκ΅­) β†’ CloudFront(λ―Έκ΅­ μ—£μ§€) β†’ S3(μ„œμšΈ)
               └─ μΊμ‹œ 히트 μ‹œ S3κΉŒμ§€ μ•ˆ 감! (20ms) 🎯

πŸ”§ λ§ˆμ΄κ·Έλ ˆμ΄μ…˜μ€ κ°„λ‹¨ν•©λ‹ˆλ‹€

DBμ—μ„œ URL의 도메인 λΆ€λΆ„λ§Œ 일괄 λ³€κ²½ν•˜λ©΄ 끝!

-- Before: S3 직접 μ ‘κ·Ό
'https://tibumon-bucket.s3.ap-northeast-2.amazonaws.com/monsters/agumon.png'

-- After: CloudFrontλ₯Ό ν†΅ν•œ μ ‘κ·Ό
'https://d1a2b3c4d5e6f7.cloudfront.net/monsters/agumon.png'

μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ½”λ“œλŠ” ν•œ 쀄도 μˆ˜μ •ν•  ν•„μš” μ—†μŠ΅λ‹ˆλ‹€. 😎


πŸ’‘ 싀무 팁

✨ URL 섀계 λͺ¨λ²” 사둀

// 경둜 ꡬ쑰λ₯Ό μ²΄κ³„μ μœΌλ‘œ 관리
String baseUrl = "https://cdn.yourservice.com";
String path = "/monsters/" + monsterId + "/idle.png";
String fullUrl = baseUrl + path;

// λ‚˜μ€‘μ— CDN μ£Όμ†Œλ§Œ λ°”κΎΈλ©΄ 전체 URL이 μžλ™μœΌλ‘œ 변경됨

⚠️ μ£Όμ˜μ‚¬ν•­

  1. S3 버킷 κΆŒν•œ: Public Read μ„€μ • ν•„μˆ˜ (λ˜λŠ” CloudFront OAI μ‚¬μš©)
  2. HTTPS: λ³΄μ•ˆμ„ μœ„ν•΄ λ°˜λ“œμ‹œ HTTPS μ‚¬μš©
  3. 이미지 μ΅œμ ν™”: μ—…λ‘œλ“œ μ „ WebP λ³€ν™˜, 리사이징 κ³ λ €

πŸ“Š 비ꡐ 정리

ν•­λͺ© S3 직접 μ ‘κ·Ό CloudFront(CDN)
ꡬ좕 λ‚œμ΄λ„ ⭐ 쉬움 ⭐⭐ 보톡
응닡 속도 보톡 빠름
κΈ€λ‘œλ²Œ λŒ€μ‘ μ œν•œμ  우수
λΉ„μš© μ €λ ΄ νŠΈλž˜ν”½ 따라 증가
ꢌμž₯ μ‹œμ  MVP, 초기 μ„œλΉ„μŠ€ μ„±μž₯κΈ°

🎯 결둠

  1. μ΄ˆκΈ°μ—λŠ” S3 URL 직접 μ €μž₯으둜 μ‹œμž‘ν•˜μ„Έμš”. κ°„λ‹¨ν•˜κ³  μΆ©λΆ„ν•©λ‹ˆλ‹€.
  2. νŠΈλž˜ν”½μ΄ μ¦κ°€ν•˜λ©΄ CloudFront μΆ”κ°€λ₯Ό κ³ λ €ν•˜μ„Έμš”.
  3. URL을 DB에 μ €μž₯ν•˜λŠ” ꡬ쑰라면 λ‚˜μ€‘μ— μ½”λ“œ μˆ˜μ • 없이 CDN μ „ν™˜ κ°€λŠ₯ν•©λ‹ˆλ‹€.

πŸ’¬ β€œμ§€κΈˆ λ‹Ήμž₯ μ™„λ²½ν•  ν•„μš”λŠ” μ—†μŠ΅λ‹ˆλ‹€. ν™•μž₯ κ°€λŠ₯ν•œ ꡬ쑰둜 μ‹œμž‘ν•˜μ„Έμš”!”