Workspace IndexKnowledge Notes › Microservice patterns — Circuit Breaker & Saga

#198PoC

Microservice patterns — Circuit Breaker & Saga

Circuit Breaker (fail fast on inter-service calls, probe recovery half-open) and Saga (distributed transactions as local-transaction chains plus compensations) — verex's settlement pipeline is a Saga; its RPC/indexer calls want a breaker.

Not yet scoped — a reading note. (Notion queue, added 2026-08-07.)

Why

Verex's settlement pipeline (oracle lookup → settlement → payout) is literally a Saga, and its RPC/indexer calls want a circuit breaker as a baseline.

How it works

Circuit Breaker (inter-service calls): trip the circuit and fail fast once failures cross a threshold, then probe recovery half-open after a cooldown. Saga (data consistency): resolve a distributed transaction as a chain of local transactions plus compensating transactions — eventual consistency without 2PC.

Related code

"""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)

Where it lands in Jayverse

  • Verex: write the settlement pipeline as an explicit Saga with a compensating transaction for each step. Oracle lookup, settlement and payout each need a defined rollback action, not just a sequence that assumes every step succeeds.
  • Rabbit/Devnet: wrap every RPC and indexer call behind a circuit breaker with a stated half-open probe interval. Fail fast once a threshold trips and probe recovery on a schedule, rather than retrying indefinitely against a degraded RPC endpoint.
  • gitboard: surface each service's breaker state — closed, open, half-open — as a visible metric. A tripped circuit is operational state worth showing on the dashboard, not something only discoverable in logs after the fact.

Key expressions

Words and phrases from this page worth keeping, with the Korean meaning and the sentence they come from.

Expression뜻 · 쓰이는 자리
fail fast장애를 조기에 감지해 빠르게 실패 처리하다 · "fail fast on inter-service calls"
probe recovery회복됐는지 시험적으로 확인해보다 · "probe recovery half-open"
half-open(회로차단기의) 반개방 상태 · "probe recovery half-open after a cooldown"
compensating transaction앞선 작업을 되돌리는 보상 트랜잭션 · "local-transaction chains plus compensations"
eventual consistency즉시는 아니지만 결국 맞춰지는 일관성 · "eventual consistency without 2PC"
2PC2단계 커밋(Two-Phase Commit) · 분산 트랜잭션을 원자적으로 확정하는 전통적 합의 프로토콜, Saga가 대신하는 대상. "eventual consistency without 2PC"

← All Knowledge Notes · Workspace Index · Top ↑

마이크로서비스 디자인 패턴 2제 정리

Circuit Breaker(서비스 간 통신 — 실패 임계치 넘으면 회로 개방, 유예 후 half-open 복구 탐색)와 Saga(분산 트랜잭션을 로컬 트랜잭션 연쇄+보상 트랜잭션으로) 정리.

아직 범위 미정 — 정독 노트. (Notion 지시, 2026-08-07 추가.)

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)

Jayverse에서의 위치

  • Verex: 정산 파이프라인을 각 단계마다 보상 트랜잭션이 있는 명시적 Saga로 작성한다. 오라클 조회, 정산, 지급 각각에 정의된 롤백 동작이 필요하다. 모든 단계가 성공한다고 가정한 순차 흐름만으로는 부족하다.
  • Rabbit/Devnet: 모든 RPC·인덱서 호출을 명시적인 half-open 프로브 간격을 가진 회로 차단기로 감싼다. 임계값을 넘으면 빠르게 실패하고 일정에 따라 복구를 프로브한다. 저하된 RPC 엔드포인트에 무한정 재시도하지 않는다.
  • gitboard: 각 서비스의 차단기 상태 — closed, open, half-open — 를 눈에 보이는 지표로 노출한다. 트립된 회로는 나중에 로그에서만 발견되는 것이 아니라 대시보드에 보여줄 만한 운영 상태다.

핵심 표현

이 페이지의 영어 본문에서 배울 만한 단어와 표현, 뜻과 나온 자리.

Expression뜻 · 쓰이는 자리
fail fast장애를 조기에 감지해 빠르게 실패 처리하다 · "fail fast on inter-service calls"
probe recovery회복됐는지 시험적으로 확인해보다 · "probe recovery half-open"
half-open(회로차단기의) 반개방 상태 · "probe recovery half-open after a cooldown"
compensating transaction앞선 작업을 되돌리는 보상 트랜잭션 · "local-transaction chains plus compensations"
eventual consistency즉시는 아니지만 결국 맞춰지는 일관성 · "eventual consistency without 2PC"
2PC2단계 커밋(Two-Phase Commit) · 분산 트랜잭션을 원자적으로 확정하는 전통적 합의 프로토콜, Saga가 대신하는 대상. "eventual consistency without 2PC"

← 전체 기술 노트 · 워크스페이스 인덱스 · 맨 위 ↑