본문 바로가기

끄적끄적

[끄적끄적] Saga에서 Exception을 Json으로 변환하다가 발생한 에러와 해결

최근 진행하고 있는 프로젝트 Gitanimals에서는 분산된 트랜잭션 간의 데이터 정합성을 맞추기 위해 redis-stream을 이용해 Saga를 구현하고 있습니다.
 
이때, Saga 중간에 예외가 발생하면 Saga를 호출했던 클라이언트가 예외에 따라 적절한 처리를 해주기 위해서 예외 인스턴스를 직렬화해서 저장해야 했습니다. (아래 사진 참고)

Saga rollback flow

 
그런데 이때 예외가 발생해서 롤백은 잘 되었는데 클라이언트는 Timeout Exception 예외를 받은 적이 조금 있었어요. 처음에는 네트워크 이슈라 생각하고 넘겼었는데, 특정 상황에서 잦게 발생하기 시작해서 예외를 파악해 보기 시작했습니다.


setAccess 가 안된다?!

Timeout Exception이 발생한 상황은 대부분 Saga안에서 JwtException 관련 예외가 발생했을때 였습니다.
 

10초 동안 대기하다가 예외를 던지는 모습

 
예외로그를 남겨보니, objectMapper로 예외를 직렬화하는 과정 중에 아래와 같은 에러가 발생하더라고요.

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Invalid type definition for type
`io.jsonwebtoken.JwtException`: Failed to construct BeanSerializer for [simple type, class 
io.jsonwebtoken.JwtException]: (java.lang.IllegalArgumentException) Failed to call `setAccess()`
on Field 'detailMessage' (of class `java.lang.Throwable`) due to `java.lang.reflect.InaccessibleObjectException`, 
problem: Unable to make field private java.lang.String java.lang.Throwable.detailMessage accessible: 
module java.base does not "opens java.lang" to unnamed module @222114ba

 
Saga 프레임워크에서 해당 예외를 잡지 못하여 적절한 예외를 응답해주지 못한 것이 문제였습니다. (다행히 Saga결과 저장만 실패했기 때문에, 다른 롤백 플로우는 정상 동작했습니다.)
 
관련해서 알아보니 Java9부터는 보안상의 이유로 예외의 특정 필드를 직접 접근하지 못하도록 막아두었고, ObjectMapper에서는 reflect를 통해서 막아둔 field에 setAccess true를 시전 하려고 하다가 예외가 발생했던 것이었습니다. 


해결

그렇다면 이것을 어떻게 해결할 수 있을까요? 
직렬화 방식을 다른 것으로 변경하더라도 언어 레벨에서 필드의 접근을 막아버렸다면, 차이가 없을 것이라 생각해서 유연한 json을 그대로 사용하기로 결정했습니다.
또한, 예외의 모든 정보를 redis에 저장하고 원래 호출자에게 넘기는 것이 정말로 원하는 것일까를 고민해 보았는데요. 지금까지 사용하면서 느낀 것은 호출자가 원하는 것은 예외의 클래스에 따라서 핸들링하는 것이라고 생각했습니다.
 
그래서, 필요하지 않은 필드는 json 직렬화 대상이 되지 않도록 구현하기로 결정했고, 아래와 같은 설정을 ObjectMapper에 추가해서 필요하지 않은 필드들은 json 직렬화 대상에서 제외시켜 주었습니다.
 

ThrowableMixIn 추가

 


결과

결과적으로, 기존에 777,777개의 Saga를 처리하는데 2분 10초 가 걸리는 로직을, 1분 57초로 줄일 수 있게 되었습니다.

맥북 에어 m2기준
2분 10초 -> 1분 57초
TPS 5982 -> TPS 6647
11% 성능향상
before

 

after

마치며

예외를 직렬화하는 경우가 흔치 않아서 그런지 왜 직렬화가 실패했는지 원인을 찾기가 매우 힘들었는데요.
흔하지 않은 상황인 만큼 작성해두지 않으면 까먹기 쉬울 것 같다 생각해서, 작성을 해보았습니다. 🙇
 
https://github.com/devxb/Netx/commits/main/

개선PR

 
긴 글 읽어주셔서 감사합니다.