Resilience4j CircuitBreaker 슬라이딩 윈도우 동작 원리(COUNT_BASED vs TIME_BASED)

2026. 5. 14. 14:37·dev/Spring

서킷브레이커를 도입하고 나서 슬라이딩 윈도우 타입에 대한 설정값들이 헷갈렸다.
COUNT_BASED의 경우 사실 직관적이지만 TIME_BASED의 경우 딱 와닿지 않는 것 같다.

Resilience4j를 기준으로 두 가지 윈도우 타입의 내부 동작 원리를 자세히 살펴보고, 어떤 상황에서 무엇을 골라야 하는지 정리해보려고 한다.


슬라이딩 윈도우란?

서킷브레이커는 세 가지 상태를 가진다.

  • CLOSED: 정상 상태 (모든 요청이 통과한다.)
  • OPEN: 차단 상태 (요청을 즉시 실패시킨다.)
  • HALF_OPEN: 시험 상태 (일부 요청만 허용해서 회복 여부를 판단한다.)

이때 CLOSED 상태에서 OPEN으로 전환할지를 결정하는 핵심 매커니즘이 바로 슬라이딩 윈도우다.

윈도우는 두 가지 일을 한다.

  1. 기록: 매 호출의 결과(성공/실패/느림)를 저장한다.
  2. 집계: 윈도우 안의 호출들로 실패율(failureRate)과 느린 호출 비율(slowCallRate)을 계산해서 임계값과 비교한다.

집계는 minimumNumberOfCalls (평가를 시작하기 위한 최소 호출 수)를 충족시킬 때만 시작한다.
예를 들어서 slidingWindowSize: 100, minimumNumberOfCalls: 20인 경우 20번의 호출부터 failureRateThreshold를 계산한다.

 

슬라이딩 윈도우 타입에는 두 가지 방식이 있다: COUNT_BASED, TIME_BASED.


COUNT_BASED

내부 동작

slidingWindowSize=N으로 설정하면 내부적으로 길이 N의 원형 배열(circular array)이 생성된다.

각 슬롯에는 한 건의 호출 결과(성공/실패, 소요시간, 느림 여부)가 저장된다.

 

[호출1] [호출2] [호출3] ... [호출N]
   ↑
 index

동작 방식

호출이 들어올 때마다 인덱스가 (index + 1) % N으로 회전하며 슬롯을 덮어쓴다.
N+1번째 호출은 1번째 호출이 있던 자리를 차지하고, 결과적으로 항상 가장 최근 N개의 호출만 메모리에 남는다.

size=5 일 때

호출 1~5:  [F][S][S][F][F]   index=0 → 5
호출 6:    [S][S][S][F][F]   호출1 자리에 호출6 덮어씀
호출 7:    [S][F][S][F][F]   호출2 자리에 호출7 덮어씀

실패율은 논리적으로는 윈도우 전체 호출 기준으로 계산되지만, Resilience4j는 매번 전체 배열을 순회하지 않고 aggregate counter를 증감시키는 방식으로 최적화한다.

단점: 시간 개념이 없다

이 방식의 가장 큰 단점은 시간 개념이 전혀 없다는 것이다.
즉 같은 설정값이라도 트래픽 양에 따라 윈도우가 커버하는 시간이 완전히 달라진다.

slidingWindowSize가 100인 경우:

  • 초당 1000건의 트래픽 → 0.1초를 커버
  • 시간당 10건의 트래픽 → 10시간을 커버

즉, 시간과 상관없이 해당 CircuitBreakerStateMachine은 계속 카운트 집계를 들고 있어야 한다.

트래픽이 일정하고 충분히 많은 서비스나 메모리 사용량을 정확히 예측해야 하는 환경에 적합할 것 같다.


TIME_BASED

내부 동작

slidingWindowSize=N으로 설정하면 이건 N초를 의미하며, 내부적으로는 초 단위 partial aggregation bucket을 circular array 형태로 유지한다.

 

각 버킷은 1초 단위로 다음 값들을 집계한다.

  • 총 호출 수
  • 실패 수
  • 느린 호출 수
  • 누적 응답 시간

