-
Notifications
You must be signed in to change notification settings - Fork 17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
여행 생성 및 여행 상세 조회 쿼리 개선 #651
Conversation
Co-authored-by: mcodnjs <[email protected]>
Co-authored-by: hgo641 <[email protected]>
Co-authored-by: hgo641 <[email protected]>
Co-authored-by: hgo641 <[email protected]>
Co-authored-by: hgo641 <[email protected]>
Co-authored-by: hgo641 <[email protected]>
📝 Jacoco Test Coverage
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
수고하셧습니다. 홍디오
@@ -60,4 +61,37 @@ public static TripExpenseResponse of( | |||
dayLogExpenseResponses | |||
); | |||
} | |||
|
|||
public static TripExpenseResponse of2( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이거 사용이 안되고 있는것 같습니다.
@@ -0,0 +1,45 @@ | |||
package hanglog.global.detector; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
쿼리 카운터 이 pr에서 넣기로 한건가요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i want...
이제 각자 쿼리 개선할텐데, 그때마다 N+1 detector 코드 복사해오고, push할 때 다시 없애는 일이 너무 번거로울 것 같아서요! 희망사항이긴합니다
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (!message.contains("prometheus")) { | ||
logger.info(message); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오 ㅋㅋㅋ 👍 👍 👍 👍 👍
} | ||
|
||
@Override | ||
protected void afterRequest(@NonNull final HttpServletRequest request, @NonNull final String message) { | ||
logger.info(message); | ||
// logger.info(message); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
주석 처리하지말고 그냥 지우는건 어떤가요?
@@ -47,7 +49,7 @@ public class DayLog extends BaseEntity { | |||
|
|||
@OneToMany(mappedBy = "dayLog", cascade = REMOVE, orphanRemoval = true) | |||
@OrderBy(value = "ordinal ASC") | |||
private List<Item> items = new ArrayList<>(); | |||
private Set<Item> items = new HashSet<>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private Set<Item> items = new HashSet<>(); | |
private Set<Item> items = new LinkedHashSet<>(); |
아이템 순서도 보장하게 LinkedHashSet을 사용하는건 어떤가요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@OrderBy
가 붙여져있으면 내부적으로 LinkedHashSet으로 처리해줘서 그냥 두었는데 명시적으로 LinkedHashSet으로 할당하는 것도 좋겠군여
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
순서는 @OrderBy
로 보장하고 있어서
굳이 HashSet보다 성능이 떨어지는(물론 둘 다 O(1)이긴 하지만) LinkedHashSet로 바꿀 필요까지는 없다는 생각입니다.!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
내부적으로 LinkedHashSet으로 처리한다면, 어떤 부분에서 성능이 다른가용 ?? 갖고 있을 때 메모리를 더 잡아먹는다 ??
저는 명시적으로 + 혹시 모를 버그 예방을 위해 LinkedHashSet에 한 표 하겠습니다 ㅎㅎ
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@OrderBy
를 쓰면 HashSet으로 생성을 해도 순서가 보장이 되는지 몰랐네요.
저도 홍고와 라온 말처럼 명시적으로 혹은 혹시 모를 버그를 방지하기 위한 LinkedHashSet으로 할당하는 것에 한표 하겠습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
성능 차이는 일반 List와 LinkedList의 차이와 같습니다.!
어떤 자료구조를 사용하냐인데 이건 답글로 일일이 달 필요까지는 없다고 생각합니다.(저희 ArrayList LinkedList 미니 미션도 했으니까요..!)
그리고 명시적으로 라는 부분도 잘 모르곘는게,
Trip의 DayLogs라면 이해가 됩니다. 저희가 추가 삭제 할 때마다 순서에 맞게 기타 DayLog의 실제 위치를 변경했으니까요.!
그런데 DayLog의 Items는 다르다고 생각합니다.
저희가 아이템 위치 바꿀 때 기존에 존재하는 아이템들 사이에 끼워 넣나요?
그냥 order 필드만 바꾸고 있습니다..! 따라서 시간복잡도는 그냥 O(1)이죠
애초에 저희가 순서 변경을 할 때 컬랙션 내에서 item의 위치를 바꾸는게 아닌데
왜 LinkedHashSet으로 바꾸는게 명시적인 효과가 있는건지 잘 모르겠다는 의견입니다..!
그으으으런데 홍고의 말대로 @OrderBy
가 붙여져있을 때
HashSet으로 선언해놔도 알아서 LinkedHashSet 형태로 저장되어 있다면
LinkedHashSet으로 바꿔도 좋을 것 같습니다. (근데 진짜 그렇게 바뀌나요? 그러면 코드 단에서 밑줄로 잡아줘야 하는거 아닌가)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
진짜 길었던 오류픽스,,
private static final List<String> JDBC_QUERY_METHOD = | ||
List.of("executeQuery", "execute", "executeUpdate"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
줄바꿈..뭔가 묘하게 불편한데..List.of를 붙이고 안에 내용을 한줄에 하나씩으로 띄우는거 어떨까요
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public void loggingAfterApiFinish() { | ||
final LoggingForm loggingForm = getCurrentLoggingForm(); | ||
|
||
ServletRequestAttributes attributes = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
얜 final 아니에용?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} | ||
|
||
private MapSqlParameterSource getDayLogToSqlParameterSource(final DayLog dayLog) { | ||
LocalDateTime now = LocalDateTime.now(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
final?
} | ||
|
||
private MapSqlParameterSource getTripCityToSqlParameterSource(final City city, final Long tripId) { | ||
LocalDateTime now = LocalDateTime.now(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
final??
trip.getDayLogs() | ||
.removeIf(dayLog -> dayLog.getOrdinal() >= requestPeriod + 1 && dayLog.getOrdinal() <= currentPeriod); | ||
trip.getDayLogs().stream() | ||
.filter(dayLog -> dayLog.getOrdinal() >= requestPeriod + 1 && dayLog.getOrdinal() <= currentPeriod) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
filter 안에 부분 메소드로 분리하는건 과할까요? 한눈에 안들어와서
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
f812570 이렇게 바꿔봤는데 어떤가요..!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
만-족
QueryCounter 부분은 이전에 올렸던 PR로 이관하겠습니다.! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
고생하셨습니다 ~ 커멘트 한번 확인해주십셔 ~~
LEFT JOIN FETCH place.category place_category | ||
WHERE dayLogs.trip.id = :tripId | ||
""") | ||
Optional<Trip> findById(@Param("tripId") Long tripId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
파이눨
softly.assertThat(trip.getDayLogs()).containsExactly(dayLog1, dayLog2, dayLog3, extraDayLog); | ||
softly.assertThat(trip.getDayLogs()) | ||
.usingRecursiveComparison() | ||
.ignoringCollectionOrder() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ignoringCollectionOrder()
이 메서드가 쓰인 이유는 HashSet을 사용해서이고,
실제로는 @OrderBy
로 주입해줘서 정상적으로 동작하는데, 모킹테스트라서 이렇게 쓰인게 맞나요 ??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵 맞습니닷
final List<City> cities = cityRepository.findCitiesByIds(tripCreateRequest.getCityIds()); | ||
if (cities.size() != tripCreateRequest.getCityIds().size()) { | ||
throw new BadRequestException(NOT_FOUND_CITY_ID); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
@@ -47,7 +49,7 @@ public class DayLog extends BaseEntity { | |||
|
|||
@OneToMany(mappedBy = "dayLog", cascade = REMOVE, orphanRemoval = true) | |||
@OrderBy(value = "ordinal ASC") | |||
private List<Item> items = new ArrayList<>(); | |||
private Set<Item> items = new HashSet<>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
내부적으로 LinkedHashSet으로 처리한다면, 어떤 부분에서 성능이 다른가용 ?? 갖고 있을 때 메모리를 더 잡아먹는다 ??
저는 명시적으로 + 혹시 모를 버그 예방을 위해 LinkedHashSet에 한 표 하겠습니다 ㅎㅎ
entityManager.flush(); | ||
entityManager.clear(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 부분 이 부분 설명해주실 수 있나용 ? 궁금해요 대면으로 설명해주셔도 괜찬항여
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DataJpaTest
를 사용했을 때 해당 시점에 자식 객체를 가져오지 못합니다.
https://stackoverflow.com/questions/33587221/spring-data-jpa-test-returns-null-list-even-after-child-is-saved
그래서 사실 TripId에 해당되는 Trip을 조회한다.
테스트도 가져온 Trip의 DayLogs size가 0인데
ignoringFields("id", "dayLogs")
가 되어있어서 통과할 수 있었던 거구요.!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
HashSet은 순서 보장이 된다면 저는 어느 것이든 상관 없어서 괜찮을 것 같습니다.
* refactor: queryCounter 적용 Co-authored-by: mcodnjs <[email protected]> * refactor: prometheus 관련 로그 출력되지 않도록 변경 * feat: DayLog saveAll 메서드 구현 Co-authored-by: hgo641 <[email protected]> * feat: tripCity saveAll 메서드 구현 Co-authored-by: hgo641 <[email protected]> * refactor: trip 검증 로직 변경 Co-authored-by: hgo641 <[email protected]> * refactor: fetch join으로 trip의 모든 정보 조회 최적화 Co-authored-by: hgo641 <[email protected]> * refactor: TripServiceTest 수정 Co-authored-by: hgo641 <[email protected]> * feat: addDayLog, addItem 메서드 생성 * test: ignoringCollectionOrder 추가 * rafactor: Left join 적용 및 테스트 리팩터링 * test: flush & clear 통한 JpaTest 오류 해결 * refactor: 컨벤션 및 사용하지 않는 메서드 제거 * refactor: 새롭게 수정된 일정을 벗어난 DayLog 삭제하는 로직 메서드 분리 * refactor: QueryCounter 제거 * refactor: 매개변수 final 추가 --------- Co-authored-by: mcodnjs <[email protected]> Co-authored-by: hgo641 <[email protected]>
* refactor: queryCounter 적용 Co-authored-by: mcodnjs <[email protected]> * refactor: prometheus 관련 로그 출력되지 않도록 변경 * feat: DayLog saveAll 메서드 구현 Co-authored-by: hgo641 <[email protected]> * feat: tripCity saveAll 메서드 구현 Co-authored-by: hgo641 <[email protected]> * refactor: trip 검증 로직 변경 Co-authored-by: hgo641 <[email protected]> * refactor: fetch join으로 trip의 모든 정보 조회 최적화 Co-authored-by: hgo641 <[email protected]> * refactor: TripServiceTest 수정 Co-authored-by: hgo641 <[email protected]> * feat: addDayLog, addItem 메서드 생성 * test: ignoringCollectionOrder 추가 * rafactor: Left join 적용 및 테스트 리팩터링 * test: flush & clear 통한 JpaTest 오류 해결 * refactor: 컨벤션 및 사용하지 않는 메서드 제거 * refactor: 새롭게 수정된 일정을 벗어난 DayLog 삭제하는 로직 메서드 분리 * refactor: QueryCounter 제거 * refactor: 매개변수 final 추가 --------- Co-authored-by: mcodnjs <[email protected]> Co-authored-by: hgo641 <[email protected]>
📄 Summary
Trip 생성
최악의 경우: 쿼리 110번, 134ms

결론 쿼리 110번 -> 4번, 속도 134ms -> 83ms
Trip 조회
최악의 경우: 쿼리 3783번, 7446ms

결론 쿼리 3783 -> 4, 속도 7446ms -> 600ms
refresh와 writer를 하나로 합칠 수 있을 것 같은데.. 쿼리 하나 줄이는 것보다 다른 일이 훨씬 급해서 여기까지..! 다음 사람이 맡아주시길...!!
🙋🏻 More