본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 04. 04:25

retry-thread-pool: Java를 위한 재시도 기능이 있는 Executor

요약

Java의 ExecutorService를 감싸 스레드 풀 수준에서 자동 재시도를 지원하는 라이브러리입니다. 백오프 전략, 타임아웃, 리스너 기능을 제공하며 의존성 없이 비동기 처리를 유지합니다.

핵심 포인트

  • 스레드 풀 수준의 자동 재시도 기능 제공
  • 지터를 포함한 다양한 백오프 전략 지원
  • Thread.sleep 없이 비동기 처리량 유지
  • Java 17 이상 및 가상 스레드 지원
  • 런타임 의존성 없는 가벼운 라이브러리

원래 adrijshikhar.dev에서 게시되었습니다.

대부분의 재시도 (retry) 라이브러리는 **단일 호출 (one call)**을 감쌉니다. 불안정한 단일 작업에는 괜찮지만, 작업 _풀 (pool)_을 실행할 때는 재시도가 사용자의 몫이 아니라 풀 (pool)의 역할이어야 합니다.

retry-thread-pool은 재시도를 스레드 풀 (thread-pool) 수준에서 처리합니다. 어떤 ExecutorService든 감싸고, 이름을 지정한 작업을 제출하여 CompletableFuture를 받으면 재시도가 자동으로 수행됩니다. Java 17 이상, Maven Central에서 사용할 수 있으며, 런타임 의존성 (runtime dependencies)이 전혀 없습니다.

빠른 시작 (Quickstart)

RetryPolicy policy = RetryPolicy.builder()
    .maxRetries(3)
    .backoff(Backoff.exponentialWithJitter(Duration.ofMillis(100), Duration.ofSeconds(5)))
...

제공 기능

  • 백오프 (Backoff)none, fixed, exponential, exponentialWithJitter. 지터 (Jitter)는 동기화된 재시도 폭풍 (retry storms)을 방지합니다.
  • 서술어 (Predicates)retryOn(...) / abortOn(...); abortOn이 우선합니다. ErrorInterruptedException은 절대 재시도하지 않습니다.
  • 시도당 타임아웃 (Per-attempt timeout) — 중단된 시도는 인터럽트(interrupted)되어 재시도되며, 워커 (worker)를 점유한 채 멈춰 있지 않습니다.
  • 리스너 (Listeners) — 작업 코드에 손을 대지 않고도 메트릭 (metrics)이나 로그 (logs)를 남길 수 있는 onRetry / onSuccess / onExhausted / onAbort를 제공합니다.
  • 통계 (Stats) — 불변 스냅샷 (immutable snapshot): 제출됨 / 성공함 / 소진됨 / 재시도됨 / 타임아웃됨 카운트.
  • 자체 풀 사용 (Bring your own pool) — 21 버전 이상의 가상 스레드 (virtual threads)를 포함한 모든 ExecutorService를 지원합니다.
  • 명확한 소진 알림 (Loud exhaustion) — 재시도 횟수 초과 시 → RetryExhaustedException 발생 (원인 = 마지막 실패); 재시도 불가능한 에러는 해당 에러 그대로 노출됩니다.

이것이 중요한 이유

  • 실행 후 망각 (Fire and forget) — 제출 → future. 코드 내에서 catch, sleep, 시도 횟수 카운터, 재스케줄링(rescheduling)이 필요 없습니다.
  • 비동기 유지 (Async stays async) — 백오프 (backoff)는 스케줄러 타이머를 사용하며, Thread.sleep을 사용하지 않습니다. 워커 (workers)는 계속 작업하며, 의존성 (dependency)이 불안정할 때도 처리량 (throughput)을 유지합니다.
  • 독립적인 복구 (Independent healing) — 각 작업은 고유한 예산을 가집니다. 하나의 불안정한 작업이 옆에 있는 99개의 작업을 멈추게 하지 않습니다.
  • 회복 탄력성은 풀의 속성 (Resilience is a pool property) — 모든 호출 지점에 재시도 로직을 스레드(threaded) 방식으로 심는 것이 아닙니다.

관측 가능성 (Observability)

