사전 서킷 브레이커
패턴

서킷 브레이커

gabury1고친 사람 github-actions[bot]

서킷 브레이커는 자꾸 실패하는 상대에게 한동안 요청을 보내지 않는 장치입니다. 실패가 정해 둔 만큼 쌓이면 부르는 쪽이 스스로 호출을 끊습니다. 그 뒤의 호출은 상대까지 가지 않고 바로 실패로 돌아옵니다. 시간이 좀 지나면 한 번만 보내 보고 그 결과로 다시 이을지를 정합니다.

쉽고 빠른 이해

서킷 브레이커는 「고장 난 상대를 계속 부르지 않기」를 자동으로 해 주는 장치입니다. 결제 서비스가 응답을 멈췄을 때 주문 서비스가 스스로 결제 호출을 한동안 끊는 것이 이 장치입니다.

이게 없으면 부르는 쪽이 먼저 쓰러집니다. 답 없는 상대를 기다리는 동안 요청이 쌓입니다. 쌓인 요청이 처리할 여력을 다 먹어 멀쩡한 다른 기능까지 같이 멈춥니다.

어떻게 도나.

  1. 호출이 실패할 때마다 세어 둡니다
  2. 정해 둔 만큼 쌓이면 끊습니다. 그 뒤의 호출은 상대에게 안 보내고 바로 실패를 돌려줍니다
  3. 정해 둔 시간이 지나면 한 번만 보내 보고, 성공하면 다시 잇습니다

대가가 있습니다. 끊겨 있는 동안에는 성공했을 요청까지 거절됩니다. 언제 끊고 언제 다시 이을지를 값으로 정해야 하는데, 그 값이 어긋나면 안 끊기거나 너무 자주 끊깁니다.

상세

이 절은 먼저 답이 없는 상대를 계속 부를 때 부르는 쪽에서 무엇이 무너지는지 봅니다. 그다음 브레이커가 오가는 세 상태를 그림으로 따라가고, 무엇을 실패로 셀지와 언제 끊고 언제 다시 이을지를 정하는 값들을 봅니다. 끝으로 끊겨 있는 동안 무엇을 돌려줄지, 이 장치를 두면 무엇이 나빠지는지를 짚습니다.

답이 없는 상대를 계속 부를 때

원격 호출의 실패는 두 가지 모양으로 옵니다. 하나는 곧바로 거절당하는 것이고, 다른 하나는 아무 답도 안 오는 것입니다. 뒤의 것이 다루기 까다롭습니다. 부르는 쪽은 상대가 죽었는지 늦을 뿐인지 못 가립니다. 그래서 포기할 때까지 기다립니다.

기다리는 것도 공짜가 아닙니다. 기다리는 요청 하나는 그동안 작업 스레드 하나와 연결 하나를 쥐고 있습니다. 상대가 계속 답을 안 주는 동안에도 새 요청은 들어옵니다. 쥔 채로 기다리는 요청이 쌓여 부르는 쪽의 여력을 다 먹습니다.

flowchart TD
    P["결제 서비스 · 답이 없다"]
    subgraph 주문["주문 서비스"]
        W1["대기 요청 1 · 스레드 점유"]
        W2["대기 요청 2 · 스레드 점유"]
        W3["대기 요청 3 · 스레드 점유"]
        WN["나머지 대기 요청"]
    end
    P --> 주문
    주문 --> X["주문 서비스 여력 소진 · 결제와 무관한 기능도 정지"]
    X --> Y["주문을 부르던 곳도 같이 정지"]

화살표 한 줄기가 결제의 무응답에서 시작해 주문 서비스의 여력 소진을 지나 그 위에서 주문을 부르던 곳까지 닿습니다.

한 곳의 고장이 부르는 쪽으로 번지고, 그 쪽을 부르던 곳으로 다시 번지는 것을 연쇄 장애라고 합니다. 여럿이 서로를 부르는 구조에서는 이렇게 번지는 폭이 넓습니다.

기다림의 상한을 정해 두는 타임아웃이 첫 방어선입니다. 다만 타임아웃은 한 번의 기다림에만 걸립니다. 상대가 계속 죽어 있으면 호출마다 그 상한을 새로 다 쓰므로, 요청이 많은 구간에서는 기다리는 요청이 여전히 쌓입니다.

