주뇽's 저장소

[Troubleshooting] Spring Boot와 JPA에서 캐시로 인해 발생한 데이터 불일치 문제 해결하기 본문

웹개발/SpringBoot

[Troubleshooting] Spring Boot와 JPA에서 캐시로 인해 발생한 데이터 불일치 문제 해결하기

뎁쭌 2024. 5. 14. 11:55
728x90
반응형

최근에 Spring Boot와 JPA를 사용하여 프로젝트를 진행하던 중, 유저의 총 좋아요 개수를 조회하는 과정에서 데이터 불일치 문제가 발생했다. 이 글에서는 해당 문제의 원인과 해결 방법에 대해 자세히 설명한다.

 

문제 상황:

  • UserAccount 엔티티와 Post 엔티티가 일대다 관계로 매핑되어 있다.
  • UserAccountRepository에서 @EntityGraph 어노테이션을 사용하여 posts 연관 관계를 함께 로드하는 findWithPostsByNickname 메서드를 정의했다.
  • 유저의 총 좋아요 개수를 조회할 때, findWithPostsByNickname 메서드를 사용하여 유저 정보와 게시글 목록을 함께 조회한다.
  • 로그를 확인해보니 이전에 조회한 유저의 닉네임이 계속 출력되는 문제가 발생했다.

문제 원인:

findWithPostsByNickname 메서드에서 @EntityGraph 어노테이션을 사용하여 posts 연관 관계를 함께 로드하면, 해당 결과가 영속성 컨텍스트에 캐시된다. 이후에 동일한 닉네임으로 다시 조회할 때, 데이터베이스에서 새로 조회하는 대신 캐시된 결과를 반환하게 된다. 이로 인해 로그에서는 이전 닉네임이 출력되는 문제가 발생한 것이다.

 

해결 방법 1: @Transactional(readOnly = true) 어노테이션을 사용한다

가장 추천하는 방법은 @Transactional 어노테이션의 readOnly 속성을 true로 설정하는 것이다.

@Transactional(readOnly = true)
public ResponseEntity<Integer> getLike(String userNickname) {
    // ...
}

readOnly를 true로 설정하면 해당 메서드 내에서 읽기 전용 트랜잭션이 적용된다. 읽기 전용 트랜잭션에서는 영속성 컨텍스트의 캐시를 사용하지 않고 항상 데이터베이스에서 최신 데이터를 조회하게 된다. 따라서 매 요청마다 데이터베이스에서 새로운 결과를 가져오므로 캐시로 인한 문제를 방지할 수 있다.

 

해결 방법 2: JPQL을 사용하여 명시적으로 LEFT JOIN FETCH를 수행한다

다른 방법으로는 JPQL을 사용하여 명시적으로 LEFT JOIN FETCH를 수행하는 것이다.

@Query("SELECT u FROM UserAccount u LEFT JOIN FETCH u.posts WHERE u.nickname = :nickname")
Optional<UserAccount> findWithPostsByNickname(@Param("nickname") String nickname);

 

JPQL을 사용하여 LEFT JOIN FETCH를 명시적으로 수행하면 캐시를 우회하고 항상 데이터베이스에서 조회하도록 할 수 있다. 이 방법은 쿼리의 유연성이 높아지는 장점이 있지만, 쿼리 작성의 복잡성이 증가할 수 있다.

추가 고려사항:

캐시로 인한 데이터 불일치 문제를 해결할 때, 다음과 같은 사항을 추가로 고려할 수 있다.

  1. 캐시 설정 조정:
    • spring.jpa.properties.hibernate.cache.use_second_level_cache 속성을 false로 설정하여 2차 캐시를 비활성화할 수 있다.
    • spring.jpa.properties.hibernate.cache.use_query_cache 속성을 false로 설정하여 쿼리 캐시를 비활성화할 수 있다.
  2. 캐시 무효화:
    • 데이터 변경 작업이 발생한 경우, 해당 데이터와 관련된 캐시를 명시적으로 무효화할 수 있다.
    • @CacheEvict 어노테이션을 사용하여 특정 캐시 엔트리를 제거할 수 있다.
  3. 캐시 범위 제한:
    • @Cacheable 어노테이션의 condition 속성을 사용하여 캐시 적용 조건을 지정할 수 있다.
    • 특정 조건에 해당하는 경우에만 캐시를 사용하도록 제한할 수 있다.

마무리: Spring Boot와 JPA에서 캐시로 인해 발생한 데이터 불일치 문제를 해결하는 방법에 대해 알아보았다. @Transactional(readOnly = true) 어노테이션을 사용하거나 JPQL을 사용하여 명시적으로 LEFT JOIN FETCH를 수행하는 것이 효과적인 해결책이 될 수 있다. 추가로 캐시 설정 조정, 캐시 무효화, 캐시 범위 제한 등의 방법을 고려하여 캐시 관련 문제를 해결할 수 있다.