개별 호출은 저장하지 않고, 1초 단위로 카운터만 누적한다 (이게 COUNT_BASED와 가장 큰 차이).

slidingWindowSize=60 일 때 (61개 버킷)

[0초][1초][2초]...[59초]
  현재 →

동작 방식

호출이 들어오면 현재 시각 기준 버킷을 찾아 그 버킷의 카운터를 증가시킨다.
1초가 흐르면 가장 오래된 버킷을 0으로 리셋하고 새 버킷으로 사용한다 (N+1 슬롯인 이유).

실패율 계산은 현재 시각 기준 윈도우 안의 모든 버킷 카운터를 합산하고, 윈도우 바깥으로 빠진 버킷은 자동으로 제외한다.

트래픽이 끊기면 자동으로 비워진다

COUNT_BASED와 결정적으로 다른 차이점은 트래픽이 끊기면 자동으로 비워진다는 점이다.
호출이 없으면 시간이 흐를수록 버킷들이 차례로 리셋된다.
N초 동안 호출이 한 건도 없으면 모든 버킷이 0이 되고, minimumNumberOfCalls를 만족하지 못해 평가 자체가 일어나지 않는다.

실패율이 누적되어 그대로 살아있는 COUNT_BASED와 결정적으로 다른 지점이다.

하지만 윈도우가 비워진다고 해서 OPEN이 자동으로 풀리는 건 아니다.
슬라이딩 윈도우는 CLOSED → OPEN 상태 전환에만 개입하기 때문에, 이미 OPEN 상태가 된 서킷브레이커는 waitDurationInOpenState가 지나야 HALF_OPEN으로 바뀌고, permittedNumberOfCallsInHalfOpenState 결과로 CLOSED 전환이 결정된다.
즉 윈도우가 비워진다는 건 상태 변경 카운트가 리셋된다는 의미일 뿐, 서킷의 상태가 변하는 건 아니다.

메모리 효율

COUNT_BASED size=1000  → 최근 1000개 호출에 대한 결과 슬롯 유지
TIME_BASED  size=60    → 버킷 61개만 저장 (그 60초간 호출이 100만 건이어도)

트래픽이 시간대별로 다르거나 (야간/새벽 트래픽이 거의 없는 시간대) 있는 환경에 적절한 방법이다.


시나리오로 보는 차이

설정: failureRateThreshold=50%, minimumNumberOfCalls=10

시나리오: 12:00~12:05 사이에 10건의 호출이 모두 실패한 뒤, 12:30까지 호출이 한 건도 없다가 12:30에 새 호출이 들어옴.

COUNT_BASED (size=10)

12:30 시점 윈도우 상태: [F][F][F][F][F][F][F][F][F][F]
실패율: 100%
→ minimumNumberOfCalls(10) 충족, 임계값(50%) 초과
→ 새 호출이 들어오는 순간 OPEN으로 전환

30분 전 장애가 현재 판정에 그대로 영향을 준다.

TIME_BASED (size=60)

12:06 시점: 12:05 이전 버킷들이 모두 윈도우 밖으로 빠짐
12:30 시점 윈도우 상태: 모든 버킷이 0
호출 수: 0건
→ minimumNumberOfCalls(10) 미달
→ 평가 자체가 일어나지 않음, CLOSED 유지

핵심 비교표

항목 COUNT_BASED TIME_BASED
slidingWindowSize의 의미 호출 개수 (N건) 시간 (N초)
내부 자료구조 길이 N의 원형 배열 길이 N+1의 1초 단위 버킷
저장 단위 개별 호출 결과 1초당 집계값
메모리 사용량 호출 수에 비례 윈도우 시간에 비례 (호출 수 무관)
트래픽이 끊기면 옛 데이터가 계속 유지됨 시간이 흐르면 자동으로 비워짐
시간 개념 없음 있음
적합한 환경 트래픽이 일정하고 많음 트래픽 변동이 크거나 적음

운영 트래픽에 맞춰 동적 변경

운영 환경에서는 시간대별 트래픽 패턴에 따라 CircuitBreaker 설정을 다르게 가져가고 싶어질 때가 있을 것 같다.

