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이 우선합니다.Error및InterruptedException은 절대 재시도하지 않습니다. - 시도당 타임아웃 (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)을 확인하며,retryOn과abortOn양쪽에 모두 나열된 클래스는 거부됩니다. - 오버플로에 안전한 백오프 (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>
...
- Repo: https://github.com/adrijshikhar/retry-thread-pool
- API docs: https://javadoc.io/doc/io.github.adrijshikhar/retry-thread-pool
재시도 (Retries) 기능은 작업이 실행되는 곳이라면 어디든 필요합니다. 작업이 풀 (pool)에서 실행된다면, 재시도 기능 또한 그 풀에 있어야 합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기