서킷 브레이커는 그 위에 얹히는 둘째 방어선입니다. 「방금 여러 번 실패했으니 지금 보내도 실패할 것」이라고 봅니다. 그래서 보내 보지도 않고 실패를 돌려줍니다. 쓸모없는 기다림을 아예 없애는 것이 브레이커가 하는 일입니다.

닫힘 · 열림 · 반열림

브레이커는 세 상태 중 하나에 있습니다. 호출 결과에 따라 다음 상태로 옮겨 갑니다. 이렇게 정해진 상태들 사이를 규칙에 따라 오가는 구조를 상태 기계라고 부릅니다.

이름은 전기 차단기에서 왔습니다. 그래서 상태 이름이 직관과 반대입니다. 닫힘이 정상이고 열림이 끊긴 상태입니다. 회로는 닫혀 있어야 전류가 흐르기 때문입니다.

상태 호출을 어떻게 하나
닫힘 그대로 보낸다. 실패만 세어 둔다
열림 안 보낸다. 곧바로 실패를 돌려준다
반열림 한 번만 보내 본다. 결과로 다음 상태를 정한다
stateDiagram-v2
    [*] --> 닫힘
    닫힘 --> 열림: 실패가 임계를 넘었다
    열림 --> 반열림: 정해 둔 시간이 지났다
    반열림 --> 닫힘: 시험 호출이 성공했다
    반열림 --> 열림: 시험 호출이 또 실패했다

그림에서 눈여겨볼 것은 열림에서 닫힘으로 바로 가는 화살표가 없다는 점입니다. 아직 살아나지 않은 상대에게 갇혔던 요청을 한꺼번에 쏟아붓지 않으려고, 반열림을 한 칸 두고 시험 호출 하나로 먼저 확인합니다.

닫힘에서 열림으로 넘어가는 대목을 호출 쪽에서 보면 이렇습니다.

sequenceDiagram
    participant 주문 as 주문 서비스
    participant 브레이커
    participant 결제 as 결제 서비스
    Note over 주문,결제: 타임아웃은 한 번의 기다림에만 걸린다
    주문 ->> 브레이커: 결제 호출 1
    브레이커 ->> 결제: 보낸다
    결제 --x 브레이커: 타임아웃 · 실패 1
    주문 ->> 브레이커: 결제 호출 2
    브레이커 ->> 결제: 보낸다
    결제 --x 브레이커: 타임아웃 · 실패 2
    주문 ->> 브레이커: 결제 호출 3
    브레이커 ->> 결제: 보낸다
    결제 --x 브레이커: 타임아웃 · 실패 3
    Note over 브레이커: 실패 3 · 끊는다
    주문 ->> 브레이커: 결제 호출 4
    브레이커 -->> 주문: 곧바로 실패 · 결제까지 안 간다

그림의 마지막 화살표가 브레이커가 버는 것입니다. 넷째 호출은 네트워크를 건너지 않으므로 기다림이 없습니다. 부르는 쪽은 그 요청을 쥐고 있던 스레드를 바로 놓아줍니다.

실패로 세는 응답

셀 것을 잘못 고르면 멀쩡한 상대를 끊습니다. 상대가 못 버티는 것으로 볼 실패는 답이 아예 없는 것, 연결이 거부되는 것, 상대가 자기 잘못이라고 밝히는 오류입니다.

반대로 부른 쪽의 잘못은 안 셉니다. 입력값이 형식에 안 맞아 거절당한 것은 상대가 건강하다는 증거이지 앓는다는 증거가 아닙니다. 이것까지 세면 잘못된 요청 몇 개가 멀쩡한 상대를 끊어 버립니다.

실패는 상대마다 따로 셉니다. 한 서비스가 결제와 배송과 알림을 부른다면 브레이커도 셋입니다. 하나로 합치면 알림이 앓을 때 결제 호출까지 같이 끊깁니다. 이렇게 고장의 영향 범위를 칸막이로 나눠 두는 것을 벌크헤드라고 부릅니다.

