κΈ€/λͺ¨μŒ

πŸ—οΈ 100만 μ‚¬μš©μž ν™•μž₯을 μœ„ν•œ μ•„ν‚€ν…μ²˜: λ‚΄κ°€ 미리 μ•Œμ•˜λ”λΌλ©΄ μ’‹μ•˜μ„ 것듀

octo54 2025. 5. 20. 10:59
λ°˜μ‘ν˜•

 

πŸ—οΈ 100만 μ‚¬μš©μž ν™•μž₯을 μœ„ν•œ μ•„ν‚€ν…μ²˜: λ‚΄κ°€ 미리 μ•Œμ•˜λ”λΌλ©΄ μ’‹μ•˜μ„ 것듀

μ΄ˆκΈ°μ—λŠ” 100λͺ…μ˜ 일일 μ‚¬μš©μžλ§ŒμœΌλ‘œλ„ λ§Œμ‘±μŠ€λŸ¬μ› μ§€λ§Œ,
λͺ‡ 달 λ§Œμ— 10,000λͺ…, 100,000λͺ…μœΌλ‘œ κΈ‰μ¦ν•˜λ©΄μ„œ κΈ°μ‘΄ μ•„ν‚€ν…μ²˜μ˜ ν•œκ³„κ°€ λ“œλŸ¬λ‚¬μŠ΅λ‹ˆλ‹€.
1,000λͺ…을 μœ„ν•΄ μ„€κ³„λœ μ‹œμŠ€ν…œμ€ 100만 λͺ…μ˜ μ‚¬μš©μžλ₯Ό κ°λ‹Ήν•˜μ§€ λͺ»ν–ˆμŠ΅λ‹ˆλ‹€.
이 κΈ€μ—μ„œλŠ” μš°λ¦¬κ°€ κ²ͺμ—ˆλ˜ λ¬Έμ œμ™€ κ·Έ ν•΄κ²° 과정을 κ³΅μœ ν•©λ‹ˆλ‹€.


🧱 1단계: 단일 λͺ¨λ†€λ¦¬μ‹ μ•„ν‚€ν…μ²˜μ˜ ν•œκ³„

πŸ’» 초기 μ•„ν‚€ν…μ²˜ ꡬ성

  • Spring Boot μ• ν”Œλ¦¬μΌ€μ΄μ…˜
  • MySQL λ°μ΄ν„°λ² μ΄μŠ€
  • NGINX λ‘œλ“œ λ°ΈλŸ°μ„œ
  • λͺ¨λ“  것이 ν•˜λ‚˜μ˜ VM에 배포됨

⚠️ 문제점

  • CPU μ‚¬μš©λ₯  μ΅œλŒ€μΉ˜ 도달
  • 쿼리 속도 μ €ν•˜
  • κ°€μš©μ„± 99% μ΄ν•˜λ‘œ κ°μ†Œ
  • DB 잠금, GC μ§€μ—°, μŠ€λ ˆλ“œ 경쟁 λ°œμƒ

❗ κ΅ν›ˆ

단일 μ„œλ²„μ™€ λͺ¨λ†€λ¦¬μ‹ κ΅¬μ‘°λ‘œλŠ” μ‚¬μš©μžκ°€ 급증할 λ•Œ 병λͺ©μ΄ λ°œμƒν•˜μ—¬ μ‹œμŠ€ν…œμ΄ λΆˆμ•ˆμ •ν•΄μ§‘λ‹ˆλ‹€.


πŸš€ 2단계: μ„œλ²„ 수 μ¦κ°€λ‘œλŠ” ν•΄κ²°λ˜μ§€ μ•ŠλŠ” 병λͺ© ν˜„μƒ

πŸ› οΈ 쑰치 사항

  • μ„œλ²„ 수λ₯Ό 늘렀 μˆ˜ν‰ ν™•μž₯ μ‹œλ„
  • λ‘œλ“œ λ°ΈλŸ°μ‹±μœΌλ‘œ νŠΈλž˜ν”½ λΆ„μ‚°

