🗣️ 배경
해당 주제는 현재 진행하고 있는 프로젝트에 새로운 feature를 도입하다 맞이한 문제이다.
우리 프로젝트 팀원들은 기존 구글 폼지를 사용하여 맞이했던 여러 가지 문제점들을 해결하기 위해 웹사이트 내에서 직접 사용자가 작성할 수 있는 폼지 서비스를 제공하고자 하였다.
폼지 서비스 중 폼지 답변에 대한 통계를 조회하는 기능이 있었다.
해당 기능에서 고민하지 않고 생각나는 대로 그대로 개발한 무능한 코드를 볼 수 있었다.
우리는 Form테이블과 1 : N 관계를 맺고 있는 FormField 테이블을 생성하였는데, 아래는 FormField의 DDL문이다.
아래는 FormField와 1: N 관계를 맺고 있는 FormAnswer 테이블의 DDL 문이다.
🤫 문제 상황
아래의 사진은 폼지 통계를 조회하는 API 중 FormField 테이블과 관련된 명세이다.
위 5개의 요구사항 필드 중 id, question, fieldType, section 이 4개는 단순히 FormField 테이블의 컬럼을 단순히 조회하는 작업이었다.
요구사항 필드 중 폼지 질문의 답변 총 개수를 나타내는 count는 테이블의 컬럼을 조회하는 것이 아닌 쿼리의 사용이 필요했다. 하지만, 사용하는 쿼리도 단순히 JPA에서 제공해주는 쿼리를 사용하면 되었다.
그 결과는 아래와 같다.
위 코드는 요구사항을 보자마자 "아 이렇게 구현하면 되겠다" 해서 작성한 코드이다.
이 코드는 FormField의 모든 요소를 순회하며
각 FormField와 연관된 FromAnswer 엔터티의 수를 구하고 있다.
위 코드의 문제는 뭘까?
위 코드는 현재 formField마다 formAnswer의 count 쿼리를 실행하고 있다.
그러므로 FormField의 개수만큼 쿼리가 나갈 것이다.
아래 이미지는 해당 API의 FormField 통계 부분에서 발생하는 쿼리이다.
사진 상으로는 4개의 쿼리가 발생하는 것만 보이지만, 현재 테스트 데이터 개수로 총 20개의 FormField를 생성해 놓았고 결과적으로 총 20개의 쿼리가 나가는 것을 확인할 수 있었다.
이것은 큰 문제이다.
질문의 개수가 20개라 가정했을 때, 1번 통계 조회API를 호출한다면 해당 부분에서만 20번의 쿼리가 나가고, 20번 통계 조회API를 호출한다면 400번의 쿼리가 나갈 것이다.
이 문제를 인지한 후 고민한 결과 쿼리를 통해 해결할 수 있었다.
🌟 해결 방법
FormAnswer의 테이블과 FormField의 테이블을 JOIN하여 해결할 수 있었다.
쿼리는 아래와 같다.
@Query(value = """
SELECT f.id AS id, f.question AS question, f.field_type AS type, f.section AS section, COUNT(fa.id) AS count
FROM (
SELECT *
FROM form_field field
WHERE field.form_id = :formId
) AS f
LEFT JOIN form_answer AS fa
ON fa.field_id = f.id
GROUP BY f.id
ORDER BY f.id
""", nativeQuery = true)
List<FieldListInfo> findFieldWithAnswerCountByFormId(@Param("formId") Long formId);
가장 먼저, 조회하고 싶은 컬럼 값을 작성하였다.
요구사항에 명시되어 있던 FormField의 id, question, type, section을 포함하고, 집계함수인 COUNT()를 활용하여 질문에 대한 답변 개수를 찾을 수 있었다.
다음으로 모든 FormField와 FormAnswer를 JOIN한 뒤 값을 계산하여 파라미터로 받은 formId에 해당하는 WHERE조건문을 실행하는 비용은 크다고 생각하였다.
그렇기 때문에 서브 쿼리를 활용하여 해당 formId에 해당하는 FormField를 먼저 조회한후 JOIIN을 실행하였다.
마지막으로 FormField의 id로 GROUP BY 명령어를 실행시켜 SQL문을 완성시켰다.
이 결과 FormField의 개수만큼 쿼리를 실행했던 이전과 달리 FormField의 개수와 상관없이 단 1번의 쿼리로 해결할 수 있었다.
아래의 사진은 통계 전체조회 API 실행 후 FormField 부분 쿼리 결과이다.
👐 성능 비교
가장 중요한 성능 비교이다.
성능을 비교한 데이터는 아래와 같다.
FormField 20개,
FormField당 FormAnswer 10개(총200개)
그리 많지 않은 숫자이다.
결과는 다음과 같았다.
FormField의 개수만큼 쿼리가 나갈 경우 : 518ms
JOIN을 활용하여 단 1번의 쿼리만 나가는 경우 : 378ms
소규모의 개수지만 140ms의 성능 차이가 났다. FormField의 개수가 늘어날수록 더욱 많은 성능 차이가 있을 것이라 예상한다.
하지만!!!! 중요!
나의 경우에는 JOIN을 통해 성능을 개선할 수 있었다.
하지만, 무조건 JOIN을 활용하여 쿼리 개수를 줄이는 것이 올바른 선택일까?
역시나 무조건이라는 것은 없는 것 같다.
위와 관련하여 아래 링크는 우아한 테크코스 5기분 중 한분이 작성한 글이다. 읽어보길 바란다!!
https://tecoble.techcourse.co.kr/post/2023-10-09-join-query-vs-multiple-quries/
'DB' 카테고리의 다른 글
트랜잭션의 전파 방식 (0) | 2023.10.24 |
---|