관리 메뉴

JIE0025

한 트랜잭션에서 동일한 데이터를 조회하면? (MyBatis의 캐싱전략과 localCacheScope 설정하기) 본문

백엔드/이슈, 트러블슈팅

한 트랜잭션에서 동일한 데이터를 조회하면? (MyBatis의 캐싱전략과 localCacheScope 설정하기)

Kangjieun11 2024. 4. 16. 21:46
728x90

 

✅  가정

 

 

학생이 존재한다.

학생은 학년별로 성적표를 출력할 수 있다.

학년당 2학기가 존재하고, 성적표 또한 학기별로 출력된다.

 

위의 이미지는 2학년 성적표를 조회하는 상황이다. 

1학기, 2학기 성적표를 둘다 체크하여 한번에 요청하면

한번에 성적표 2개가 보인다. 

 

* 1학기 성적표 한개만 체크하면 성적표 1개만 보인다.

 

✅  상황

한번에 2학년 1학기, 2학기를  성적표를 조회하는 과정은 한 트랜잭션에서 일어난다.

 

💻 수도코드

성적표 객체 생성

출력해야하는 개수만큼 반복
    <-- 성적표 제작을 위한 코드 시작-->
    학생을 조회한다.
    학생의 개인정보가 존재한다면 복호화한다.
    
    해당 학기에 무슨 과목을 들었는지 리스트 생성
    Database에서 학생의 성적데이터를 가져오는 함수 호출
    
    성적표 객체에 필요한 데이터 삽입
    <-- 생략 -->

성적표 객체 반환

 

 

 

학생을 조회한다 = 학생 DB에서 데이터를 가져온다는 의미이다.

학생을 가져온 이후 개인정보에 대해 복호화를 진행한다.

 

 

위의 2 코드 모두

1,2학기 성적표에서 같은 동작을 할것이다.

 

1명의 1-2학기 성적을 조회했기 때문에

가져오는 학생의 정보도 동일하다.

 

 

✅  문제

 

문제는 2학기 성적표를 제작하는 상황에서 발생한다. 

 

 

1학기 성적표를 제작하고,

2번째 성적표를 제작하려는 상황에서 복호화코드에서 에러가 났다.

 

확인해보니, 2번째 성적표의 회원조회코드에서,

집주소(암호화되어있어야하는 정보) 가 이미 복호화된 정보로 들어오는것이었다.

 

 

 

✅ 사고과정

왜 2번째 성적표의 회원조회시 

복호화된 정보가 들어오는것일까?

DB에는 암호화된 정보가 저장되어있고, 이것을 업데이트하는 쿼리문은 존재하지 않는다. 

아직 1개의 요청에 대해 트랜잭션이 끝나지 않았기도하다.

 

 

그렇다는것은 효율성을 위해

어떤 공간에 조회한 값을 저장하고, (데이터 복호화를 하더라도 이곳에서 변경하고)

 

똑같은 조회가 발생했을때

해당 데이터를 가져온다는 이야기가 된다. 

 

 

 

 

JPA의 1차 캐싱 전략과 매우 유사한 상황이라고 느껴졌다.

 

DB는 외부 저장공간으로, 요청을 너무 많이하면 성능이 떨어지기 때문에 

 

JPA가 자체적으로 1차 캐시를 사용하여, 

DB에서 가져온 데이터, 엔티티의 값이 변경되면, 

캐시에 해당 값을 변경하고, 나중에 한번에 DB로 업데이트 하는 전략...

 

 

...

 

MyBatis를 사용하고 있던 나는 
MyBatis의 캐싱전략에 대해 알아보게 되었다. 

 

 

 

✅ 해결

MyBatis에도 내장 캐시가 있다. 

MyBatis도 다른 ORM프레임워크처럼 캐싱 전략이 존재한다. 

  • Local Cache (1차 레벨 캐시)
  • 2차레벨 캐시 (보조캐시)

 

 

⏺  Local Cache 

로컬캐시는 1차 레벨의 캐시로,

별도의 설정을 하지 않아도 언제나 켜져있는 캐시이다.

 

 

💻 Local Cache의 적용범위(Scope)를 설정할 수 있다.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
	PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
	"http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
	<settings>
		<setting name="localCacheScope" value="STATEMENT" />
	</settings>
</configuration>

 

위에 MyBatis를 설정하는 XML파일 내부에

<setting name="localCacheScope" value="STATEMENT" />

해당 코드를 작성해, STATEMENT 혹은 SESSION으로 설정하면 된다. 

 

1️⃣ SESSION

Local Cache의 Default값이다.

 

캐시 정보는   <세션이 유지되는 동안> 살아있는데,

 

- 트랜잭션이 끝나거나 (Commit or Rollback)

- INSERT, UPDATE, DELETE로 DB에 FLUSH 가 실행될 경우 (DB에 데이터가 변경됨)

Cache 정보는 폐기된다.

 

*** 같은 데이터 조회시  (파라미터까지 똑같은 경우를 의미)

캐시에 있는 정보를 가져와서 사용하므로

조회 성능이 좋아진다.  ***

 

🤔 아까의 문제상황을 다시 생각해보자.

 

1개의 요청동안 2개의 성적표를 제작, 
트랜잭션은 1개의 요청동안 유지된다.

1학기 성적표를 제작하면서               
DB에서 멤버를 조회하고
멤버 데이터가 캐시에 저장
사용한다.(복호화 코드 동작)

2학기 성적표를 제작하면서 , 같은 데이터의 조회이므로   
캐시에서 멤버를 조회하고
사용한다. (복호화 코드 동작 실패)

이미 복호화 되어있는 데이터라 복호화 실패 발생 

 

 

 

2️⃣ STATEMENT

그래서 우리는 Cache Scope를 STATEMENT로 바꿔주어야한다. 

 

Mapper에 정의된 액션 하나 (SQL 쿼리문 하나하나)를 STATEMENT로 추측한다고 한다

(출처 : https://12bme.tistory.com/364)

 

 

 

이전에는 같은 트랜잭션에서 같은 조회시에 사용되는 쿼리문이 같기 떄문에 캐시를 사용했었지만

 

Cache Scope를 STATEMENT로 바꿔주면 

같은 데이터를 똑같이 DB에서 가져오게 된다. 

 

>> DB에는 암호화된 데이터가 존재하기 때문에

이렇게 되면  복호화코드에서 에러가 발생하지 않는다. 

 

 

효율성 면에선 떨어지지만, 내가 수정할 수 없는 코드였고,
local에서만 발생하는 문제였었다.

따라서 Local의 MyBatis 설정파일 자체를 변경하는것이 
가장 좋은 선택이라고 판단했다. 

 

 

 


 

References

https://codingdreamtree.tistory.com/92

 

MyBatis 의도치 않은 캐싱

1. 발견 실무에서 복잡한 비즈니스를 다루는 로직에서 도저히 이해가 안가는 상황이 나오게 되었다. 상황은 이렇다. 첫 번째로 조회한 객체에는 객체 내부에 컬렉션을 가지고 있는데, 다음 메서

codingdreamtree.tistory.com

https://12bme.tistory.com/364

 

[오픈소스] MyBatis 내장 cache에 대해서

MyBatis에는 2가지 내장 Cache가 존재합니다. local session cache, second level cache 두가지 입니다. local session cache는 임의로 켜거나 끌 수 없고, 무조건 활성화됩니다. 반면 second level cache는 mapper namespace 단

12bme.tistory.com