새소식

스프링

직접 재시도 로직 구현했는데 알고 보니 이미 있는 리얼 허거덩거덩스한 상황

  • -

 

안녕하세요.

 

리얼 허거덩거덩스한 상황인데요

 

문제는 그렇게 시작됐습니다.

 

저희 서비스 "BigPicture"는 매일 자동으로 여러 경제 지표와 뉴스를 크롤링하여 매주 경제 리포트를 생성해주는 서비스인데요

 

크롤링 시 모종의 이유로 실패해서 데이터를 수집하지 못한다면 큰일나겠죠~?

 

사실 또 저희가 크롤링 후 변환 로직에 LLM과 베타버전인 자바용 랭체인을 사용해서 예상치 못한 오류가 엄청 많았습니다.

 

그래서 생각해낸게...

 

크롤링 로직을 재시도 로직으로 감싸서 실행하자!

 

 

 

시작합니다

 

우선 파라미터로 메인 로직을 () -> ~~~ 로 넘겨줘야 할 것 같은데, 뭘 써야할까요?

 

찾아보니 Supplier라는 인터페이스가 있었습니다.

 

이런걸 함수형 인터페이스라 부르는데 대표적으로

 

Predicate<T>

  • 입력값을 하나 받아서 boolean 값을 반환
  • boolean test(T t)

Supplier<T>

  • 입력값 없이 값을 반환
  • T get()

Consumer<T>

  • 입력값을 받아서 처리하지만, 반환값이 없음
  • void accept(T t)

Function<T, R>

  • 입력값을 받아서 처리하지만, 반환값이 없음
  • R apply(T t)
  •  

이렇게 있었습니다.

 

저는 메인 로직들이 공통적으로 입력값을 받지 않고 CrawlingResultDto를 반환하고 있었기 때문에 Supplier를 사용하기로 했습니다.

 

@Builder
@AllArgsConstructor
@Getter
public class CrawlingResultDto {

    private Boolean result;
    private String message;

    public static CrawlingResultDto of(Boolean result, String message) {
        return CrawlingResultDto.builder()
                .result(result)
                .message(message)
                .build();
    }
}

 

CrawlingResultDto는 각 크롤링 결과를 받아옵니다

 

result는 성공 여부, 그리고 message는 어떤 작업을 완료했는지를 반환해줍니다

 

자 이제 재시도 로직입니다.

 

@Slf4j
public class RetryExecutor {

    private final static int RETRY_COUNT = 3;

    public static void execute(Supplier<CrawlingResultDto> supplier, String taskName) {
        String message = "";
        for (int i = 0; i < RETRY_COUNT; i++) {
            try {
                CrawlingResultDto result = supplier.get();
                message = result.getMessage();
                if (result.getResult()) {
                    log.info("{}: {}", taskName, message);
                    return;
                }
            } catch (Exception e) {
                log.warn("{}: {}번째 시도 중 예외 발생 - {}", taskName, i + 1, e.getMessage());
            }
        }
    }
}

 

일단 최대 3번 실행되게 했구요, 로직 실행 시 예외는 try-catch로 잡고 크롤링 결과(result.getResult)가 true면 바로 리턴시켜줬습니다.

 

실제 사용 시

 

public void exchangeCrawling() {
    RetryExecutor.execute(() -> exchangeCrawlingService.crawling(), "ExchangeCrawlingService");
}

 

이런 식으로 사용됩니다.

 

어... 근데 찾아보니 spring-retry 라는게 있네요?

 

 

GitHub - spring-projects/spring-retry

Contribute to spring-projects/spring-retry development by creating an account on GitHub.

github.com

 

흠...

 

@Retryable(
        retryFor = Exception.class,
        maxAttempts = RETRY_COUNT
)
public void exchangeCrawling() {
    exchangeCrawlingService.crawling();
}

 

음 쉽게 해결됐네요

 

그 물론 단순 예외 기준 재시도로는 @Retryable이 좋은 것 같긴 한데, 실패 시점에 후처리나 반환값 그리고 로깅 등의 비표준 요구사항이 있으면 제가 구현한 RetryExecutor를 쓰는게 나을 것 같습니다.

 

둘 다 뭐 좋다는거죠~

 

에... 오늘은 성공이라 하긴 좀 애매하고

 

일단 해결

 

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.