GoRetryKit은 네트워크 호출, 외부 API 통신, 일시적 장애 대응 등에서 실패 시 자동 재시도를 제공하는 최소 의존성 Go 라이브러리입니다. 400여 줄의 순수 Go 코드로 구성되어 복잡한 외부 패키지 없이도 신뢰성 있는 재시도 로직을 구현할 수 있습니다.
핵심 설계 철학
재시도는 단순 반복이 아니라 전략 기반의 오류 회복 과정입니다. GoRetryKit은 지수 백오프, 임계값 기반 중단, 컨텍스트 기반 타임아웃, 오류 필터링 등을 내장하여 개발자가 비즈니스 로직에 집중할 수 있도록 합니다. 특히 context.Context와의 완전한 호환성을 통해 애플리케이션 전체의 생명주기 제어를 지원합니다.
사용 사례
- 외부 REST API 호출 시 연결 거부 또는 5xx 응답 발생 시 재시도
- Kafka 또는 RabbitMQ 메시지 전송 실패 후 임시 네트워크 두절 대응
- 분산 캐시(Redis/Memcached) 접근 시 타임아웃 또는 클러스터 리밸런싱 중 일시적 장애 처리
- DB 트랜잭션 중
SQLSTATE 40001(serialization failure) 같은 재시도 가능한 오류 대응
기능 요약
- ✅ 컨텍스트 연동:
context.WithTimeout,context.WithCancel와 자연스럽게 통합 - ✅ 백오프 정책: 고정 간격, 지수 증가, Jitter 적용 가능
- ✅ 오류 분류: 특정 에러 타입만 재시도하거나, 특정 에러는 즉시 전파하도록 설정 가능
- ✅ 결과 추출: 반환 값과 오류를 동시에 캡처하는
DoWithResult함수 제공 - ✅ 테스트 친화적: 인터페이스 기반 설계로 모킹 및 단위 테스트 용이
설치 및 기본 사용법
go get -u github.com/yourorg/goretrykit
간단한 재시도 예제:
package main
import (
"context"
"fmt"
"net/http"
"time"
"github.com/yourorg/goretrykit"
)
func fetchResource(ctx context.Context) error {
resp, err := http.DefaultClient.Do(http.NewRequestWithContext(ctx, "GET", "https://api.example.com/data", nil))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
return fmt.Errorf("HTTP %d", resp.StatusCode)
}
return nil
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
err := goretrykit.Do(
ctx,
func() error { return fetchResource(ctx) },
goretrykit.WithMaxAttempts(4),
goretrykit.WithJitterBackoff(500*time.Millisecond, 2.0, 0.2),
goretrykit.WithShouldRetry(func(err error) bool {
return !errors.Is(err, context.DeadlineExceeded)
}),
)
if err != nil {
fmt.Printf("최종 실패: %v\n", err)
return
}
fmt.Println("성공적으로 데이터 가져옴")
}
고급 활용: 결과 반환 및 조건부 재시도
다음은 JSON 응답을 파싱하고, 특정 상태 코드나 유효성 검사 실패 시만 재시도하는 예제입니다:
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
func getUser(ctx context.Context) (User, error) {
var u User
resp, err := http.DefaultClient.Get("https://api.example.com/user/123")
if err != nil {
return u, err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusNotFound {
return u, goretrykit.PermanentError(fmt.Errorf("user not found"))
}
if resp.StatusCode != http.StatusOK {
return u, fmt.Errorf("unexpected status: %d", resp.StatusCode)
}
if err := json.NewDecoder(resp.Body).Decode(&u); err != nil {
return u, err
}
return u, nil
}
// 실행
user, err := goretrykit.DoWithResult(
ctx,
getUser,
goretrykit.WithMaxAttempts(3),
goretrykit.WithFixedDelay(1 * time.Second),
)