Skip to content

Commit 3e42ddb

Browse files
authored
Merge pull request #209 from YAPP-Github/develop
[Refactor] SLO에 영향을 미치는 쿼리 개선 작업 Prod 적용
2 parents b01e5a4 + c96438f commit 3e42ddb

File tree

13 files changed

+125
-58
lines changed

13 files changed

+125
-58
lines changed

src/main/java/eatda/domain/cheer/Cheer.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ public class Cheer extends AuditingEntity {
4949
@OneToMany(mappedBy = "cheer", cascade = CascadeType.ALL, orphanRemoval = true)
5050
private Set<CheerImage> images = new HashSet<>();
5151

52+
/*
53+
CheerTags가 Embedded이기 때문에 BatchSize를 그대로 적용하지 못함.
54+
성능을 위해서는 Embedded 제거 후 직접 @OneToMany로 매핑 필요함.
55+
현재 데이터가 많지 않음으로 현상 유지하며 모니터링.
56+
추후 재설계 필요
57+
*/
5258
@Embedded
5359
private CheerTags cheerTags;
5460

src/main/java/eatda/domain/store/Store.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ public class Store extends AuditingEntity {
5757
@Embedded
5858
private Coordinates coordinates;
5959

60+
/*
61+
현재는 가게당 평균 응원 수가 5개 이하이므로 BatchSize=10이 적절함.
62+
데이터 증가를 고려하여 IN 쿼리 한 번당 최대 30개 Store의 Cheer를 로딩하도록 설정.
63+
향후 응원 수가 증가하거나 Store 리스트 조회 규모가 커질 경우
64+
성능 모니터링 후 BatchSize 조정 및 Fetch 전략 재검토 필요.
65+
*/
6066
@OneToMany(mappedBy = "store")
6167
private List<Cheer> cheers = new ArrayList<>();
6268

src/main/java/eatda/repository/cheer/CheerRepository.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,18 @@
88
import eatda.domain.store.StoreCategory;
99
import jakarta.persistence.criteria.JoinType;
1010
import java.util.List;
11+
import org.springframework.data.domain.Page;
1112
import org.springframework.data.domain.PageRequest;
1213
import org.springframework.data.domain.Pageable;
1314
import org.springframework.data.jpa.domain.Specification;
14-
import org.springframework.data.jpa.repository.EntityGraph;
1515
import org.springframework.data.jpa.repository.JpaRepository;
1616
import org.springframework.lang.Nullable;
1717

1818
public interface CheerRepository extends JpaRepository<Cheer, Long> {
1919

20-
@EntityGraph(attributePaths = {"member", "cheerTags.values"})
21-
List<Cheer> findAllByStoreOrderByCreatedAtDesc(Store store, PageRequest pageRequest);
20+
Page<Cheer> findAllByStoreOrderByCreatedAtDesc(Store store, PageRequest pageRequest);
2221

23-
default List<Cheer> findAllByConditions(@Nullable StoreCategory category,
22+
default Page<Cheer> findAllByConditions(@Nullable StoreCategory category,
2423
List<CheerTagName> cheerTagNames,
2524
List<District> districts, Pageable pageable) {
2625
Specification<Cheer> spec = createSpecification(category, cheerTagNames, districts);
@@ -36,7 +35,9 @@ private Specification<Cheer> createSpecification(@Nullable StoreCategory categor
3635
}
3736
if (!cheerTagNames.isEmpty()) {
3837
spec = spec.and(((root, query, cb) -> {
39-
query.distinct(true);
38+
if (query != null) {
39+
query.distinct(true);
40+
}
4041
return root.join("cheerTags").join("values", JoinType.LEFT)
4142
.get("name").in(cheerTagNames);
4243
}));
@@ -47,8 +48,7 @@ private Specification<Cheer> createSpecification(@Nullable StoreCategory categor
4748
return spec;
4849
}
4950

50-
@EntityGraph(attributePaths = {"store", "member", "cheerTags.values"})
51-
List<Cheer> findAll(Specification<Cheer> specification, Pageable pageable);
51+
Page<Cheer> findAll(Specification<Cheer> specification, Pageable pageable);
5252

5353
int countByMember(Member member);
5454

src/main/java/eatda/repository/store/StoreRepository.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
import eatda.exception.BusinessException;
99
import java.util.List;
1010
import java.util.Optional;
11+
import org.springframework.data.domain.Page;
1112
import org.springframework.data.domain.Pageable;
1213
import org.springframework.data.jpa.domain.Specification;
13-
import org.springframework.data.jpa.repository.EntityGraph;
1414
import org.springframework.data.jpa.repository.JpaRepository;
1515
import org.springframework.data.jpa.repository.Query;
1616
import org.springframework.lang.Nullable;
@@ -33,16 +33,15 @@ default Store getById(Long id) {
3333
""")
3434
List<Store> findAllByCheeredMemberId(long memberId);
3535

36-
default List<Store> findAllByConditions(@Nullable StoreCategory category,
36+
default Page<Store> findAllByConditions(@Nullable StoreCategory category,
3737
List<CheerTagName> cheerTagNames,
3838
List<District> districts,
3939
Pageable pageable) {
4040
Specification<Store> spec = createSpecification(category, cheerTagNames, districts);
4141
return findAll(spec, pageable);
4242
}
4343

44-
@EntityGraph(attributePaths = {"cheers"})
45-
List<Store> findAll(Specification<Store> spec, Pageable pageable);
44+
Page<Store> findAll(Specification<Store> spec, Pageable pageable);
4645

4746
private Specification<Store> createSpecification(@Nullable StoreCategory category,
4847
List<CheerTagName> cheerTagNames,
@@ -52,8 +51,12 @@ private Specification<Store> createSpecification(@Nullable StoreCategory categor
5251
spec = spec.and((root, query, cb) -> cb.equal(root.get("category"), category));
5352
}
5453
if (!cheerTagNames.isEmpty()) {
55-
spec = spec.and(((root, query, cb) ->
56-
root.join("cheers").join("cheerTags").join("values").get("name").in(cheerTagNames)));
54+
spec = spec.and(((root, query, cb) -> {
55+
if (query != null) {
56+
query.distinct(true);
57+
}
58+
return root.join("cheers").join("cheerTags").join("values").get("name").in(cheerTagNames);
59+
}));
5760
}
5861
if (!districts.isEmpty()) {
5962
spec = spec.and((root, query, cb) -> root.get("district").in(districts));

src/main/java/eatda/repository/story/StoryRepository.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,12 @@
33
import eatda.domain.story.Story;
44
import org.springframework.data.domain.Page;
55
import org.springframework.data.domain.Pageable;
6-
import org.springframework.data.jpa.repository.EntityGraph;
76
import org.springframework.data.jpa.repository.JpaRepository;
87

98
public interface StoryRepository extends JpaRepository<Story, Long> {
10-
@EntityGraph(attributePaths = "images")
119
Page<Story> findAllByOrderByCreatedAtDesc(Pageable pageable);
1210

1311
Page<Story> findAllByMemberIdOrderByCreatedAtDesc(Long memberId, Pageable pageable);
1412

15-
@EntityGraph(attributePaths = {"member", "images"})
1613
Page<Story> findAllByStoreKakaoIdOrderByCreatedAtDesc(String storeKakaoId, Pageable pageable);
1714
}

src/main/java/eatda/service/cheer/CheerService.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.stream.IntStream;
2626
import lombok.RequiredArgsConstructor;
2727
import org.springframework.beans.factory.annotation.Value;
28+
import org.springframework.data.domain.Page;
2829
import org.springframework.data.domain.PageRequest;
2930
import org.springframework.data.domain.Sort;
3031
import org.springframework.data.domain.Sort.Direction;
@@ -115,12 +116,15 @@ private void saveCheerImages(Cheer cheer,
115116

116117
@Transactional(readOnly = true)
117118
public CheersResponse getCheers(CheerSearchParameters parameters) {
118-
List<Cheer> cheers = cheerRepository.findAllByConditions(
119+
Page<Cheer> cheerPage = cheerRepository.findAllByConditions(
119120
parameters.getCategory(),
120121
parameters.getCheerTagNames(),
121122
parameters.getDistricts(),
122-
PageRequest.of(parameters.getPage(), parameters.getSize(), Sort.by(Direction.DESC, "createdAt"))
123+
PageRequest.of(parameters.getPage(), parameters.getSize(),
124+
Sort.by(Direction.DESC, "createdAt"))
123125
);
126+
127+
List<Cheer> cheers = cheerPage.getContent();
124128
return toCheersResponse(cheers);
125129
}
126130

@@ -140,11 +144,12 @@ private CheersResponse toCheersResponse(List<Cheer> cheers) {
140144
@Transactional(readOnly = true)
141145
public CheersInStoreResponse getCheersByStoreId(Long storeId, int page, int size) {
142146
Store store = storeRepository.getById(storeId);
143-
List<Cheer> cheers = cheerRepository.findAllByStoreOrderByCreatedAtDesc(store, PageRequest.of(page, size));
147+
Page<Cheer> cheersPage = cheerRepository.findAllByStoreOrderByCreatedAtDesc(store, PageRequest.of(page, size));
144148

145-
List<CheerInStoreResponse> cheersResponse = cheers.stream()
149+
List<CheerInStoreResponse> cheersResponse = cheersPage.getContent().stream()
146150
.map(CheerInStoreResponse::new)
147-
.toList(); // TODO N+1 문제 해결
151+
.toList();
152+
148153
return new CheersInStoreResponse(cheersResponse);
149154
}
150155
}

src/main/java/eatda/service/store/StoreService.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.Optional;
2020
import lombok.RequiredArgsConstructor;
2121
import org.springframework.beans.factory.annotation.Value;
22+
import org.springframework.data.domain.Page;
2223
import org.springframework.data.domain.PageRequest;
2324
import org.springframework.data.domain.Sort;
2425
import org.springframework.data.domain.Sort.Direction;
@@ -45,7 +46,7 @@ public StoreResponse getStore(long storeId) {
4546
// TODO : N+1 문제 해결
4647
@Transactional(readOnly = true)
4748
public StoresResponse getStores(StoreSearchParameters parameters) {
48-
List<Store> stores = storeRepository.findAllByConditions(
49+
Page<Store> stores = storeRepository.findAllByConditions(
4950
parameters.getCategory(),
5051
parameters.getCheerTagNames(),
5152
parameters.getDistricts(),

src/main/resources/application-dev.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ spring:
2727
jpa:
2828
hibernate:
2929
ddl-auto: validate
30+
properties:
31+
hibernate:
32+
default_batch_fetch_size: 30
3033

3134
flyway:
3235
enabled: true

src/main/resources/application-local.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ spring:
2222
max-file-size: 5MB
2323
max-request-size: 20MB
2424

25+
# BatchSize 미적용시를 비교하기 위해 local에는 BatchSize를 추가하지 않음
2526
jpa:
2627
hibernate:
2728
ddl-auto: validate

src/main/resources/application-prod.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ spring:
2727
jpa:
2828
hibernate:
2929
ddl-auto: validate
30+
properties:
31+
hibernate:
32+
default_batch_fetch_size: 30
3033

3134
flyway:
3235
enabled: true

0 commit comments

Comments
 (0)