-
Notifications
You must be signed in to change notification settings - Fork 6
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
[BE] Refactor/#430 핀 복사 시 JdbcTemplate을 이용하여 bulk insert하도록 변경 #617
Conversation
- datasource url에 &rewriteBatchedStatements=true&profileSQL=true&logger=Slf4JLogger&maxQuerySizeToLog=999999 추가할 것
- 추상화 후 실패하는 테스트의 영속성 컨텍스트 문제 해결
- 실제 서브모듈에도 반영할것
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.
도이 코드 잘 봤습니당~
2가지 질문 남겼는데 답변 해주시면 감사하겠습니당 ㅎㅎ
@@ -0,0 +1,168 @@ | |||
package com.mapbefine.mapbefine.common.repository; |
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.
전 pin 패키지 내에 infrastructure 패키지 만들어 준 후에 거기서 사용하는 걸로 생각했었어요!
제가 말을 명쾌하게 드리지 못한 것 같네요ㅠ
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.
앗 아직 Draft 상태인 PR이라.. 본문 작성 및 보완하고 나면 Ready for Review로 전환 후 다시 말씀드리겠습니다 ㅠ
|
||
public interface PinBatchRepositoryCustom { | ||
|
||
int[] saveAllToTopic(Topic topicForCopy, List<Pin> originalPins); |
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.
Collection으로 반환은 안되는건가용?
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.
Draft 상태지만 미리 남겨주셔서 말씀드리자면, 기존 batchUpdate의 시그니처를 그대로 가져가고자 했습니다!
batchUpdate에서 변경된 행 수를 int[] 로 주는데, 굳이 Collection으로 바꿔줄 이유를 모르겠어서용
public interface PinBatchRepositoryCustom { | ||
|
||
int[] saveAllToTopic(Topic topicForCopy, List<Pin> originalPins); | ||
|
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.
- 커스텀 리포지토리에 구현할 메서드를 정의한 인터페이스를 만들어주어야 합니다.
|
||
@Slf4j | ||
@Repository | ||
public class PinBatchRepositoryCustomImpl implements PinBatchRepositoryCustom { |
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.
- 1에서 만든 인터페이스의 구현체를 만드는데, 이때 중요한 것은 클래스명입니다!
인터페이스 이름 + Impl
로 지어주어야해요.
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.
Impl 로 만들지 않는다면 작동을 안하나요?
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.
@kpeel5839 https://cs-ssupport.tistory.com/511 다른 이름도 설정할 수 있는 방법이 있긴한데, 널리 쓰이진 않는 것 같아요 ㅋㅋ
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.
Impl 이 짱이긴하죠 껄껄
@@ -9,11 +10,14 @@ | |||
import org.springframework.stereotype.Repository; | |||
|
|||
@Repository | |||
public interface PinRepository extends JpaRepository<Pin, Long> { | |||
public interface PinRepository extends JpaRepository<Pin, Long>, PinBatchRepositoryCustom { |
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.
- 2에서 만든 커스텀 리포지토리 구현체를 이렇게 JpaRepository와 합쳐서 사용할 수 있습니다.
그리고 사용자 정의로 구현한 내용이 기본 구현이나 Repository aspect(공식문서 워딩인데 Repository에 대해 전역적으로 적용되는 기능들을 말하는 게 아닐까 싶어요)보다 우선시 된다고 하네용
private static class PinImageInsertDto { | ||
|
||
private final String imageUrl; | ||
private final Long pinId; | ||
private final boolean isDeleted; |
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.
불가피하게 DB 저장 시 사용할 Dto가 필요했습니다.
딱 여기에서만 사용하는 Dto라 내부 클래스로 정의했습니다.
핀 저장 -> 아이디 조회 -> DTO 생성 -> 핀 이미지 저장 이 한 메서드에서 이루어져서 좀 더 객체 책임을 분리해보는 방법을 고민해보고 싶긴 하네요
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.
그러게요. 사실 saveAll 메서드가 bulk insert를 지원했다면 한번에 이루어졌을 로직이라, 억지로 분리할 필요는 없다는 생각도 드네요!
private int[] bulkInsertPins(Topic topicForCopy, List<Pin> originalPins) { | ||
String bulkInsertSql = "INSERT INTO pin (" | ||
+ "name, description, member_id, topic_id, location_id," | ||
+ " created_at, updated_at)" | ||
+ " VALUES (" | ||
+ " ?, ?, ?, ?, ?," | ||
+ " ?, ?)"; | ||
LocalDateTime createdAt = topicForCopy.getLastPinUpdatedAt(); | ||
Long topicId = topicForCopy.getId(); | ||
Long creatorId = topicForCopy.getCreator().getId(); | ||
log.debug("[Query] bulk insert size {} : {}", originalPins.size(), bulkInsertSql); | ||
|
||
return jdbcTemplate.batchUpdate(bulkInsertSql, new BatchPreparedStatementSetter() { |
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.
bulk insert하는 부분입니다.
- 복사할 Pin들을 bulk insert한다.
- bulk insert 쿼리 실행 후, 해당 쿼리의 첫번째 id값을 받아온다.
- id값을 이용해 새로 저장할 PinImage의 내용을 만든다.
- PinImageInsertDto를 bulk insert한다.
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.
도이 코드 잘 봤어요!
역시 천재 또또이.. 아주 잘 해내주셨군요
그냥 간단하게 코멘트 남겼습니다!
읽어보시고 답변해주시면 감사할 것 같아요!
@@ -55,12 +56,12 @@ public Long saveTopic(AuthMember member, TopicCreateRequest request) { | |||
Topic topic = convertToTopic(member, request); | |||
List<Long> pinIds = request.pins(); | |||
|
|||
topicRepository.save(topic); | |||
topicRepository.flush(); |
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.
flush
하지 않으면 어떤 일이 발생하나요?
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.
JdbcTemplate은 바로 쿼리문을 쏘니까 필요한 듯요..
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.
다들 꼼꼼히 봐주셔서 감사합니다!!
JdbcTemplate은 바로 쿼리문을 쏘니까 필요한 듯요..
맞아요 저도 그런 이유로 flush를 넣었었는데요.. (JdbcTemplate의 쿼리 이전에 topic을 먼저 save하려고)
아래 다른 코멘트에서 확인되듯이 flush를 안해도 topic이 먼저 save 되는거였네요.
왜냐하면 saveAllToTopic 메서드 내에서 batchUpdate를 실행하기 전에 전달받은 topic을 조회하기 때문에,
조회하는 일이 발생했으니까 JPA에서 insert를 먼저 시켜주는 것 같아요. 제가 생각한 게 맞을까요?
++ flush는 지우겠습니다!
backend/src/main/java/com/mapbefine/mapbefine/topic/application/TopicCommandService.java
Outdated
Show resolved
Hide resolved
topicRepository.save(topic); | ||
topicRepository.flush(); |
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.
topicRepository.save(topic); | |
topicRepository.flush(); | |
topicRepository.saveAndFlush(topic); |
은 어떠신가요?
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.
근데 flush 필요 없는 것 맞았습니다 ^.^ ㅜ 지울게요! 좋은거 알려주셔서 감사해여
@@ -105,14 +106,15 @@ private void copyPinsToTopic( | |||
) { | |||
List<Pin> originalPins = findAllPins(pinIds); | |||
validateCopyablePins(member, originalPins); | |||
topic.increasePinCount(pinIds.size()); | |||
topic.updateLastPinUpdatedAt(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.
@PrePersist
protected void prePersist() {
lastPinUpdatedAt = LocalDateTime.now();
}
현재 Topic
에는 위와 같은 코드가 존재하는데요!
그렇기 때문에, Save
하면서 LastPinupdateAt
은 이미 현재 시각으로 적용되어 있지 않나용?
아니라면 따끔하게 말씀해주십숑
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.
헉 맞아요!! id 채번하는 데에만 정신이 팔려서 꼼꼼하지 못했던 부분이 많네요 ㅠㅠ 감사합니당
@@ -105,14 +106,15 @@ private void copyPinsToTopic( | |||
) { | |||
List<Pin> originalPins = findAllPins(pinIds); | |||
validateCopyablePins(member, originalPins); | |||
topic.increasePinCount(pinIds.size()); | |||
topic.updateLastPinUpdatedAt(LocalDateTime.now()); | |||
pinRepository.flush(); |
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.
여기도 flush 가 있는 것을 보니 batch update 를 진행하기 위해서는 명시적인 flush 호출이 필요한가보군요 흠흠
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.
update할때는 flush를 해주어야 하는 게 맞아요!
이전에 delete가 반영안되는 문제 해결할 때, member update가 되지 않았던 부분 문제랑 동일하다고 보면 될거같아용
https://map-befine-official.github.io/trouble-shooting-jpa-delete-and-persistence/
private static class PinImageInsertDto { | ||
|
||
private final String imageUrl; | ||
private final Long pinId; | ||
private final boolean isDeleted; |
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.
흠... 근데 너무 묶여있는 작업이라 현재 방식도 괜찮다고 보긴하는데.. 이에 대해 도이 생각은 어떠신가요??
+ "(image_url, pin_id) " | ||
+ "VALUES " | ||
+ "(?, ?)"; | ||
log.debug("[Query] bulk insert size {} : {}", pinImages.size(), bulkInsertSql); |
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.
여기서 왜 log
를 따로 찍는지도 PR 에 너무 상세하게 적어주셔서 이해가 쉬웠습니다!
역시 도이짱!
+ " VALUES (" | ||
+ " ?, ?, ?, ?, ?," | ||
+ " ?, ?)"; | ||
LocalDateTime createdAt = topicForCopy.getLastPinUpdatedAt(); |
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.
아 이거를 위해서 topic.updateLastPinUpdatedAt
를 만들어주신건가??!
근데 그렇다기에는 flush
해주면서 @PrePersist
가 진행되어서 위 메서드를 호출해서 현재 시각으로 설정해주지 않더라도 설정이 되어 있지 않나요??
잘 모르겠네요 허허허허
validateId(firstIdFromBatch); | ||
|
||
return IntStream.range(0, originalPins.size()) | ||
.filter(index -> rowCount[index] != -3) |
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.
오 minus 3 이면 무슨 의미를 가지나요
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.
꼭 -3 뿐만 아니라, 음수 값일때 수행하지 못하도록 할 필요는 없을까요 ?
SUCCESS_NO__INFO
가 어떤 상황에 발생하는건지는 알 수 없지만, 성공은 한거니까 상관없을까요 ?
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.
넵 저는 성공이니까 상관없다고 생각했어요! 성공해서 insert된 레코드들에 대한 작업을 해주니까요.
쥬니 생각은 어떠신가요?
.filter(index -> rowCount[index] != -3) | ||
.mapToObj(index -> { | ||
Pin pin = originalPins.get(index); | ||
return PinImageInsertDto.of(pin.getPinImages(), firstIdFromBatch + index); |
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.
도이 고생 많으셨어요 !!
뒤늦게 알고리즘에 뛰어들어서, 리뷰가 늦어졌네요 😢
이미 리뷰가 꼼꼼하게 남겨져있는 것 같아서, 중복 질문은 피해서 코멘트 남겼습니다 !
String bulkInsertSql = "INSERT INTO pin (" | ||
+ "name, description, member_id, topic_id, location_id," | ||
+ " created_at, updated_at)" | ||
+ " VALUES (" | ||
+ " ?, ?, ?, ?, ?," | ||
+ " ?, ?)"; |
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.
String bulkInsertSql = "INSERT INTO pin (" | |
+ "name, description, member_id, topic_id, location_id," | |
+ " created_at, updated_at)" | |
+ " VALUES (" | |
+ " ?, ?, ?, ?, ?," | |
+ " ?, ?)"; | |
String bulkInsertSql = "INSERT INTO pin " | |
+ "(name, description, member_id, topic_id, location_id, " | |
+ "created_at, updated_at)" | |
+ "VALUES ("?, ?, ?, ?, ?, ?, ?)"; |
이런 개행이 더 예쁜 것 같은데, 기존과 같이 개행을 적용하신 이유가 있나요 !?
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.
필드를 두 줄로 나눈 부분을 ?이랑 같게 두는 게 ? 개수가 맞는지 확인하기 편하다고 생각했어요!
근데 괄호는 제가 봐도 안예쁘네여 ㅋㅋ 괄호만 수정해볼게요
private List<PinImageInsertDto> createPinImageInsertDtos(List<Pin> originalPins, int[] rowCount) { | ||
Long firstIdFromBatch = jdbcTemplate.queryForObject("SELECT last_insert_id()", Long.class); | ||
validateId(firstIdFromBatch); |
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.
해당 쿼리문에 대한 queryForObject
메서드의 반환값이 Null일 가능성이 존재하나요 ?
DB 자체의 문제로 발생하는 거면, 내부적으로 다른 예외를 던져주지 않을까 싶은데.. 아닌가요 ?
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.
맞네요. null일 가능성은 없네요!
근데 last_insert_id()
가 트랜잭션 별로 관리되어서 만약 이전에 insert를 성공한 적이 없다면 0을 반환하더라구요.
이 부분에 대한 예외로 수정하겠습니당
@@ -55,12 +56,12 @@ public Long saveTopic(AuthMember member, TopicCreateRequest request) { | |||
Topic topic = convertToTopic(member, request); | |||
List<Long> pinIds = request.pins(); | |||
|
|||
topicRepository.save(topic); | |||
topicRepository.flush(); |
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.
JdbcTemplate은 바로 쿼리문을 쏘니까 필요한 듯요..
|
||
topicRepository.save(topic); |
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.
엥 그러네 ...
assertSoftly(softly -> { | ||
softly.assertThat(copiedPins).extracting("pinInfo") | ||
.usingRecursiveComparison() | ||
.isEqualTo(originalPinInfos); | ||
softly.assertThat(copiedPins.get(0).getCreator()) | ||
.isEqualTo(copier); | ||
softly.assertThat(copiedPins).hasSize(originalPins.size()) | ||
.flatMap(Pin::getPinImages) | ||
.allSatisfy(pinImage -> { | ||
assertThat(pinImage.getImageUrl()).isEqualTo(PinImageFixture.IMAGE_URL); | ||
}); | ||
}); | ||
} |
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.
assertSoftly
를 사용하면, assertAll
을 사용하는 것보다 테스트 성능이 현저히 떨어진다는 사실을 알고 계신가요 ?
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.
허걱스 몰랐어요. 근데 성능이 현저히 떨어진다는 건 테스트 시간이 많이 길어진다는 뜻인가요?! assertSoftly로 하면 assertAll과 달리 각 테스트가 실패한 코드 라인 위치까지 보여줘서 편하다구 생각했는데 이 점은 어떻게 생각하시나요..?!
validateId(firstIdFromBatch); | ||
|
||
return IntStream.range(0, originalPins.size()) | ||
.filter(index -> rowCount[index] != -3) |
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.
꼭 -3 뿐만 아니라, 음수 값일때 수행하지 못하도록 할 필요는 없을까요 ?
SUCCESS_NO__INFO
가 어떤 상황에 발생하는건지는 알 수 없지만, 성공은 한거니까 상관없을까요 ?
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.
도이 커밋단위가 세세해서, 제가 작업하고 있는 느낌이 들 정도로 읽기 수월했어요!
고생하셨습니다!
빠르게 머지 하시죵~
@@ -57,7 +57,6 @@ public Long saveTopic(AuthMember member, TopicCreateRequest request) { | |||
List<Long> pinIds = request.pins(); | |||
|
|||
topicRepository.save(topic); | |||
topicRepository.flush(); |
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.
👍
@@ -78,7 +80,7 @@ private List<PinImageInsertDto> createPinImageInsertDtos(List<Pin> originalPins, | |||
validateId(firstIdFromBatch); | |||
|
|||
return IntStream.range(0, originalPins.size()) | |||
.filter(index -> rowCount[index] != -3) | |||
.filter(index -> rowCount[index] != EXECUTE_FAILED) |
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.
👍
String bulkInsertSql = "INSERT INTO pin " | ||
+ "(name, description, member_id, topic_id, location_id, " | ||
+ "created_at, updated_at) " | ||
+ "VALUES " | ||
+ "(?, ?, ?, ?, ?, " | ||
+ "?, ?)"; |
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 record PinImageInsertDto( | ||
String imageUrl, | ||
Long pinId, | ||
boolean isDeleted | ||
) { |
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 increasePinCount() { | ||
pinCount++; | ||
} |
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.
👍
@@ -106,7 +105,6 @@ private void copyPinsToTopic( | |||
List<Pin> originalPins = findAllPins(pinIds); | |||
validateCopyablePins(member, originalPins); | |||
topic.increasePinCount(pinIds.size()); | |||
topic.updateLastPinUpdatedAt(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.
👍
@@ -139,7 +137,6 @@ public Long merge(AuthMember member, TopicMergeRequest request) { | |||
List<Pin> originalPins = getAllPinsFromTopics(originalTopics); | |||
|
|||
topic.increasePinCount(originalPins.size()); | |||
topic.updateLastPinUpdatedAt(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.
👍
작업 대상
#430 참조부탁드립니다.
조금씩 오랜 기간 작업했다보니 공유/기록할 만한 사항이 많아 보여 다소 긴 내용 양해부탁드려요 🥲
추가로 함께 변경된 사항
pinRepository.findAllByIdIn(Long pinId)
에 fetch join 적용 (이로 인해 Latency가 악화되지는 않는 것을 확인했습니다)📄 작업 내용
용어 정리
초반에 헷갈려서 단어를 섞어 썼는데요. 지금은 아래와 같이 이해하고 사용하고 있습니다!
insert into Pin values (?,?,?), (?,?,?), (?,?,?)...
와 같이 하나의 insert 쿼리에서 여러 행을 추가하는 것batchUpdate()
메서드를 표현할 때 해당 단어를 사용했습니다.(결론부터) 성능 비교
이슈에 올렸던 성능 비교에서는 핀 이미지가 없는 핀으로만 테스트했기 때문에 논외로 치고 다시 진행했습니다.
/topics/new
Bulk Insert 적용 이전
findAllByIdIn(Long pinId)
에서의 N+1 문제도 함께 존재했습니다.Bulk Insert 적용 이후
겪었던 문제
생각하지 못한 어려움을 해결하고 조금씩 작업하느라 오래 걸렸네요.
슬랙 및 대화로 충분히 공유되었다고 생각하지만, 문서화를 위해 추가 기록해둡니다.
1.
1:N
연관 관계가 있는 엔티티들을 Bulk Insert 할 때,1
에 해당하는 엔티티(pin)의 Id를 모두 알아야N
(pinImage)을 저장할 수 있다.last_insert_id()
함수를 사용해 bulk insert한 pin의 첫번째 id를 받아온 뒤 해당하는 개수만큼 계산해 pinId를 가진 pinImage를 저장합니다.2. JdbcTemplate을 주입받는 Repository로 인해 DataJpaTest가 터지는 문제
그밖에 고려한 사항
1. Pin을 복사해올 때, Topic의 pinCount, lastPinUpdatdAt도 업데이트 해야 함
saveAllToTopic
내에서topic.update...
를 해주었습니다.이 부분에 대해서는 다른 분들의 의견도 들어보고 싶어요!
2. SQL 로깅
한 가지 인지해야 할 것이, 현재 구현된 bulk insert는 hibernate에서 수행되는 쿼리가 아니기 때문에 저희의 QueryCounter에 걸리지 않고, Hibernate 로그에도 찍히지 않습니다.
🙋🏻 주의 사항
앞서 말한 것처럼, 현재 구현된 bulk insert는 hibernate에서 수행되는 쿼리가 아니기 때문에 저희의 QueryCounter에 걸리지 않고, Hibernate 로그에도 찍히지 않습니다!
만약 개발하다가 해당 쿼리 로그를 확인할 필요가 있다면 로컬 환경에서 위 설정을 추가해주세요.
스크린샷
📎 관련 이슈
레퍼런스