마이크로서비스 디자인 패턴 2제 정리
Circuit Breaker(서비스 간 통신 — 실패 임계치 넘으면 회로 개방, 유예 후 half-open 복구 탐색)와 Saga(분산 트랜잭션을 로컬 트랜잭션 연쇄+보상 트랜잭션으로) 정리.
아직 만들지 않았습니다
이 페이지는 카드에 적힌 내용을 펼쳐 보여줄 뿐입니다 — 돌아가는 코드도, 열어볼 데모도 아직 없습니다. 무엇을 왜 만들려는지가 아래에 있습니다.
어떻게 보나
아직 범위 미정 — 정독 노트. (Notion 지시, 2026-08-07 추가.)
기술 노트
각 항목이 실제로 무엇을 보여주고 어떻게 동작하는지 — 위 카드보다 자세한 기술 설명입니다.
마이크로서비스 디자인 패턴 2제 정리준비 중
목적: Verex 연결: 정산 파이프라인(오라클 조회→정산→페이아웃)이 정확히 Saga 구조이고, RPC·인덱서 호출부에는 Circuit Breaker가 기본기입니다.
동작 방식: ① Circuit Breaker(서비스 간 통신): 연쇄 장애 방지 — 실패가 임계치를 넘으면 회로를 열어 호출을 즉시 실패시키고, 유예 후 half-open으로 회복을 탐색. ② Saga(데이터 일관성): 분산 트랜잭션을 로컬 트랜잭션의 연쇄 + 보상 트랜잭션(compensation)으로 풀기 — 2PC 없이 최종 일관성.
관련 코드:
"""Circuit Breaker + Saga: two microservice patterns in one toy demo.
Circuit Breaker: an inter-service call site that trips OPEN after N failures,
fails fast while open, then probes recovery via a HALF_OPEN trial after a
cooldown. Saga: a settlement pipeline of local steps, each with a compensating
rollback, undone in reverse order the moment one step fails.
"""
import time
class CircuitBreaker:
def __init__(self, fail_threshold=3, cooldown=0.05):
self.fail_threshold = fail_threshold
self.cooldown = cooldown
self.failures = 0
self.state = "CLOSED"
self.opened_at = None
def call(self, fn):
if self.state == "OPEN":
if time.monotonic() - self.opened_at >= self.cooldown:
self.state = "HALF_OPEN"
else:
raise RuntimeError("circuit open: failing fast")
try:
result = fn()
except Exception:
self.failures += 1
if self.state == "HALF_OPEN" or self.failures >= self.fail_threshold:
self.state, self.opened_at = "OPEN", time.monotonic()
raise
else:
self.failures, self.state = 0, "CLOSED"
return result
def flaky_rpc(calls=[0]):
calls[0] += 1
if calls[0] <= 3:
raise ConnectionError("oracle RPC timeout")
return "oracle_price=42"
breaker = CircuitBreaker(fail_threshold=2, cooldown=0.02)
for i in range(5):
try:
print(f"attempt {i}: {breaker.call(flaky_rpc)} (state={breaker.state})")
except Exception as e:
print(f"attempt {i}: failed ({e}) (state={breaker.state})")
time.sleep(0.03)
# --- Saga: oracle lookup -> settlement -> payout, with compensations ---
ledger = []
def oracle_lookup():
ledger.append("oracle_locked")
def undo_oracle_lookup():
ledger.remove("oracle_locked")
def settle():
ledger.append("settled")
def undo_settle():
ledger.remove("settled")
def payout():
raise RuntimeError("payout provider unavailable")
steps = [(oracle_lookup, undo_oracle_lookup), (settle, undo_settle), (payout, None)]
completed = []
try:
for step, compensate in steps:
step()
completed.append(compensate)
print("saga completed:", ledger)
except Exception as e:
print(f"saga failed at step: {e} -- rolling back")
for compensate in reversed(completed):
if compensate:
compensate()
print("ledger after rollback:", ledger)