우아한테크코스
JPA 성능 개선기 4. saveAll()
상황 현재 스케줄을 등록할 때 기존 스케줄을 deleteAll한 뒤, 새로운 스케줄을 saveAll하는 방식을 취하고 있다. 기존에는 하루씩 일정을 등록할 수 있었으나, 사용자의 피드백을 반영하는 과정에서 다중선택 기능이 추가되었다. 따라서 여러 날의 일정을 한번에 등록할 수 있게 되었다. 문제점 여러 스케줄을 등록할 때 스케줄 하나하나마다 save 쿼리가 나가 DB에 부하를 주고 있는 상황이다. 코드 private void saveAllByCoachAndDate(Long coachId, ScheduleUpdateRequest request) { Coach coach = findCoach(coachId); List schedules = toSchedules(request, coach); scheduleR..
JPA 성능 개선기 3. 페치조인
상황 코치가 크루 한명의 면담 히스토리를 조회할 때 crew_id, reservation_status 조건에 맞는 모든 면담을 조회한다. 문제점 조회한 면담을 DTO로 변환해 필요한 정보를 응답값으로 넘겨줄 때 SELECT 쿼리가 의도치않게 발생하는 문제가 발생했다. 하나의 면담마다 SELECT 쿼리가 2개씩 추가로 발생하는 N+2 문제가 발생한 것이다. 코드 Service public List findCrewHistoryByCoach(Long crewId) { validateCrewId(crewId); List reservations = reservationRepository.findAllByCrewIdAndReservationStatus(crewId, DONE); List response = new ..
JPA 성능 개선기 2. 1+N 문제 우아하게 해결하기
상황 기존에는 크루 메인뷰에서 단순히 코치 목록을 조회했으나, 사용자 피드백을 받는 과정에서 코치가 면담가능한 일정이 등록되어 있는지 메인뷰에서 알고 싶다는 피드백이 들어왔다. 코치를 매번 클릭해서 등록된 일정이 있는지 찾는 과정이 번거로웠던 것 같다. 그래서 아래와 같이 예약가능한 코치일 경우 오른쪽 상단에 초록점을 통해 표시하도록 만들기로 했다. 문제점 코치가 한명일 때는 문제가 없었지만, 코치 수가 늘어남에 따라 coach_id에 해당하는 schedule 테이블을 조회하는 쿼리가 비례해서 나가는 상황이 발생했다. 즉, 코치수에 비례해 쿼리가 하나씩 추가로 발생하는 1+N 문제가 발생한 것이다. 코드 @Transactional(readOnly = true) public List findAll() { ..
JPQL을 사용할 때는 join 타입을 꼭 명시하자
상황 Spring Data JPA 사용시 @Query 애노테이션으로 JPQL을 사용하면서 문득 이상한 점을 발견했다. 어떤 쿼리가 나가는지 확인했을 때 내부적으로 cross join을 사용한 것이다! 평소에 inner join이나 left join 정도만 알고 있었던 터라, cross join이 무엇인지 잘 몰랐다. cross join이 무엇인지 알아보고, 이것을 어떻게 해결할 수 있는지 알아보자. 테이블 결합 방식 교차결합(Cross Join) 교차결합(Cross Join)은 카티전곱(Cartesian product)라고도 불리며 곱집합으로 계산된다. 곱집합은 미드 시리즈와 비슷하다. 시즌1부터 6까지 각 10편씩 있다고 가정하면, 시즌1-1, 시즌1-2, … 시즌 6-10 까지 총 60편(6x10)이..
JPA 성능 개선기 1. 쿼리 개수 및 시간 로깅
서론 Spring Data JPA를 사용하면 메소드 이름만으로도 쿼리문을 자동으로 만들어준다. 덕분에 쿼리문을 작성하지 않고도 빠르게 개발이 가능하다. 그러나 잘못사용할 경우 N+1 문제와 같이 쓸데없이 많은 쿼리가 날라가 성능저하가 발생할 수 있다. JPA 성능을 개선하기 전 각각의 요청마다 얼마나 많은 쿼리가 발생하는지 눈으로 확인하기 위해 쿼리 개수 및 시간을 로그로 찍어보려고 한다. JPA에서 쿼리 개수 및 시간을 측정하는 방법 StatementInspector 하이버네이트에서 제공하는 기능 중 StatementInspector이라는 것이 있다. StatementInspector를 이용하면 JPA가 자동으로 생성하는 SQL문을 중간에 조회하거나 수정할 수 있다. hibernate 공식 문서 공식 ..
Mysql 쿼리최적화
서론 쿼리최적화를 왜 해야할까? 그 이유는 바로 긴 로딩시간으로 인한 사용자 이탈을 방지하기 위해서이다. 웹 애플리케이션에서는 DB를 조회하고 저장하는 작업이 주를 이루는데, 쿼리 성능이 낮다면 자연스레 응답속도 또한 늦어질 수 밖에 없다. 로딩 속도가 사용자 경험 뿐만 아니라 회사의 매출액과도 직결된다는 내용의 기사도 있다. 참고 : 웹 로딩 속도 1초에 아마존 매출 68억달러 달렸다 문제되는 쿼리 확인 아래 SQL문을 통해 실행 시간이 긴 쿼리를 확인할 수 있다. # 실행 시간이 긴 쿼리 확인 SELECT query, exec_count, sys.format_time(avg_latency) AS "avg latency", rows_sent_avg, rows_examined_avg, last_seen ..
더미데이터 100만개를 넣기위한 여정
인덱스를 통해 쿼리 실행계획을 개선하려고 한다. 실제 서비스를 운영하는 DB라고 가정하고 실행계획을 보기 위해 더미데이터 100만개를 각 테이블에 넣어야 했다. 테이블마다 100만개의 더미데이터를 넣는 것은 쉽지 않다. 더미데이터를 생성하고 넣으면서 시도했던 방법을 정리해봤다. 실제 성공한 방법은 방법 6 이다. 방법 1 - mockaroo를 이용해서 데이터를 생성하고 삽입 랜덤 데이터를 자동으로 만들어주는 mockaroo라는 사이트가 있다. 이 사이트를 이용하면 내가 원하는 형태로 랜덤 데이터를 생성할 수 있다. 적절한 Type이 없다면 Regular expression 타입을 고르고 내가 원하는 문자열을 만들 수도 있다. 참고 : Mockaroo - regular-expressions 그러나 무료 버..
[트러블 슈팅] The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
상황 DB와 was를 연결하는 과정에서 에러가 발생했다. 서버가 DB에 패킷을 보냈으나, JDBC Driver가 DB로부터 어떠한 패킷도 받지 못했다는 에러메시지가 출력되었다. 원인 Mysql 설정 파일에서 bind-address를 제대로 설정해주지 않아 생긴 문제였다. 설정 파일은 /etc/mysql/mysql.conf.d 경로 내에 mysqld.cnf라는 이름으로 존재한다. 아래 명령어를 입력하면 리눅스 내 my.cnf 설정파일이 어디에 존재하는지 알 수 있다. mysqld --verbose --help | grep -A 1 'Default options' 해결 방법 설정파일에 들어가면 bind-address 값이 127.0.0.1로 되어 있다. 이것을 0.0.0.0 으로 변경한 뒤 mysql을 재시..