π μ΄λ―Έμ§ 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μ΄ μλμΌλ‘ λ³κ²½λ¨
β οΈ μ£Όμμ¬ν
- S3 λ²ν· κΆν: Public Read μ€μ νμ (λλ CloudFront OAI μ¬μ©)
- HTTPS: 보μμ μν΄ λ°λμ HTTPS μ¬μ©
- μ΄λ―Έμ§ μ΅μ ν: μ λ‘λ μ WebP λ³ν, 리μ¬μ΄μ§ κ³ λ €
π λΉκ΅ μ 리
| νλͺ© | S3 μ§μ μ κ·Ό | CloudFront(CDN) |
|---|---|---|
| κ΅¬μΆ λμ΄λ | β μ¬μ | ββ λ³΄ν΅ |
| μλ΅ μλ | λ³΄ν΅ | λΉ λ¦ |
| κΈλ‘λ² λμ | μ νμ | μ°μ |
| λΉμ© | μ λ ΄ | νΈλν½ λ°λΌ μ¦κ° |
| κΆμ₯ μμ | MVP, μ΄κΈ° | μλΉμ€ μ±μ₯κΈ° |
π― κ²°λ‘
- μ΄κΈ°μλ S3 URL μ§μ μ μ₯μΌλ‘ μμνμΈμ. κ°λ¨νκ³ μΆ©λΆν©λλ€.
- νΈλν½μ΄ μ¦κ°νλ©΄ CloudFront μΆκ°λ₯Ό κ³ λ €νμΈμ.
- URLμ DBμ μ μ₯νλ ꡬ쑰λΌλ©΄ λμ€μ μ½λ μμ μμ΄ CDN μ ν κ°λ₯ν©λλ€.
π¬ βμ§κΈ λΉμ₯ μλ²½ν νμλ μμ΅λλ€. νμ₯ κ°λ₯ν κ΅¬μ‘°λ‘ μμνμΈμ!β