예를 들어 저녁 피크 시간에는 짧은 윈도우로 빠르게 장애를 감지하고, 새벽 시간(트래픽이 낮은 시간)에는 너무 민감하게 OPEN 되지 않도록 더 긴 윈도우를 사용하고 싶은 경우

 

다만 런타임에 윈도우 타입(COUNT_BASED ↔ TIME_BASED) 자체를 변경하는 건 사실상 어렵다. (내부 자료구조가 다르기 때문)

 

  • COUNT_BASED → 최근 N개 호출 기반 circular array
  • TIME_BASED → 초 단위 partial aggregation bucket

반면 같은 타입을 유지한 채로 설정값은 운영 중 동적으로 조정할 수 있다.

  • slidingWindowSize
  • minimumNumberOfCalls
  • failureRateThreshold
CircuitBreakerConfig peakConfig =
    CircuitBreakerConfig.custom()
        .slidingWindowType(SlidingWindowType.TIME_BASED)
        .slidingWindowSize(30)
        .minimumNumberOfCalls(20)
        .failureRateThreshold(50)
        .build();

CircuitBreaker newCircuitBreaker =
    CircuitBreaker.of("ceph-api", peakConfig);

circuitBreakerRegistry.replace(
    "ceph-api",
    newCircuitBreaker
);

 

@Scheduled(cron = "0 0 18 * * *")  // 매일 저녁 6시
public void switchToPeakConfig() {
    circuitBreakerRegistry.replace("ceph-api", buildPeakCircuitBreaker());
}

@Scheduled(cron = "0 0 2 * * *")   // 매일 새벽 2시
public void switchToOffPeakConfig() {
    circuitBreakerRegistry.replace("ceph-api", buildOffPeakCircuitBreaker());
}

 

멀티 인스턴스 환경에서는 각 애플리케이션 인스턴스가 독립적인 CircuitBreaker 상태를 가지기 때문에, 설정 교체 역시 각 인스턴스별로 개별 적용된다.


마무리

정리하자면, 트래픽이 24시간 안정적이라면 COUNT_BASED도 괜찮지만, 시간대별 편차가 있는 서비스라면 거의 무조건 TIME_BASED를 선택하는 게 안전하다.
옛날 장애 흔적이 오늘까지 따라오는 사고를 만들지 않으려면 말이다.

 

 

저작자표시 (새창열림)

'dev > Spring' 카테고리의 다른 글

테스트 더블 정리 (Dummy, Fake, Stub, Spy, Mock)  (0) 2026.05.26
[SpringMVC] 요청 매핑, API 요청 매핑  (0) 2022.11.23
스프링 컨테이너와 스프링 빈  (1) 2022.11.05
[Spring] @ResponseBody 어노테이션  (0) 2022.06.07
[Spring] Spring의 컨텍스트?  (0) 2021.07.12
'dev/Spring' 카테고리의 다른 글
  • 테스트 더블 정리 (Dummy, Fake, Stub, Spy, Mock)
  • [SpringMVC] 요청 매핑, API 요청 매핑
  • 스프링 컨테이너와 스프링 빈
  • [Spring] @ResponseBody 어노테이션
:j
:j
ddongjunn@gmail.com
  • :j
    dev.j
    :j
  • 전체
    오늘
    어제
    • :j
      • dev
        • Ceph
        • CS
        • Spring
        • k8s
        • Java
        • JPA
        • Web
        • CCNA
        • 코딩테스트
        • JavaScript
        • XML
        • JSON
        • CSS
        • html
        • jQuery
        • Mssql
        • Oracle
      • 회고
      • :j story
  • 블로그 메뉴

    • 홈
    • 태그
    • github
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    Resilience4J
    오버로딩
    class
    오버라이딩
    CustomContainer
    HAVING
    MSSQL
    항해플러스
    다형성
    id
    ceph
    항해99
    <br>
    group by
    Queue
    appendChild
    멤버변수
    지역변수
    Name
    항해백앤드
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
:j
Resilience4j CircuitBreaker 슬라이딩 윈도우 동작 원리(COUNT_BASED vs TIME_BASED)
상단으로

티스토리툴바