태스크 코드를 직접 계측(instrumenting)하지 않고도 풀(pool)이 무엇을 하고 있는지 확인하세요:

  • 리스너 (Listeners) — 모든 상태 전환 시 onRetry / onSuccess / onExhausted / onAbort가 실행됩니다. 이를 Micrometer, StatsD 또는 로그로 연결하세요.
  • stats() — 불변 스냅샷(immutable snapshot)을 제공합니다: 제출됨(submitted) / 성공함(succeeded) / 소진됨(exhausted) / 중단됨(aborted) / 재시도됨(retried) / 시간 초과됨(timed-out) / 거부됨(rejected) 정보와 더불어 활성(active) + 대기(queued) 카운트를 포함합니다. 대시보드나 상태 확인(health check)을 위해 이 데이터를 스크래핑(scrape)하세요.
  • 로그 (Logs)System.Logger를 통해 기존 백엔드로 라우팅됩니다. 별도의 연결 설정이 필요 없습니다.
  • 지연 시간 (Latency)TaskEvent.attemptDuration(시도당) 및 stats().totalExecutionMillis(합계)를 통해 단순 카운트뿐만 아니라 타이밍 정보도 제공합니다.
RetryExecutor executor = RetryExecutor.builder()
    .retryPolicy(policy)
    .listener(new RetryListener() {
...

생명주기 및 제어 (Lifecycle & control)

  • AutoCloseable — try-with-resources를 사용하세요. close()를 호출하면 새로운 제출을 중단하고, 반환되기 전에 진행 중인 작업과 이미 예약된 재시도 작업을 모두 처리(drain)합니다.
  • 자신이 생성한 것만 소유함 — 내부 풀(internal pool)은 스스로 종료하지만, 외부에서 전달받은 풀은 사용자가 직접 종료해야 합니다.
  • 취소 (Cancellation)future.cancel(true)는 실행 중인 시도를 중단하고 대기 중인 재시도를 취소합니다. 취소(Cancelled)는 소진(exhausted)과 다르므로, 잘못된 onExhausted 이벤트가 발생하지 않습니다.

견고성 (Robustness)

  • Fail-fast 설정 — 빌더(builder)가 build() 시점에 검증합니다: maxRetries >= 0, 양수 시간(positive durations)을 확인하며, retryOnabortOn 양쪽에 모두 나열된 클래스는 거부됩니다.
  • 오버플로에 안전한 백오프 (Overflow-safe backoff) — 지수적 지연(exponential delays)은 오버플로되는 대신 깔끔하게 상한선(cap)에 도달합니다. 지터(jitter)는 [0, delay] 범위 내의 전체 지터(full jitter)를 적용합니다.
  • 부하 상황에서도 정확함 — 스케줄러 스레드는 사용자의 코드를 실행하지 않습니다(시도 및 리스너는 워크 풀(work pool)에서 실행됨). 또한 통계(stats)는 락 프리(lock-free) 방식으로 작동합니다.

의존성 제로 (Zero dependencies)

로깅은 JDK의 System.Logger 파사드(facade, Java 9 이상)를 통해 수행됩니다. SLF4J/Log4j가 있으면 해당 경로로 라우팅되고, 없으면 조용히 처리됩니다. 아티팩트(artifact) 하나만 추가하면 되며, 그 외 다른 것은 따라오지 않습니다.

에이전트 우선 (Agent-first)

AI 에이전트가 예제만 보고도 바로 사용할 수 있도록 설계되었습니다:

  • llms.txt — 에이전트(agents)를 문서로 안내하는 디스커버리 인덱스 (discovery index).
  • docs/AI_USAGE.md — 전체 공개 인터페이스 (public surface) 및 기능별 레시피 (recipe).
  • AGENTS.md — 라이브러리를 편집하는 에이전트를 위한 빌드/테스트/컨벤션 (build/test/conventions).
  • 문서 = 테스트 컴파일 (Docs = compiling tests) — 모든 레시피는 ExamplesTest 내의 실제 테스트입니다. API를 변경하면 예제들이 컴파일되지 않아 빌드가 실패합니다. 따라서 문서가 코드와 동떨어지는 현상 (drift)이 발생할 수 없습니다.
// ExamplesTest에서 가져옴 — 매 빌드마다 컴파일 및 통과
@Test
void exhaustionSurfacesLastFailure() {
...

사용해 보기 (Try it)

<dependency>
  <groupId>io.github.adrijshikhar</groupId>
  <artifactId>retry-thread-pool</artifactId>
...

재시도 (Retries) 기능은 작업이 실행되는 곳이라면 어디든 필요합니다. 작업이 풀 (pool)에서 실행된다면, 재시도 기능 또한 그 풀에 있어야 합니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0