-
Notifications
You must be signed in to change notification settings - Fork 44
[Volume5] 상품 조회 성능 최적화 — 인덱스, 원자적 좋아요 동기화, Redis 캐시 (엄인국) #203
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
base: ukukdin
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,29 +1,38 @@ | ||
| package com.loopers.application.brand; | ||
|
|
||
| import com.loopers.domain.model.brand.event.BrandDeletedEvent; | ||
| import com.loopers.domain.model.brand.event.BrandProductsDeletedEvent; | ||
| import com.loopers.domain.model.product.Product; | ||
| import com.loopers.domain.repository.ProductRepository; | ||
| import org.springframework.context.ApplicationEventPublisher; | ||
| import org.springframework.context.event.EventListener; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
|
|
||
| @Component | ||
| public class BrandDeletedEventHandler { | ||
|
|
||
| private final ProductRepository productRepository; | ||
| private final ApplicationEventPublisher eventPublisher; | ||
|
|
||
| public BrandDeletedEventHandler(ProductRepository productRepository) { | ||
| public BrandDeletedEventHandler(ProductRepository productRepository, | ||
| ApplicationEventPublisher eventPublisher) { | ||
| this.productRepository = productRepository; | ||
| this.eventPublisher = eventPublisher; | ||
| } | ||
|
|
||
| @EventListener | ||
| public void handle(BrandDeletedEvent event) { | ||
| List<Product> products = productRepository.findAllByBrandId(event.brandId()); | ||
| List<Long> deletedProductIds = new ArrayList<>(); | ||
| for (Product product : products) { | ||
| if (!product.isDeleted()) { | ||
| productRepository.save(product.delete()); | ||
| deletedProductIds.add(product.getId()); | ||
| } | ||
| } | ||
| eventPublisher.publishEvent(new BrandProductsDeletedEvent(event.brandId(), deletedProductIds)); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,8 +5,10 @@ | |
| import com.loopers.domain.model.product.Product; | ||
| import com.loopers.domain.repository.BrandRepository; | ||
| import com.loopers.domain.repository.ProductRepository; | ||
| import com.loopers.infrastructure.cache.CacheConfig; | ||
| import com.loopers.support.error.CoreException; | ||
| import com.loopers.support.error.ErrorType; | ||
| import org.springframework.cache.annotation.Cacheable; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
| import java.util.List; | ||
|
|
@@ -26,6 +28,7 @@ public ProductQueryService(ProductRepository productRepository, BrandRepository | |
| } | ||
|
|
||
| @Override | ||
| @Cacheable(value = CacheConfig.PRODUCT_DETAIL, key = "#productId") | ||
| public ProductDetailInfo getProduct(Long productId) { | ||
| Product product = productRepository.findActiveById(productId) | ||
| .orElseThrow(() -> new CoreException(ErrorType.PRODUCT_NOT_FOUND)); | ||
|
|
@@ -48,6 +51,7 @@ public ProductDetailInfo getProduct(Long productId) { | |
| } | ||
|
|
||
| @Override | ||
| @Cacheable(value = CacheConfig.PRODUCT_LIST, key = "'brand:' + #brandId + ':sort:' + #sort + ':page:' + #page + ':size:' + #size") | ||
| public PageResult<ProductSummaryInfo> getProducts(Long brandId, String sort, int page, int size) { | ||
|
Comment on lines
53
to
55
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# PRODUCT_LIST 캐시 eviction 로직 확인
rg -n "PRODUCT_LIST" --type=java -A 5 -B 2Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 6442 🏁 Script executed: #!/bin/bash
# ProductCacheEvictHandler 호출 위치 확인
rg -n "ProductCacheEvictHandler\|evictAllProductCaches\|evictProductListAfterCommit" --type=java -B 3 -A 3Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 67 🏁 Script executed: #!/bin/bash
# ProductService의 캐시 무효화 호출 위치 확인
rg -n "evictProductListAfterCommit" --type=java -B 5 -A 2Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 4100 🏁 Script executed: #!/bin/bash
# 전체 ProductService 내용 확인 (특히 mutation 메서드들)
cat -n apps/commerce-api/src/main/java/com/loopers/application/product/ProductService.java | head -150Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 5115
원래 지적한 상품 생성/수정/삭제 시마다 전체 대신 변경된 상품의 성능 영향을 모니터링하고, 캐시 히트율이 낮으면 TTL 조정이나 캐시 전략 재검토를 수행하라. 🤖 Prompt for AI Agents
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more.
다만, 향후 개선 시점 판단을 위해 아래 두 가지 지표를 사전에 Micrometer로 수집해 두길 권장한다:
이 수치가 임계점을 초과할 때 ✏️ Learnings added
🧠 Learnings used |
||
| PageResult<Product> products = productRepository.findAllActive(brandId, sort, page, size); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package com.loopers.domain.model.brand.event; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public record BrandProductsDeletedEvent( | ||
| Long brandId, | ||
| List<Long> deletedProductIds | ||
| ) { | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.