flowchart TD
    subgraph 주문["주문 서비스"]
        B1["결제용 브레이커 · 닫힘"]
        B2["배송용 브레이커 · 닫힘"]
        B3["알림용 브레이커 · 열림"]
    end
    B1 --> P["결제 서비스"]
    B2 --> D["배송 서비스"]
    B3 -.-> N["알림 서비스 · 앓는 중"]

알림용 하나만 열려 있어 알림 호출만 멈춥니다. 결제와 배송은 그대로 흐릅니다.

언제 끊고 언제 다시 이을지 정하는 값

브레이커는 네 값으로 조율합니다. 어느 것도 정답이 없습니다. 그 상대를 부르는 빈도와 상대가 회복하는 데 걸리는 시간이 값을 정합니다.

정하는 값 무엇을 정하나 작게 잡으면 크게 잡으면
실패 임계 몇 번, 또는 몇 퍼센트가 실패해야 끊나 잠깐 흔들려도 끊긴다 상대가 앓아도 오래 안 끊긴다
최소 표본 판단에 필요한 최소 호출 수 호출 두세 번으로 끊긴다 뜸한 호출에서는 영영 안 끊긴다
끊어 두는 시간 열림에서 반열림까지 얼마나 회복 못 한 상대를 자꾸 찌른다 살아난 뒤에도 한참 못 쓴다
시험 호출 수 반열림에서 몇 개를 보내 보나 어쩌다 한 번 성공에 속는다 갓 살아난 상대가 다시 눕는다

표의 둘째 줄이 자주 빠지는 값입니다. 임계를 비율로 잡으면 호출 수가 적을 때 비율이 심하게 요동칩니다. 두 번 부르고 한 번 실패한 것을 절반 실패로 읽으면, 어쩌다 난 오류 하나로 브레이커가 열립니다.

끊어 두는 시간은 상대가 회복하는 데 실제로 걸리는 시간에 맞춰야 합니다. 상대가 다시 뜨는 데 시간이 걸린다면 그보다 짧게 잡은 재시험은 전부 헛걸음입니다. 그 헛걸음이 갓 일어서는 상대를 다시 밀칩니다.

끊겨 있는 동안의 대체 응답

열려 있는 동안에도 부르는 쪽은 자기를 부른 쪽에 무언가를 돌려줘야 합니다. 이때 내놓을 대체 응답을 폴백이라고 합니다.

가장 단순한 폴백은 폴백이 없는 것입니다. 곧바로 오류를 돌려주고 끝냅니다. 아무것도 안 해 주는 것처럼 보이지만 기다림이 사라진다는 점에서 이미 이득입니다. 응답이 십 초 뒤에 실패로 오는 것과 지금 실패로 오는 것은 부르는 쪽에 전혀 다른 일입니다.

더 나은 폴백은 기능을 줄여서라도 답을 만드는 것입니다. 추천 목록을 못 불러오면 인기 상품을 대신 보여줍니다. 최근에 받아 둔 값이 있으면 조금 낡았더라도 그것을 씁니다. 이렇게 일부 기능을 접고 나머지를 살리는 것을 우아한 저하라고 부릅니다.

폴백을 고를 때 물을 것은 하나입니다. 틀린 답을 주는 것보다 실패하는 편이 나은 호출인지를 봅니다. 잔액이나 재고처럼 낡은 값이 곧 사고가 되는 데이터라면, 대신 답하지 말고 실패를 그대로 올려야 합니다.

쓰면 나빠지는 것

끊겨 있는 동안에는 성공했을 요청까지 거절됩니다. 상대가 완전히 죽은 것이 아니라 일부만 앓고 있었다면, 그동안 살아 있던 부분으로 갈 요청도 함께 막힌 것입니다.

상대가 언제 살아났는지도 늦게 압니다. 브레이커는 끊어 두는 시간이 끝나기 전에는 확인하러 가지 않으므로, 상대가 곧바로 회복해도 그 시간만큼은 끊긴 채로 있습니다.

