백엔드/spring
@Transactional 과 외부 시스템 연동
콤비네이션피자라지
2024. 12. 23. 01:10
오늘은 24년 12월 둘째주에 업무를 진행하면서 발생했던 문제 상황과 해결한 방법을 공유하려 한다.
특정 유저가 닉네임 ( DB ) 를 업데이트 했는데, 닉네임 API 를 호출하면 이전 닉네임이 응답값으로 오는 현상이 발생했다.
히스토리 상 DB 업데이트도 성공적으로 완료되었기 때문에 문제될만한 지점이 없어보였다.
문제상황 요약

유저는 자신의 닉네임을 자유롭게 변경할수 있다!
하지만, 우리 서비스에 닉네임을 조회하는 API 가 있었고, 서비스의 급 성장으로 인해
닉네임 API 에 캐싱을 적용하게 되었었다.

문제가 되는 코드는 아래와 같았다.

- 트랜잭션 시작
- 유저를 찾는다.
- 닉네임을 수정하고 DB 업데이트 한다.
- 닉네임 캐싱 API 를 evict 처리 한다.
- 트랜잭션이 종료 되고 update 쿼리가 수행된다.
위 상황에서 4번이 완료되면 캐시 서버에서 해당 프로파일은 삭제된다.
이 때 5번 트랜잭션이 종료되기 전에 다시 닉네임 캐싱 API 가 호출되면, 업데이트 전 프로파일 정보로 새로 캐싱하게 된다.
최종적으로 DB와 캐시 값이 일치하지 않는 상황이 발생한다.

해결 방법
@Transactional 과 외부 시스템 통신 코드를 하나로 묶어서 자주 발생하는 문제중에 하나인데, 이럴 경우
Transactional 동작에 맞춰서 외부 시스템 통신코드를 실행해주는게 편리하다.
- @Transactional 종료 시, 성공적으로 commit 이 발생했다면 캐시를 evict
- exception 이 발생했다면 캐시 유지.
afterCommit 이벤트 등록
Spring 에서는 현재 상태의 Transaction 에 따라 이벤트를 등록하는 방법을 제공한다.

현재 트랜잭션 상태에 있다면, afterCommit 이벤트 리스너를 등록해서, 커밋이 완료 된 후에만 캐시를 evict 하게 할 수 있다.
annotation based 로 해결하는 방법으로는 TransactionEventListener를 이용하는 방법도 있다.
깨달은 점 : @Transactional 안에서 외부 시스템을 호출 시 주의하자.