πŸ’₯ μ—¬μ „νžˆ λ°œμƒν•œ 문제

  • λ°μ΄ν„°λ² μ΄μŠ€ μ—°κ²° 수 μ œν•œ
  • GC μ§€μ—°μœΌλ‘œ μΈν•œ 응닡 μ‹œκ°„ 증가
  • λΆˆκ· ν˜•ν•œ νŠΈλž˜ν”½ λΆ„μ‚° 문제

❗ κ΅ν›ˆ

λ‹¨μˆœνžˆ μ„œλ²„ 수λ₯Ό λŠ˜λ¦¬λŠ” κ²ƒλ§ŒμœΌλ‘œλŠ” 근본적인 병λͺ© 문제λ₯Ό ν•΄κ²°ν•  수 μ—†μŠ΅λ‹ˆλ‹€.
λ°μ΄ν„°λ² μ΄μŠ€ ν™•μž₯μ„± λ¬Έμ œμ™€ GC νŠœλ‹μ„ λ¨Όμ € κ³ λ €ν•΄μ•Ό ν•©λ‹ˆλ‹€.


🧩 3단계: λ§ˆμ΄ν¬λ‘œμ„œλΉ„μŠ€ μ•„ν‚€ν…μ²˜λ‘œμ˜ μ „ν™˜

πŸ’‘ μ „ν™˜ μ „λž΅

  • λͺ¨λ†€λ¦¬ν‹±μ„ λ§ˆμ΄ν¬λ‘œμ„œλΉ„μŠ€λ‘œ 뢄리
  • μ„œλΉ„μŠ€λ³„ 독립 배포 및 ν™•μž₯
  • μ„œλΉ„μŠ€ κ°„ 톡신을 μœ„ν•œ λ©”μ‹œμ§€ 큐 λ„μž… (예: RabbitMQ, Kafka)

🌟 효과

  • μ„œλΉ„μŠ€λ³„ 독립적 ν™•μž₯ κ°€λŠ₯
  • μž₯μ•  λ°œμƒ μ‹œ νŠΉμ • μ„œλΉ„μŠ€λ§Œ 쀑단
  • 개발 및 배포 속도 ν–₯상

πŸ’‘ μ˜ˆμ‹œ μ½”λ“œ: FastAPIλ₯Ό μ΄μš©ν•œ μ„œλΉ„μŠ€ 뢄리

from fastapi import FastAPI

app = FastAPI()

@app.get("/user/{user_id}")
async def get_user(user_id: int):
    return {"user_id": user_id, "status": "active"}

πŸ—ƒοΈ 4단계: λ°μ΄ν„°λ² μ΄μŠ€ 샀딩 및 캐싱 μ „λž΅

πŸ—„οΈ λ°μ΄ν„°λ² μ΄μŠ€ 문제 ν•΄κ²° μ „λž΅

  • μ‚¬μš©μž ID 기반 λ°μ΄ν„°λ² μ΄μŠ€ 샀딩
  • Redisλ₯Ό ν™œμš©ν•œ μΊμ‹±μœΌλ‘œ 읽기 λΆ€ν•˜ κ°μ†Œ
  • 읽기/μ“°κΈ° λΆ„λ¦¬λ‘œ λ°μ΄ν„°λ² μ΄μŠ€ λΆ€ν•˜ λΆ„μ‚°

πŸ’‘ μ˜ˆμ‹œ μ½”λ“œ: Redis 캐싱

import redis

r = redis.Redis(host='localhost', port=6379, db=0)

# 데이터 캐싱
r.set("user:1001", "active")

# μΊμ‹œ 데이터 쑰회
status = r.get("user:1001")
print(f"User 1001 status: {status.decode()}")

πŸ” 5단계: λͺ¨λ‹ˆν„°λ§ 및 μžλ™ ν™•μž₯

πŸ“Š λͺ¨λ‹ˆν„°λ§ 도ꡬ

  • Prometheus: λ©”νŠΈλ¦­ μˆ˜μ§‘ 및 μ‹œκ°ν™”
  • Grafana: λŒ€μ‹œλ³΄λ“œ ꡬ성 및 κ²½κ³  μ„€μ •

