LabHub
Get started
배우기 러닝패스 코스

Building an EAI Middleware Layer

Rejecting Fast Is the Kind Thing to Do

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

한 줄 요약

느린 대상 하나가 허브 전체를 끌어내리는 것을 연쇄 장애라고 한다. 막는 방법은 두 가지를 겹치는 것이다 — 죽은 대상은 부르지 않고 바로 거절하고(서킷 브레이커), 살아 있지만 느린 대상에는 동시에 보낼 수 있는 양을 대상별로 묶는다(벌크헤드). 둘 다 "기다리게 하느니 빨리 거절한다" 는 같은 원리, 곧 백프레셔다.

왜 이게 필요했나

계정계가 멈췄다고 하자. 허브는 요청마다 계정계에 연결하고 응답을 기다린다. 4모듈의 타임아웃이 3초라면, 초당 100건이 들어오는 동안 허브에는 기다리는 스레드가 300개 쌓인다. 스레드마다 메모리와 소켓을 쥐고 있으니 허브가 먼저 숨이 막히고, 계정계와 아무 상관없는 보험 청구 거래까지 허브 앞에서 줄을 선다. 계정계 하나의 장애가 허브를 거쳐 모든 거래의 장애가 된다.

타임아웃만으로는 부족하다. 타임아웃은 "한 건을 얼마나 기다릴까" 에 답할 뿐, "죽은 줄 뻔히 아는 대상을 계속 부를 것인가" 에는 답하지 않는다. 그리고 죽어 가는 대상에 요청을 계속 쏟아부으면 그 대상이 회복할 기회도 빼앗는다.

어떻게 동작하나

서킷 브레이커. 마틴 파울러의 글이 정리한 대로, 보호할 호출을 감싸는 객체가 실패를 세다가 기준에 닿으면 회로를 연다. 열린 동안은 호출하지 않고 즉시 오류를 돌려준다. 파울러는 이 패턴이 마이클 나이가드의 책 『Release It!』 으로 널리 알려졌다고 적는다. 상태는 셋이다.

상태 호출 전이
CLOSED 한다 연속 실패가 기준에 닿으면 → OPEN
OPEN 안 한다(즉시 E904) 재시도 시간이 지나면 → HALF_OPEN
HALF_OPEN 시험 호출 한 건만 성공 → CLOSED, 실패 → OPEN(시간을 처음부터 다시)

반열림(HALF_OPEN)이 핵심이다. 회로를 영원히 열어 두면 대상이 회복해도 모르고, 시간이 지났다고 한꺼번에 다 닫으면 막 살아난 대상에 밀린 요청이 쏟아져 다시 죽인다. 한 건으로 떠보고, 그 한 건이 성공하면 닫는다. 여러 스레드가 동시에 물어도 시험 호출은 한 건이어야 하므로 상태 변경은 잠금 안에서 한다.

무엇을 실패로 셀까. 잔액 부족(B201)은 계정계가 건강하게 답한 것이다. 이것을 실패로 세면, 월말에 잔액 부족이 몰리는 것만으로 멀쩡한 계정계로 가는 길을 스스로 끊는다. 실패는 대상이 처리하지 못했다고 말한 것(E500), 답이 없던 것(E901), 연결조차 안 된 것(E902) — 시스템 오류만 센다.

벌크헤드. 배의 격벽처럼, 한 구획에 물이 차도 다른 구획은 마른 채로 두는 설계다. 대상마다 동시 처리 한도(세마포어)를 따로 둔다. 계정계 몫이 3이면 계정계로 가는 요청은 동시에 3건까지만 들어가고, 넷째부터는 기다리지 않고 과부하(E905)로 거절한다. 청구 몫은 따로 있으니 계정계가 느려도 청구 거래는 자기 몫으로 빠르게 지나간다. 한도를 넘은 요청을 줄 세워 기다리게 하면 결국 스레드가 쌓여 처음 문제로 돌아간다 — 빨리 거절하면 호출자(채널)가 재시도·대체 경로·안내 문구 중에서 고를 수 있다.

서킷과 재시도. 재시도는 일시적인 실패를 넘기는 데 쓰고, 서킷은 지속적인 실패에서 호출을 멈추는 데 쓴다. 재시도하는 쪽이 서킷의 거절(E904)까지 재시도하면 서킷이 열린 의미가 없다. 그래서 거절 코드를 따로 두고, 호출자는 E904·E905 에 즉시 재시도하지 않는다.

숫자는 어떻게 정하나. 연속 실패 기준이 너무 낮으면 순간적인 흔들림에도 회로가 열리고, 너무 높으면 죽은 대상을 오래 부른다. 재시도 시간은 대상이 보통 회복하는 데 걸리는 시간보다 짧으면 반열림 시험이 계속 실패만 하고, 너무 길면 회복한 뒤에도 한참 거절한다. 동시 처리 한도는 대상이 감당하는 동시성(연결 풀 크기 등)을 넘지 않게 잡는다. 정답인 숫자는 없고, 대상의 평소 지표를 보고 정한 뒤 전이 기록을 보며 고친다.

상태 전이를 기록한다. 서킷이 열렸다는 사실은 장애 신호다. 전이(CLOSED→OPEN, OPEN→HALF_OPEN, HALF_OPEN→CLOSED)를 시각과 대상과 함께 남기면, "계정계가 14:02 에 열려서 14:05 에 닫혔다" 가 한 줄로 보인다. 운영에서는 이 전이를 알림으로 연결한다.

현장에서 만나는 모습

가장 흔한 실수는 서킷을 하나만 두는 것이다. 허브 전체에 브레이커 하나를 걸면 계정계 장애로 청구·카드 거래까지 E904 를 받는다 — 격리가 아니라 전면 차단이다. 두 번째는 동시 처리 한도를 스레드 풀 하나로만 거는 것이다. 풀 전체가 대상 하나에 잠기면 결과는 벌크헤드가 없는 것과 같다. 세 번째는 업무 오류를 실패로 세는 서킷이다. 네 번째는 반열림 없이 "30초 뒤 자동으로 닫기" 로 만든 것 — 닫히는 순간 밀린 요청이 한꺼번에 쏟아진다. 마지막으로, 서킷이 열렸다는 기록이 없으면 운영자는 "E904 가 왜 나와요?" 라는 문의를 받고서야 안다.

다음 실습에서 할 것

breaker.py 에 상태 기계를 만들고, 채점기가 가짜 시계로 시간을 돌려 전이를 확인한다. 그다음 두 대상(계정계·청구)에 중계하는 뼈대(relay_base.py)에 붙인다 — 죽은 대상에 즉시 E904, 동시 한도를 넘으면 E905, 대상별로 나눠 격리, 상태 전이 기록, 업무 오류는 실패로 세지 않기. 채점기는 계정계 픽스처의 호출 수와 동시 처리 최댓값으로 확인한다.