같은 서비스를 서버 여러 대로 굴리면 판단이 갈립니다. 서버마다 자기가 본 실패만 세므로, 어떤 서버는 끊어 놓고 어떤 서버는 계속 보냅니다. 그래서 같은 요청이 어느 서버로 갔느냐에 따라 성공하기도 하고 실패하기도 합니다.

다시 잇는 순간도 조심할 대목입니다. 서버들이 비슷한 때에 끊었다면 시험 호출도 비슷한 때에 나갑니다. 갓 일어선 상대에게 호출이 한꺼번에 몰리는 이 모양을 썬더링 허드라고 부릅니다. 서버마다 끊어 두는 시간을 조금씩 어긋나게 해서 흩어 놓습니다.

flowchart TD
    subgraph 주문["주문 서비스 · 같은 서비스의 서버 셋"]
        S1["서버 1 · 열림"]
        S2["서버 2 · 열림"]
        S3["서버 3 · 닫힘"]
    end
    S3 -->|"그냥 보낸다"| T["결제 서비스"]
    S1 -.->|"시험 호출"| T
    S2 -.->|"시험 호출"| T

닫힌 서버 하나만 호출을 보내는 동안 열린 둘은 아무것도 안 보냅니다. 끊어 두는 시간이 끝나면 점선 둘이 함께 살아나 시험 호출이 한곳으로 몰립니다.

장애를 볼 때 봐야 할 것이 하나 늘어난다는 점도 대가입니다. 호출이 실패했을 때 상대가 안 받은 것인지 브레이커가 안 보낸 것인지를 가려야 합니다. 그래서 브레이커를 두면 상태가 바뀔 때마다 기록을 남깁니다. 지금 어느 상태인지도 밖에서 볼 수 있게 해 둡니다.

쓰는 호출과 안 쓰는 호출

네트워크를 건너는 호출에 씁니다. 상대가 따로 굴러가는 마이크로서비스이든 바깥 회사의 서비스이든, 내가 못 고치는 쪽이 앓을 때 내 여력까지 같이 빨려 들어가는 구조면 브레이커가 맞습니다.

사람이 화면 앞에서 기다리는 경로일수록 차이가 큽니다. 화면이 한참 멈춰 있다가 오류를 보여주는 것과 곧바로 줄어든 화면을 보여주는 것은 다른 서비스입니다.

같은 프로세스 안의 함수 호출에는 안 씁니다. 네트워크를 안 건너므로 기다림이 없습니다. 실패해도 남의 스레드를 묶지 않습니다. 끊어서 아낄 것이 없습니다.

한 번 돌고 끝나는 배치 작업에도 안 맞습니다. 브레이커는 호출이 이어지는 동안 쌓인 실패로 판단하는데, 호출이 몇 번 없으면 판단이 설 만큼 쌓이지 않습니다. 그런 곳은 재시도와 기다림 간격을 늘리는 것으로 충분합니다.

관련 항목

서킷 브레이커가 막으려는 장애

연쇄 장애 · 재시도 폭풍 · 썬더링 허드 · 부분 실패 · 단일 장애점 · 스레드 고갈 · 커넥션 고갈 · 회색 장애

같은 호출에 함께 거는 다른 보호 장치

타임아웃 · 재시도 · 지수 백오프 · 지터 · 벌크헤드 · 속도 제한 · 스로틀링 · 부하 차단 · 백프레셔 · 재시도 예산

끊겨 있는 동안 응답을 대신 만드는 수단

폴백 · 우아한 저하 · 캐싱 · stale-if-error · 읽기 전용 모드

서킷 브레이커가 기대는 동작 개념

상태 기계 · 헬스 체크 · 멱등성 · 가용성 · 회복 탄력성 · 결함 격리

상대가 앓는지 살아났는지 재는 지표

오류율 · 꼬리 지연 · 초당 요청 수 · 골든 시그널 · 서비스 수준 목표 · 오류 예산

브레이커가 자주 놓이는 시스템 구조

마이크로서비스 · 서비스 메시 · 사이드카 · 리버스 프록시 · 로드 밸런서 · 서비스 디스커버리

다른 이름: circuit breaker · Circuit Breaker · 서킷브레이커 · 회로 차단기 · 서킷 브레이커 패턴