백엔드/spring

@Transactional 과 외부 시스템 연동

콤비네이션피자라지 2024. 12. 23. 01:10

 

오늘은 24년 12월 둘째주에 업무를 진행하면서 발생했던 문제 상황과 해결한 방법을 공유하려 한다.
특정 유저가 닉네임 ( DB ) 를 업데이트 했는데, 닉네임 API 를 호출하면 이전 닉네임이 응답값으로 오는 현상이 발생했다.
히스토리 상 DB 업데이트도 성공적으로 완료되었기 때문에 문제될만한 지점이 없어보였다.

 


 

문제상황 요약

간단한 닉네임 업데이트

 

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

 

 

 

닉네임 API 호출량 증가로 캐싱 12시간 적용!

 

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

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

 


 

해결 방법

 

@Transactional 과 외부 시스템 통신 코드를 하나로 묶어서 자주 발생하는 문제중에 하나인데, 이럴 경우

Transactional 동작에 맞춰서 외부 시스템 통신코드를 실행해주는게 편리하다.

 

  1. @Transactional 종료 시, 성공적으로 commit 이 발생했다면 캐시를 evict
  2. exception 이 발생했다면 캐시 유지.

 

afterCommit 이벤트 등록

Spring 에서는 현재 상태의 Transaction 에 따라 이벤트를 등록하는 방법을 제공한다.

현재 트랜잭션 상태에 있다면, afterCommit 이벤트 리스너를 등록해서, 커밋이 완료 된 후에만 캐시를 evict 하게 할 수 있다.

annotation based 로 해결하는 방법으로는 TransactionEventListener를 이용하는 방법도 있다.

 

 

깨달은 점 : @Transactional 안에서 외부 시스템을 호출 시 주의하자.