최근 진행하고 있는 프로젝트 Gitanimals에서는 분산된 트랜잭션 간의 데이터 정합성을 맞추기 위해 redis-stream을 이용해 Saga를 구현하고 있습니다.
이때, Saga 중간에 예외가 발생하면 Saga를 호출했던 클라이언트가 예외에 따라 적절한 처리를 해주기 위해서 예외 인스턴스를 직렬화해서 저장해야 했습니다. (아래 사진 참고)
그런데 이때 예외가 발생해서 롤백은 잘 되었는데 클라이언트는 Timeout Exception 예외를 받은 적이 조금 있었어요. 처음에는 네트워크 이슈라 생각하고 넘겼었는데, 특정 상황에서 잦게 발생하기 시작해서 예외를 파악해 보기 시작했습니다.
setAccess 가 안된다?!
Timeout Exception이 발생한 상황은 대부분 Saga안에서 JwtException 관련 예외가 발생했을때 였습니다.
예외로그를 남겨보니, 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 직렬화 대상에서 제외시켜 주었습니다.
결과
결과적으로, 기존에 777,777개의 Saga를 처리하는데 2분 10초 가 걸리는 로직을, 1분 57초로 줄일 수 있게 되었습니다.
맥북 에어 m2기준
2분 10초 -> 1분 57초
TPS 5982 -> TPS 6647
11% 성능향상
마치며
예외를 직렬화하는 경우가 흔치 않아서 그런지 왜 직렬화가 실패했는지 원인을 찾기가 매우 힘들었는데요.
흔하지 않은 상황인 만큼 작성해두지 않으면 까먹기 쉬울 것 같다 생각해서, 작성을 해보았습니다. 🙇
https://github.com/devxb/Netx/commits/main/
긴 글 읽어주셔서 감사합니다.
'끄적끄적' 카테고리의 다른 글
[끄적끄적] supervisorScope를 주의해서 사용하자 (0) | 2025.01.19 |
---|---|
[끄적끄적] 코루틴 실패전이 트러블 슈팅 (feat. 구조화된 동시성) (1) | 2025.01.05 |
[끄적끄적] 44만 SAGA 를 처리하며 얻은 인사이트 (1) | 2024.10.27 |
[끄적끄적] 효율적인 키 분배 및 리밸런싱 방식 (1) | 2024.06.16 |
[끄적끄적] 주문 Saga Isolation 부족 해결하기 (1) | 2024.05.02 |