[JPA] 쓰기 지연으로 인한 서비스 오류 경험

 구체적인 상황은 다음과 같다. 

 

 Admin 페이지에서 POST api 요청이 올때마다 db에서 기존 데이터들을 모두 제거한 후에 request body로 들어온 데이터들을 저장해주는 로직을 기대하고 다음과 같은 코드를 작성하였다.

 

featureRepository.deleteAll()
sectionRepository.deleteAll()

//req로 들어온 데이터 저장
...

 

하지만 클라이언트 qa중 500 server error가 발생한다는 말을 듣고 찾아보니 다음과 같은 로그가 남겨져 있는 것을 볼 수 있었다.

 

Duplicate entry ~ for key ~

엔티티에서 unique값으로 설정해놓은 필드가 중복된다는 뜻이었다. 그런데 삽입된 데이터는 중복이 아예 없는데, 이상하다고 생각하면서 코드를 다시 보던 중 JPA 쓰기 지연과 관련된 문제일 수 있겠다는 생각이 들어 찾아보니 문제가 있었다.

 

 

알아도 까먹었으면 넌 모르는거다

 

 기존 코드는 repository에서 delete쿼리를 날리고 뒤에 insert쿼리가 날아가는 것을 의도하고 만들었는데, delete와 insert 사이에 트랜잭션이 종료(커밋)될 일이 발생하지 않으면 SQL이 바로 날아가지 않는다. jpa는 entitymanager(영속성 컨텍스트)에 저장된(영속화된) 엔티티를 기준으로 쿼리를 쓰기 버퍼에 모아놓고, 트랜잭션 커밋시 모아놓은 쿼리를 날린다. 그런데 이때 날아가는 쿼리는 순서대로 날아가는 것이 아니라, hibernate가 지정한 순서대로 날아간다. 그걸 설명한게 위의 사진이다.

 

(참고: https://eckrin.tistory.com/18)

 

[JPA] JPA 구조, 특성

- DB와 객체를 어떻게 설계하여 매핑(Mapping)할 것인가? - 내부에서 JPA는 어떻게 동작할까? 어플리케이션의 개발시 하나의 EntityManagerFactory를 통해서 고객의 요청(트랜잭션)이 올때마다 EntityManager가

eckrin.tistory.com

 

 결국 처음에 실행한 delete문 대신, 뒤에서 나온 insert문이 먼저 나와서 'unique 컬럼에 대해서 이미 존재하는 필드값이 포함된 데이터를 삽입했다'라면서 에러를 낸 것이다. 

 

featureRepository.deleteAllInBatch()
sectionRepository.deleteAllInBatch()

featureRepository.flush()
sectionRepository.flush()

println("feature:${featureRepository.findAll().size}")
println("section:${sectionRepository.findAll().size}")

 

delete문 직후 flush를 호출하여 db와 싱크를 맞춰주면, 내가 의도한 순서대로 동작하게 할 수 있다.

 

추가로 deleteAll() 같은경우 iter문을 통해서 리스트의 element마다 삭제 쿼리가 나가는데, 대신 deleteAllInBatch() 메소드를 사용하게 되면 delete * from table; 과 같이 한번에 모두 삭제하는 쿼리 하나가 나가므로 네트워크 통신상의 이득도 볼 수 있다.

 

'[ Backend ] > Spring DB, JPA' 카테고리의 다른 글

[JPA] N+1 문제  (0) 2023.07.11
[Spring DB] 스프링과 트랜잭션, 트랜잭션 전파  (0) 2023.07.02
[JPA] 연관관계 설정 고민  (0) 2023.05.07
[JPA] JDBC  (0) 2023.02.05
[QueryDsl] QueryDsl 사용해보기  (0) 2022.11.22