๐โก Circuit Breaker + ์บ์ ๊ธฐ๋ฐ ํฌ์ค์ฒดํฌ: ๋ง์ดํฌ๋ก์๋น์ค ์์ ์ฑ์ ์๋ฒฝํ ์กฐํฉ
๐ ๋ฐฐ๊ฒฝ: ์ ์ด ์กฐํฉ์ด ํ์ํ๋?
Bible Card SaaS๋ 8๊ฐ์ ๋ง์ดํฌ๋ก์๋น์ค๋ก ๊ตฌ์ฑ๋ ๋ณต์กํ ์์คํ ์ ๋๋ค. ๊ฐ ์๋น์ค๊ฐ ๋ ๋ฆฝ์ ์ผ๋ก ์ด์๋๋ฉด์๋ ์๋ก ํต์ ํด์ผ ํ๋ ํ๊ฒฝ์์, ๋จ์ผ ์๋น์ค์ ์ฅ์ ๊ฐ ์ ์ฒด ์์คํ ์ ๋ง๋น์ํค๋ ์ฐ์ ์ฅ์ (Cascade Failure)๊ฐ ๊ฐ์ฅ ํฐ ์ํ ์์์์ต๋๋ค.
์ด๊ธฐ ๋ฌธ์ ์ ๋ค
- JWT ๊ฒ์ฆ ์คํจ ์ 5์ด ๋๊ธฐ โ ์ฌ์ฉ์ ๊ฒฝํ ์ ํ
- ์ด๋ฏธ์ง ์์ฑ ์๋น์ค ์ฅ์ ์ ์ ์ฒด ์์คํ ๋ค์ด
- ํฌ์ค์ฒดํฌ๊ฐ ๋ณ๋ชฉ์ด ๋์ด ๋ชจ๋ํฐ๋ง ์์ฒด๊ฐ ์์คํ ๋ถํ ์ฆ๊ฐ
- ๊ตฌ๊ธ ๋ก๊ทธ์ธ์์ โCannot serialize non-str key Noneโ ์ค๋ฅ ๋ฐ์
๐ก๏ธ Circuit Breaker: ์ ๊ธฐ ์ฐจ๋จ๊ธฐ์์ ๋ฐฐ์ด ์งํ
ํต์ฌ ์์ด๋์ด
์ค์ ์ ๊ธฐ ์ฐจ๋จ๊ธฐ๊ฐ ๊ณผ๋ถํ ์ ์ ๊ธฐ๋ฅผ ์ฐจ๋จํด์ ํ์ฌ๋ฅผ ๋ฐฉ์งํ๋ฏ์ด, ์ํํธ์จ์ด์์๋ ์๋น์ค ์ฅ์ ์ ์์ฒญ์ ์ฐจ๋จํด์ ์ฐ์ ์ฅ์ ๋ฅผ ๋ฐฉ์งํฉ๋๋ค.
3๊ฐ์ง ์ํ๋ก ๋์
๐ข CLOSED (์ ์) โ ๋ชจ๋ ์์ฒญ ํ์ฉ ๐ด OPEN (์ฐจ๋จ) โ ๋ชจ๋ ์์ฒญ ์ฆ์ ๊ฑฐ๋ถ ๐ก HALF_OPEN (ํ ์คํธ) โ ์ผ๋ถ ์์ฒญ์ผ๋ก ๋ณต๊ตฌ ํ์ธ
์ค์ ๊ตฌํ ์์
JWT ๊ฒ์ฆ์ Circuit Breaker๋ก ๋ณดํธ
user_info = await with_circuit_breaker( name=โjwt_verificationโ, func=jwt_verification, config=CircuitConfig( failure_threshold=5, # 5๋ฒ ์คํจ์ ์ฐจ๋จ timeout_duration=30.0, # 30์ด ํ ์ฌ์๋ call_timeout=2.0 # 2์ด ํ์์์ ) )
๋๋ผ์ด ํจ๊ณผ
- Before: JWT ๊ฒ์ฆ ์คํจ ์ 5์ด ๋๊ธฐ
- After: ์ฅ์ ๊ฐ์ง ์ ์ฆ์ 503 ์๋ต (5ms)
- ๊ฒฐ๊ณผ: ์ฌ์ฉ์๋ ๋น ๋ฅธ ์๋ฌ ๋ฉ์์ง๋ฅผ ๋ฐ๊ณ , ์๋ฒ๋ ๋ฆฌ์์ค ๋ณดํธ
๐ฅ ํฌ์ค์ฒดํฌ์ ํจ์ : ์์ฌ๊ฐ ํ์๋ฅผ ๋ ์ํ๊ฒ ๋ง๋ค ์ ์๋ค
๊ธฐ์กด ํฌ์ค์ฒดํฌ์ ๋ฌธ์
โ ์ํํ ํฌ์ค์ฒดํฌ
@router.get(โ/healthโ) async def bad_health_check():
Prometheus๊ฐ 15์ด๋ง๋ค ์ด๊ฑธ ํธ์ถํ๋ฉดโฆ
db_status = await db.execute(โSELECT 1โ) # DB ๋ถํ! paddle_status = await paddle_api.get_products() # ์ธ๋ถ API ๋ถํ! redis_status = await redis.ping() # Redis ๋ถํ!
return {โstatusโ: โokโ if all([db_status, paddle_status, redis_status]) else โfailโ}
๋ฌธ์ ์ : ํฌ์ค์ฒดํฌ ์์ฒด๊ฐ ์์คํ ์ ๋ถํ๋ฅผ ์ฃผ์ด โ๊ฑด๊ฐ์ ํ์ธํ๋ ค๋ค๊ฐ ๋ณ์ ๋ง๋๋โ ์ํฉ ๋ฐ์
์บ์ ๊ธฐ๋ฐ ํด๊ฒฐ์ฑ
โ ์์ ํ ์บ์ ๊ธฐ๋ฐ ํฌ์ค์ฒดํฌ
class HealthChecker: def init(self): self.health_cache = {} self.last_check_time = {} self.check_interval = 30 # 30์ด๋ง๋ค๋ง ์ค์ ์ฒดํฌ
async def get_component_health(self, component: str, check_func): now = time.time()
์บ์๊ฐ ์ ํจํ๋ฉด ์ฆ์ ๋ฐํ
if (now - self.last_check_time.get(component, 0)) self.check_interval: return self.health_cache.get(component)
์ค์ ์ฒดํฌ๋ 30์ด๋ง๋ค๋ง
try: result = await asyncio.wait_for(check_func(), timeout=2.0) self.health_cache[component] = {โstatusโ: โhealthyโ, โlast_checkโ: now} except Exception as e: self.health_cache[component] = {โstatusโ: โunhealthyโ, โerrorโ: str(e)}
self.last_check_time[component] = now return self.health_cache[component]
์ฑ๋ฅ ๊ฐ์ ๊ฒฐ๊ณผ
- ์๋ต์๊ฐ: 2000ms โ 200ms (10๋ฐฐ ํฅ์)
- ์์คํ ๋ถํ: 95% ๊ฐ์
- ๋ชจ๋ํฐ๋ง ์์ ์ฑ: Prometheus๊ฐ ์๋ฌด๋ฆฌ ์์ฃผ ํธ์ถํด๋ ์์
๐ Circuit Breaker + ์บ์ ํฌ์ค์ฒดํฌ์ ์๋์ง
์๋ฒฝํ ์กฐํฉ์ธ ์ด์
- Circuit Breaker: ์ฅ์ ๋ฐ์ ์ ๋น ๋ฅธ ์ฐจ๋จ
- ์บ์ ํฌ์ค์ฒดํฌ: ์ฅ์ ๊ฐ์ง๋ฅผ ์ํ ๋ชจ๋ํฐ๋ง์ด ์์คํ ์ ๋ถํ ์์
- ์ํธ ๋ณด์: ์๋ก์ ์ฝ์ ์ ๋ณด์ํ๋ ์๋ฒฝํ ํํธ๋์ญ
์ค์ ์ฅ์ ์๋๋ฆฌ์ค
09:00 - ๋ชจ๋ ์๋น์ค ์ ์ ๐ข โโโ Circuit Breaker: CLOSED โโโ ํฌ์ค์ฒดํฌ: ์บ์๋ ๊ฒฐ๊ณผ๋ก ๋น ๋ฅธ ์๋ต โโโ ์ฌ์ฉ์: ์ ์ ์๋น์ค ์ด์ฉ
09:05 - Auth Service ์ฅ์ ๋ฐ์ ๐ฅ โโโ Circuit Breaker: 3๋ฒ ์คํจ ํ OPEN ์ ํ โโโ ํฌ์ค์ฒดํฌ: ์บ์๋ก ์ฅ์ ์ํ ๋น ๋ฅด๊ฒ ์ ํ โโโ ์ฌ์ฉ์: ์ฆ์ โ์๋น์ค ๋ถ๊ฐโ ๋ฉ์์ง (๋๊ธฐ ์๊ฐ ์์)
09:35 - 30์ด ํ ์๋ ๋ณต๊ตฌ ์๋ ๐ โโโ Circuit Breaker: HALF_OPEN์ผ๋ก ์ ํ โโโ ํฌ์ค์ฒดํฌ: ์ค์ ์ฒดํฌ๋ก ๋ณต๊ตฌ ํ์ธ โโโ ๋ณต๊ตฌ ์ฑ๊ณต ์ CLOSED๋ก ์ ํ
09:36 - ์ ์ ์๋น์ค ์ฌ๊ฐ โ
๐ ์ค์ธก ์ฑ๋ฅ ๋ฐ์ดํฐ
Circuit Breaker ํจ๊ณผ
Before Circuit Breaker
$ time curl http://localhost:8001/api/auth/me
5.234s (JWT ๊ฒ์ฆ ์คํจ ์)
After Circuit Breaker
$ time curl http://localhost:8001/api/auth/me
0.005s (์ฆ์ 503 ์๋ต)
์บ์ ํฌ์ค์ฒดํฌ ํจ๊ณผ
1์ฐจ ํธ์ถ (์ค์ ์ฒดํฌ)
$ time curl http://localhost:8001/api/gateway/health/full
2.5s - ๋ชจ๋ ์๋น์ค ์ค์ ํธ์ถ
2์ฐจ ํธ์ถ (์บ์ ์ฌ์ฉ)
$ time curl http://localhost:8001/api/gateway/health/full
0.2s - ์บ์๋ ๊ฒฐ๊ณผ ๋ฐํ
Circuit Breaker ํต๊ณ
{ โjwt_verificationโ: { โstateโ: โclosedโ, โtotal_callsโ: 1000, โtotal_failuresโ: 12, โfailure_rateโ: 0.012, โcurrent_failure_countโ: 0 } }
๐ฏ Bible Card SaaS์์์ ์ค์ ์ ์ฉ
๋ณดํธ ๋์
- JWT ๊ฒ์ฆ: ์ธ์ฆ ์๋น์ค ์ฅ์ ์์๋ ๋น ๋ฅธ ์๋ต
- ์๋น์ค ๊ฐ ํธ์ถ: Gateway โ Auth, Payment, BibleCards ๋ฑ
- ์ธ๋ถ API: Paddle ๊ฒฐ์ , OpenAI ์ด๋ฏธ์ง ์์ฑ
- ํฌ์ค์ฒดํฌ: ๋ชจ๋ํฐ๋ง์ด ์์คํ ๋ถํ ์์ด ๋์
์ค์ ์ฝ๋ ์์น
backend_gateway/ โโโ services/circuit_breaker.py # Circuit Breaker ๊ตฌํ โโโ services/unified_proxy.py # JWT ๊ฒ์ฆ ๋ณดํธ โโโ services/proxy.py # ์๋น์ค ํธ์ถ ๋ณดํธ โโโ services/health_check.py # ์บ์ ๊ธฐ๋ฐ ํฌ์ค์ฒดํฌ โโโ api/gateway.py # ํต๊ณ API ์ ๊ณต
๐ ๊ฒฐ๋ก : ๋ง์ดํฌ๋ก์๋น์ค ์์ ์ฑ์ ์๋ก์ด ํ์ค
์ด ์กฐํฉ์ด ํน๋ณํ ์ด์
- ์ฌ์ฉ์ ๊ฒฝํ: ์ฅ์ ์์๋ ๋น ๋ฅธ ํผ๋๋ฐฑ
- ์์คํ ์์ ์ฑ: ์ฐ์ ์ฅ์ ์์ ์ฐจ๋จ
- ์ด์ ํจ์จ์ฑ: ์๋ ๋ณต๊ตฌ๋ก ์๋ ๊ฐ์ ์ต์ํ
- ๋ชจ๋ํฐ๋ง ์์ ์ฑ: ๊ฑด๊ฐ ํ์ธ์ด ๊ฑด๊ฐ์ ํด์น์ง ์์
ํ์ฅ ๊ฐ๋ฅ์ฑ
- Retry Pattern: Circuit Breaker์ ์กฐํฉ์ผ๋ก ๋ ๊ฐ๋ ฅํ ๋ณต์๋ ฅ
- Bulkhead Pattern: ๋ฆฌ์์ค ๊ฒฉ๋ฆฌ๋ก ์ฅ์ ์ํฅ ๋ฒ์ ์ต์ํ
- Cache-Aside Pattern: ๋ฐ์ดํฐ ์ ๊ทผ ์ฑ๋ฅ ๊ทน๋ํ
์ต์ข ๋ฉ์์ง
โCircuit Breaker + ์บ์ ํฌ์ค์ฒดํฌ๋ ๋จ์ํ ํจํด ์กฐํฉ์ด ์๋๋ผ, ๋ง์ดํฌ๋ก์๋น์ค ์๋์ ์์กด ์ ๋ต์ ๋๋ค.โ
ํ๋์ ๋ถ์ฐ ์์คํ ์์ ์ฅ์ ๋ ์ธ์ ๋ ๋ฐ์ํ ์ ์์ต๋๋ค. ์ค์ํ ๊ฒ์ ์ฅ์ ๋ฅผ ์๋ฐฉํ๋ ๊ฒ์ด ์๋๋ผ, ์ฅ์ ๊ฐ ๋ฐ์ํ์ ๋ ์์คํ ์ด ์ด๋ป๊ฒ ๋ฐ์ํ๋๋์ ๋๋ค.
Circuit Breaker๋ ๋น ๋ฅธ ์คํจ๋ฅผ, ์บ์ ๊ธฐ๋ฐ ํฌ์ค์ฒดํฌ๋ ์์ ํ ๋ชจ๋ํฐ๋ง์ ์ ๊ณตํฉ๋๋ค. ์ด ๋์ ์กฐํฉ์ ๋ง์ดํฌ๋ก์๋น์ค๊ฐ ์๋ก๋ฅผ ๋ณดํธํ๋ฉด์๋ ์ ์ฒด ์์คํ ์ ๊ฐ์ฉ์ฑ์ ์ต๋ํํ๋ ์๋ฒฝํ ์๋ฃจ์ ์ ๋๋ค.
โ์ข์ ์ํคํ ์ฒ๋ ์ฅ์ ๋ฅผ ์จ๊ธฐ๋ ๊ฒ์ด ์๋๋ผ, ์ฅ์ ์ ํจ๊ป ์ถค์ถ๋ ๊ฒ์ด๋ค.โ ๐บ๐
ใ ใ ใ ใ ใ ์ถค์ถฐ๋ผ ๋์ ์์ด์ ํธ์ผ~