Spring/스프링으로 시작하는 리액티브 프로그래밍

[Reactor] Reactor Sequence 디버깅 방법

꾸준함. 2024. 7. 31. 10:40

서론

동기식 또는 명령형 프로그래밍 방식은 예외가 발생했을 때 Stacktrace를 확인하거나 예외 발생이 예상되는 코드에 브레이크 포인트를 걸어서 문제가 발생한 원인을 단계적으로 찾아가면 되기 때문에 상대적으로 디버깅 난이도가 쉽습니다.

하지만 Reactor의 경우 코드 자체는 선언형 프로그래밍 방식으로 작성하지만 작업들이 대부분 비동기적으로 실행되기 때문에 디버깅 난이도가 상대적으로 어렵습니다.

이번 게시글에서는 디버깅 난이도를 낮추기 위해 Reactor에서 제공하는 몇 가지 방법을 간단히 소개하겠습니다.

 

1. Debug 모드 사용

Reactor에서의 디버그 모드 활성화는 Hooks.onOperatorDebug()를 통해 이루어집니다.

우선 디버그 모드를 활성화하지 않을 경우 디버깅하기 어렵다는 것을 증명하기 위해 Hooks.onOperatorDebug() 라인을 주석 처리해 보겠습니다.

 

1.1 디버그 모드 활성화하지 않은 코드

 

 

 

부연 설명

  • NPE가 발생하지만 정확히 어느 라인에서 발생했는지 확인하기 어려움
  • 에러 메시지를 통해 map Operator에서 NPE가 발생한 것까지는 추정이 가능하지만 map Operator가 네 개나 있기 때문에 어떤 map Operator에서 NPE가 발생했는지 구체적으로 알기 힘듦

 

1.2 디버그 모드 활성화한 결과

  • 코드는 동일하고 Hooks.onOperatorDebug() 라인 주석을 해제함

 

 

부연 설명

  • Operator 체인상에서 에러가 발생한 지점을 정확히 가리키고 있으며 에러가 시작된 지점부터 에러 전파 상태를 친절하게 표시해 주는 것을 확인 가능
  • 이처럼 디버그 모드를 활성화할 경우 에러가 발생한 지점을 좀 더 명확하게 찾을 수 있지만 비용이 많이 드는 동작이기 때문에 처음부터 디버그 모드를 활성화하는 것은 권장하지 않음
    • 에러가 발생할 경우 캡처한 정보를 기반으로 에러가 발생한 Assembly의 Stacktrace를 원본 Stacktrace 중간에 끼워넣기 때문에 비용이 많이 듦

 

1.3 Intellij에서의 Reactor Sequence 디버깅 지원

  • Intellij에서는 개발자가 Hooks.onOperatorDebug()를 직접 추가하지 않아도 디버그 모드 활성화하는 방법을 제공
  • File > Settings > Language & Frameworks > Reactive Streams > Enable Debug Mode 활성화 > Debug method initialization method > Hooks.onOperatorDebug() 활성화

 

 

1.4 상용 환경에서의 디버깅 설정

  • Reactor에서는 애플리케이션 내 모든 Operator 체인의 Stacktrace 캡처 비용을 지불하지 않고 디버깅 정보를 추가할 수 있도록 별도 Java 에이전트 제공
 

reactor-tools/reactor-tools/src/main/java/reactor/tools/agent/ReactorDebugAgent.java at master · spring-attic/reactor-tools

A set of tools to improve Project Reactor's debugging and development experience. - spring-attic/reactor-tools

github.com

 

2. checkpoint() Operator 사용

checkpoint() Operator를 사용할 경우 특정 Operator 체인 내 Stacktrace만 캡처합니다.

checkpoint()를 이용하는 방법은 크게 세 가지입니다.

  • Traceback을 출력하는 방법
  • Traceback 출력 없이 식별자를 포함한 Description을 출력해서 에러 발생지점을 예상하는 방법
  • Traceback과 Description을 모두 출력하는 방법

 

2.1 Traceback을 출력하는 방법

  • checkpoint()를 사용하면 실제 에러가 발생한 assembly 지점 또는 에러가 전파된 assembly 지점의 traceback이 추가됨

 

 

 

부연 설명

  • ArithmeticException 예외가 직접적으로 발생한 지점인지 아니면 에러가 전파된 지점인지 알 수 없지만, map() Operator 다음에 추가한 checkpoint() 지점까지는 에러가 전파되었다는 것을 예상할 수 있음
  • 15번째 라인이 zipWith() Operator가 위치한 라인이기 때문에 zipWith() Operator에서 직접적으로 에러가 발생했음을 예상할 수 있음
    • 원본 데이터를 내보내는 just()에는 특별한 처리 로직이 없기 때문에 에러 발생 가능성이 없고 결국에는 zipWith()에서 에러가 발생해 Downstream으로 전파되었음을 확신할 수 있음

 

2.2 Traceback 출력 없이 식별자를 포함한 Description을 출력해서 에러 발생지점을 예상하는 방법

  • checkpoint(description)을 사용하면 에러 발생 시 Traceback을 생략하고 description을 통해 에러 발생 지점을 예상할 수 있음

 

 

부연 설명

  • 실행 결과를 보면 checkpoint()의 파라미터로 입력한 description이 출력된 것을 확인 가능

 

2.3 Traceback과 Description을 모두 출력하는 방법

  • checkpoint(description, forceStackTrace)를 사용하면 description과 Traceback을 모두 출력할 수 있음

 

 

3. log() Operator를 사용한 디버깅

log() Operator는 Reactor Sequence의 동작을 로그로 출력하는데, 해당 로그를 통해 디버깅이 가능합니다.

log() Operator를 사용하면 에러가 발생한 지점에 단계적으로 접근할 수 있습니다.

 

 

부연 설명

  • log() Operator를 추가한 결과 Subscriber에 전달된 결과 이외에 onSubscribe(), request(), onNext() 같은 signal이 출력됐으며 해당 시그널들은 두 번째 map() Operator에서 발생한 시그널
  • 로그를 통해 두 번째 map() Operator가 `melon`이라는 문자열을 내보내지만 두 번째 map() Operator 이후의 어떤 지점에서 `melon` 문자열을 처리하던 중 에러가 발생해 cancel() signal이 출력됐음을 확인할 수 있음
    • 따라서 세 번째 map() Operator 내부에 어떤 문제가 있는지 밝혀내면 된다는 것을 유추할 수 있음

 

참고

  • 스프링으로 시작하는 리액티브 프로그래밍 (황정식 저자)
반응형

'Spring > 스프링으로 시작하는 리액티브 프로그래밍' 카테고리의 다른 글

[Spring] Spring WebFlux 개요  (0) 2024.08.06
[Reactor] Reactor 테스트  (0) 2024.07.31
[Reactor] Context  (0) 2024.07.30
[Reactor] Scheduler  (0) 2024.07.24
[Reactor] Sinks  (0) 2024.07.23