Spring boot

DB call을 줄여보자

지금 quiz api 하나를 호출하면 많은 쿼리가 호출된다.

거기다가 bookmark여부를 같이 달라는 api 수정 요청이 들어왔다.
bookmark table까지 참고하게되면 여기서 더 많은 쿼리가 호출될 것이다.

이대로는 안되겠다고 생각해서 개선할 수 있는 부분을 정리해보았다.

1. JWT filter

JWT token에 대해 알아보니 세션 대신 토큰을 사용하면 stateless하게 검증할 수 있다고 이해했다.
즉 DB에 접근할 필요 없이 검증할 수 있어야 하는데, 우리 JWT filter에서는 토큰에서 유저 정보를 뽑아 우리 db에 있는 유저인지 확인하고 role정보를 가져와서 세션에 저장하는 과정이 포함되어있었다.

모든 api는 filter를 거치기 때문에 여기서 db접근을 하면 모든 api마다 1회의 쿼리가 추가되는 것이다.
그래서 db를 거치지 않도록 고쳐보았다.
먼저 oauth 로그인을 마치면 succeshandler에서 토큰을 발행하는데 토큰에 role를 미리 넣어줬다.
그 후 jwtfilter에서 db접근을 없애고 대신 토큰의 이메일과 role을 가져와 세션에 넣었다.
모든 api에서 추가적으로 1회 발생하던 db접근을 없앨 수 있었다.
https://velog.io/@cham/JWT-JWT-%EA%B3%A0%EC%B0%B0-SecurityJWT
이 글에서 내 생각이 맞을 수 있겠다는 용기를 얻었다ㅎ

2. JPA n+1문제

사실 이게 가장 큰 원인이다.

우리 서비스에서는 문제 하나당 여러개의 기대키워드가 매핑된다. 각 키워드 번호에 따른 키워드 값을 가져오기 위해 여러번의 쿼리가 실행된다.

바로 이부분!

사실 이거 말고도 '문제'를 가져오는 api의 경우 참고하는 테이블이 5개 이상이기 때문에 fetch join을 사용하려했었다.
그런데 OneToMany 관계의 테이블이 2개 이상이면 fetch join을 사용할 경우 MultipleBagFetchException 이 발생한다고 한다.

우리 문제 테이블에는 question_tag, related_keyword, bookmark 총 세 개의 OneToMany관계를 가지고 있기 때문에 사용할 수 없다.

이럴 경우 세 가지 해결 방안이 있다고 한다.

  • 자식 테이블 하나에만 Fetch Join을 걸고 나머진 Lazy Loading로
  • 모든 자식 테이블을 다 Lazy Loading으로 (fetch join 사용안함)
  • hibernate batch size 설정

 

세 번째 방법을 사용하려 한다.
hibernate의 batch size를 설정해두면 지정된 수만큼 in절에 부모 Key를 사용하여 쿼리 수를 줄여준다.

application.yml의 jpa: properties: hibernate: default_batch_fetch_size: 1000 를 설정해주면 1000개 이하의 키워드는 하나의 쿼리로 불러올 수 있게 된다.
jenkins로 자동화를 해뒀다고 해도 application.yml파일은 테섭에 올라가지 않기 때문에 따로 설정해줘야한다.

3. 테이블 수정

기획이 바뀌면서 question과 tag의 관계가 M:N에서 1:N으로 바뀌었다.
문제당 여러개의 태그가 매핑될 수 있었는데 이제는 문제당 딱 하나의 태그만 매핑하게되었다.

문제는 이전에는 M:N관계였기 때문에 테이블을 하나 더 사용하고 있다는 것이다.
question을 조회하면 question_tag, tag 테이블까지 두 번을 조회하게 된다.
question_tag 테이블을 삭제하고 question에 tag 컬럼을 추가하려한다.

 

참고: https://jojoldu.tistory.com/165