πŸŒ€ μžλ™ ν™•μž₯ μ„€μ •

  • CPU μ‚¬μš©λ₯ μ΄ 80% 이상일 λ•Œ μžλ™μœΌλ‘œ μΈμŠ€ν„΄μŠ€ μΆ”κ°€
  • λ©”λͺ¨λ¦¬ μ‚¬μš©λ₯ μ΄ 75% μ΄ν•˜λ‘œ λ–¨μ–΄μ§€λ©΄ μΈμŠ€ν„΄μŠ€ μΆ•μ†Œ

πŸ’‘ μ˜ˆμ‹œ μ½”λ“œ: Prometheus μ„€μ •

global:
  scrape_interval: 15s

scrape_configs:
  - job_name: "my_service"
    static_configs:
      - targets: ["localhost:8000"]

πŸ’‘ μ €μ˜ 생각

μ‹œμŠ€ν…œμ„ ν™•μž₯ν•˜λŠ” κ³Όμ •μ—μ„œ κ°€μž₯ μ€‘μš”ν•œ 것은 초기 섀계 λ‹¨κ³„μ—μ„œ ν™•μž₯성을 κ³ λ €ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€.
μ΄ˆκΈ°μ—λŠ” λ‹¨μˆœν•œ ꡬ쑰가 λΉ λ₯Έ 개발과 배포에 μœ λ¦¬ν•  수 μžˆμ§€λ§Œ,
μ‚¬μš©μž μˆ˜κ°€ κΈ‰μ¦ν•˜λ©΄μ„œ ꡬ쑰적 ν•œκ³„μ— λΆ€λ”ͺ히게 λ©λ‹ˆλ‹€.

κ°œμΈμ μœΌλ‘œλŠ” λ§ˆμ΄ν¬λ‘œμ„œλΉ„μŠ€ μ•„ν‚€ν…μ²˜μ™€ λ°μ΄ν„°λ² μ΄μŠ€ 샀딩을 ν†΅ν•œ ν™•μž₯이
λŒ€κ·œλͺ¨ νŠΈλž˜ν”½μ„ μ²˜λ¦¬ν•˜λŠ” 데 κ°€μž₯ νš¨κ³Όμ μ΄μ—ˆλ‹€κ³  λŠκΌˆμŠ΅λ‹ˆλ‹€.
λ˜ν•œ, λͺ¨λ‹ˆν„°λ§κ³Ό μžλ™ ν™•μž₯을 톡해 μ‹€μ‹œκ°„μœΌλ‘œ μ‹œμŠ€ν…œ μƒνƒœλ₯Ό κ΄€λ¦¬ν•˜λŠ” 것이
μ•ˆμ •μ μΈ μ„œλΉ„μŠ€ 운영의 ν•΅μ‹¬μž…λ‹ˆλ‹€.

μ•žμœΌλ‘œ μƒˆλ‘œμš΄ ν”„λ‘œμ νŠΈλ₯Ό μ‹œμž‘ν•  λ•ŒλŠ” μ΄ˆκΈ°λΆ€ν„° ν™•μž₯성을 κ³ λ €ν•œ 섀계λ₯Ό 톡해
λΉ„μš© 절감과 μ•ˆμ •μ„± 확보λ₯Ό λ™μ‹œμ— μΆ”κ΅¬ν•˜λŠ” 것이 ν•„μš”ν•˜λ‹€κ³  μƒκ°ν•©λ‹ˆλ‹€.


 

μ‹œμŠ€ν…œ ν™•μž₯μ„±, λ§ˆμ΄ν¬λ‘œμ„œλΉ„μŠ€ μ•„ν‚€ν…μ²˜, λ°μ΄ν„°λ² μ΄μŠ€ 샀딩, 캐싱 μ „λž΅, μžλ™ ν™•μž₯, λͺ¨λ‹ˆν„°λ§,
Spring Boot, FastAPI, Redis, Prometheus, Grafana