Skip to content

[volume-5] 5주차 인덱스와 캐시를 사용한 성능 최적화 - #192

Merged
move-wook merged 2 commits into
Loopers-dev-lab:move-wookfrom
move-wook:round5
Mar 16, 2026
Merged

[volume-5] 5주차 인덱스와 캐시를 사용한 성능 최적화 #192
move-wook merged 2 commits into
Loopers-dev-lab:move-wookfrom
move-wook:round5

Conversation

@move-wook

@move-wook move-wook commented Mar 12, 2026

Copy link
Copy Markdown

📌 Summary

  • 배경: 상품 조회 API 3개(최신순, 브랜드별 가격순, 좋아요순)가 5,000만건 기준 LIMIT 20임에도 20초 이상 소요. 인덱스 없이 Full Table Scan + filesort 발생
  • 목표: 인덱스 설계로 슬로우 쿼리 제거 + 로컬 캐시 적용으로 반복 조회 성능 최적화
  • 결과: 23초 → 0.003초 (약 8,000배 개선). 인덱스로 80~98% 감소, 캐시 hit 시 DB 조회 제거

🧭 Context & Decision

문제 정의

  • 현재 동작/제약: 상품 테이블에 PK 외 인덱스 없음. 모든 조회가 Full Table Scan + filesort 수행
  • 문제(또는 리스크): LIMIT 20인데 5,000만건 전부 스캔 후 정렬 → 23초 응답. 사용자 이탈 및 DB 부하
  • 성공 기준(완료 정의): 상품 조회 API 응답 시간 1초 이내

선택지와 결정

  • 고려한 대안:
    • A: Redis 캐시 — 멀티 인스턴스 환경에서 캐시 일관성 보장, 외부 서버 필요
    • B: Caffeine 로컬 캐시 — JVM 메모리 기반, TTL + 최대 개수 제한 지원, 네트워크 비용 없음
  • 최종 결정: Caffeine 로컬 캐시
  • 트레이드오프: 멀티 인스턴스 시 캐시 일관성 미보장. 단일 인스턴스에서는 Redis 대비 지연 없이 더 빠름
  • 추후 개선 여지: 멀티 인스턴스 전환 시 Redis로 교체 고려

🏗️ Design Overview

변경 범위

  • 영향 받는 모듈/도메인: Product (상품 조회 API)
  • 신규 추가: CacheConfig, Caffeine 의존성, @Cacheable/@CacheEvict 적용
  • 제거/대체: 없음

주요 컴포넌트 책임

  • CacheConfig: Caffeine 캐시 매니저 설정 (TTL 5분, 최대 100개)
  • ProductFacade: 조회 시 @Cacheable로 캐시 hit/miss 처리, 변경 시 @CacheEvict로 즉시 무효화

DB 인덱스 (DDL 직접 적용)

CREATE INDEX idx_products_deleted_created ON products(deleted_at, created_at DESC);
CREATE INDEX idx_products_brand_deleted_price ON products(brand_id, deleted_at, price);
CREATE INDEX idx_products_deleted_likes ON products(deleted_at, likes_count DESC);

🔁 Flow Diagram

Main Flow — 캐시 hit

sequenceDiagram
  autonumber
  participant Client
  participant Controller
  participant ProductFacade
  participant CaffeineCache

  Client->>Controller: GET /api/v1/products?sort=latest
  Controller->>ProductFacade: getProducts(pageable)
  ProductFacade->>CaffeineCache: @Cacheable 키 조회
  CaffeineCache-->>ProductFacade: 캐시 hit (0.003초)
  ProductFacade-->>Controller: Page<ProductInfo>
  Controller-->>Client: 200 OK
Loading

Main Flow — 캐시 miss

sequenceDiagram
  autonumber
  participant Client
  participant Controller
  participant ProductFacade
  participant CaffeineCache
  participant ProductService
  participant DB

  Client->>Controller: GET /api/v1/products?sort=latest
  Controller->>ProductFacade: getProducts(pageable)
  ProductFacade->>CaffeineCache: @Cacheable 키 조회
  CaffeineCache-->>ProductFacade: 캐시 miss
  ProductFacade->>ProductService: getAll(pageable)
  ProductService->>DB: SELECT ... (인덱스 사용)
  DB-->>ProductService: 20건 반환 (~5초)
  ProductService-->>ProductFacade: Page<Product>
  ProductFacade->>CaffeineCache: 결과 캐시 저장
  ProductFacade-->>Controller: Page<ProductInfo>
  Controller-->>Client: 200 OK
Loading

캐시 무효화 Flow

sequenceDiagram
  autonumber
  participant Admin
  participant Controller
  participant ProductFacade
  participant CaffeineCache
  participant ProductService
  participant DB

  Admin->>Controller: PUT /api/admin/v1/products/{id}
  Controller->>ProductFacade: updateProduct(id, command)
  ProductFacade->>ProductService: update(id, command)
  ProductService->>DB: UPDATE products
  DB-->>ProductService: 완료
  ProductFacade->>CaffeineCache: @CacheEvict 전체 삭제
  ProductFacade-->>Controller: ProductInfo
  Controller-->>Admin: 200 OK
Loading

목적: 50M 행 products 테이블의 상품 조회 쿼리 성능 개선 (응답 시간 23초 → 1초 이하 단축).

핵심 변경점: Caffeine 로컬 캐시(TTL 5분, 최대 100개) 도입으로 ProductFacade의 getProducts, getProductsByBrandId 메서드에 @Cacheable 적용하고, 등록/수정/삭제 시 @CacheEvict로 캐시 전체 무효화. DB 인덱스 3개(deleted_at+created_at, brand_id+deleted_at+price, deleted_at+likes_count) 생성 필요.

리스크/주의사항: (1) PR 목표에서 DB 인덱스 DDL 적용을 언급했으나 코드에 마이그레이션 파일(Flyway V*.sql)이 없는데, 인덱스가 별도로 적용되었는지 확인 필요인지요? (2) Caffeine 로컬 캐시는 다중 인스턴스 환경에서 인스턴스 간 일관성 미보장 - Redis 전환 시기는 언제를 계획 중인지요? (3) allEntries=true로 전체 캐시 무효화하므로 대량 업데이트 시 성능 영향 가능.

테스트/검증: ProductFacadeTest 추가 작성 필요 (캐시 hit/miss 시나리오)하고 기존 ProductV1ApiE2ETest 실행 확인 필요. 실제 DB 인덱스 생성 및 쿼리 실행 계획(EXPLAIN) 검증 완료되었는지 확인 필요한지요?

move-wook and others added 2 commits March 12, 2026 21:01
- spring-boot-starter-cache + Caffeine 의존성 추가
- CacheConfig: TTL 5분, 최대 100개 엔트리 설정
- ProductFacade: 상품 목록 조회에 @Cacheable 적용
- 상품 등록/수정/삭제 시 @CacheEvict로 캐시 무효화

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 인덱스 적용 전 슬로우 쿼리 분석 (EXPLAIN, 측정 결과)
- 복합 인덱스 설계 원칙 및 적용 결과
- 캐시 전략 (Caffeine 선택 이유, TTL, CacheEvict)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Mar 12, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

캐싱 기능을 상품 모듈에 추가한다. Caffeine 기반 캐시 매니저를 설정하고, ProductFacade의 상품 조회, 등록, 수정, 삭제 메서드에 캐싱 어노테이션을 적용한다.

Changes

Cohort / File(s) Summary
의존성 추가
apps/commerce-api/build.gradle.kts
spring-boot-starter-cache 및 caffeine 라이브러리 의존성 추가
캐시 설정
apps/commerce-api/src/main/java/com/loopers/config/CacheConfig.java
Caffeine 기반 CacheManager 빈 등록. "products" 및 "products:brand" 캐시 정의, 만료 시간 5분, 최대 크기 100개
캐싱 적용
apps/commerce-api/src/main/java/com/loopers/application/product/ProductFacade.java
@Cacheable@CacheEvict 어노테이션으로 getProducts, getProductsByBrandId 메서드에 캐싱 적용. registerProduct, updateProduct, deleteProduct 메서드는 캐시 무효화 처리

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~15 minutes

운영 관점 검토사항

캐시 정책의 적절성

5분의 만료 시간과 100개 항목의 최대 크기 설정이 실제 트래픽 패턴에 맞는지 검증이 필요하다. 상품 정보의 변경 빈도와 조회 빈도를 기반으로 TTL을 조정해야 한다.

캐시 키 설계

getProducts와 getProductsByBrandId 메서드의 캐시 키가 페이지네이션 파라미터(page, size, sort 등)를 포함하는지 확인이 필요하다. 서로 다른 페이징 요청이 캐시 충돌을 일으키지 않도록 보장해야 한다.

캐시 일관성 문제

registerProduct, updateProduct, deleteProduct 메서드에서 "products" 및 "products:brand" 캐시를 동시에 무효화한다. 그러나 부분 업데이트 시나리오에서 불필요한 캐시 무효화가 발생할 수 있으므로, 실제 비즈니스 요구사항에 따라 세분화된 무효화 전략 검토를 권장한다.

성능 테스트

캐시 적중률(hit ratio), 응답 시간 개선, 데이터베이스 조회 감소 정도를 측정하는 성능 테스트 작성이 필요하다. 또한 동시성 환경에서 캐시 스탬피드(cache stampede) 현상을 모니터링해야 한다.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 주요 변경 사항인 인덱스와 캐시를 통한 성능 최적화를 명확하게 요약하고 있다.
Description check ✅ Passed PR 설명은 배경, 목표, 결과를 포함하고 설계 의사결정, 변경 범위, 주요 컴포넌트 책임, 흐름도를 모두 제시하여 템플릿 요구사항을 충족한다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/commerce-api/src/main/java/com/loopers/application/product/ProductFacade.java`:
- Around line 30-31: The current CacheEvict on ProductFacade misses other write
paths so list caches stay stale; update the cache eviction to a single
centralized write path or add eviction hooks in the other mutating flows: add
eviction when likes change and when brand deletes affect products. Concretely,
either move `@CacheEvict`(value = {"products","products:brand"}, allEntries=true)
out of ProductFacade into the shared product write entry (or a new
ProductCacheEvictor invoked by all mutators), and ensure
ProductService.increaseLikes, ProductService.decreaseLikes and
ProductService.deleteByBrandId call that evictor (or publish a domain event
handled by the evictor). Also add integration tests that assert by-likes
reflects immediate like changes and that latest/by-brand lists drop products
after deleteByBrandId.

In `@apps/commerce-api/src/main/java/com/loopers/config/CacheConfig.java`:
- Around line 16-23: In CacheConfig update the cacheManager() bean so the
CaffeineCacheManager is transaction-aware by calling setTransactionAware(true)
on the CaffeineCacheManager instance (inside the cacheManager() method in class
CacheConfig), ensuring cache evictions occur after transaction commit; also add
an integration test targeting ProductFacade methods registerProduct,
updateProduct, and deleteProduct that simulates a concurrent read during a
modifying transaction to verify the "products" and "products:brand" caches are
not reloaded with pre-commit state.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1c1a1567-8465-434f-ad6b-a9c2d7fc4253

📥 Commits

Reviewing files that changed from the base of the PR and between 61c48a9 and 54634ae.

⛔ Files ignored due to path filters (3)
  • doc/index-cache/cache-strategy.md is excluded by !**/*.md and included by **
  • doc/index-cache/index-design-and-result.md is excluded by !**/*.md and included by **
  • doc/index-cache/slow-query-before-index.md is excluded by !**/*.md and included by **
📒 Files selected for processing (3)
  • apps/commerce-api/build.gradle.kts
  • apps/commerce-api/src/main/java/com/loopers/application/product/ProductFacade.java
  • apps/commerce-api/src/main/java/com/loopers/config/CacheConfig.java

Comment on lines +30 to 31
@CacheEvict(value = {"products", "products:brand"}, allEntries = true)
@Transactional

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

목록 캐시 무효화가 일부 상품 변경 경로를 놓치고 있다.

현재 무효화는 ProductFacade의 등록·수정·삭제에만 걸려 있다. 그런데 apps/commerce-api/src/main/java/com/loopers/domain/product/ProductService.java:92-110increaseLikes/decreaseLikes는 좋아요 정렬 결과를 바꾸고, apps/commerce-api/src/main/java/com/loopers/application/brand/BrandFacade.java:45-54apps/commerce-api/src/main/java/com/loopers/domain/product/ProductService.java:112-116deleteByBrandId는 목록 포함 대상을 바꾸는데도 이 캐시를 비우지 않는다. 운영에서는 by-likes, latest, by-brand 응답이 최대 5분간 stale 상태로 남는다. 무효화를 ProductFacade가 아니라 상품 쓰기 공통 경로로 올리거나, 도메인 이벤트/전용 evictor로 모아 모든 변경이 한 지점을 통과하게 해야 한다. 좋아요 증가·감소 직후 by-likes 정렬이 즉시 반영되는지, 브랜드 삭제 후 latestby-brand 목록에서 해당 상품이 사라지는지 통합 테스트를 추가해야 한다.

Also applies to: 44-45, 51-52, 66-67, 83-84

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/commerce-api/src/main/java/com/loopers/application/product/ProductFacade.java`
around lines 30 - 31, The current CacheEvict on ProductFacade misses other write
paths so list caches stay stale; update the cache eviction to a single
centralized write path or add eviction hooks in the other mutating flows: add
eviction when likes change and when brand deletes affect products. Concretely,
either move `@CacheEvict`(value = {"products","products:brand"}, allEntries=true)
out of ProductFacade into the shared product write entry (or a new
ProductCacheEvictor invoked by all mutators), and ensure
ProductService.increaseLikes, ProductService.decreaseLikes and
ProductService.deleteByBrandId call that evictor (or publish a domain event
handled by the evictor). Also add integration tests that assert by-likes
reflects immediate like changes and that latest/by-brand lists drop products
after deleteByBrandId.

Comment on lines +16 to +23
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager("products", "products:brand");
cacheManager.setCaffeine(Caffeine.newBuilder()
.expireAfterWrite(5, TimeUnit.MINUTES)
.maximumSize(100)
);
return cacheManager;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In Spring Framework, when a @transactionalmethod also uses@CacheEvict with CaffeineCacheManager, are evictions deferred until transaction commit by default? What is the default behavior and effect of CaffeineCacheManager#setTransactionAware?

💡 Result:

No. With Spring’s caching abstraction, a @CacheEvict on a @Transactional method is not deferred to transaction commit by default; the eviction is performed when the cache advice runs (by default after the method returns, but still before the surrounding transaction commits). If the transaction later rolls back, the cache eviction is not automatically rolled back.

What setTransactionAware does (and its default):

  • setTransactionAware(false) (default) means cache put/evict/clear happen immediately (not synchronized with the transaction). [1]
  • setTransactionAware(true) makes Spring expose transaction-aware cache decorators so that put/evict/clear are executed only in the “after-commit” phase of a successful Spring-managed transaction; if there is no transaction, they execute immediately. [1][2]

So enabling transaction awareness is what makes evictions effectively “deferred until commit” (and skipped on rollback). [1][2]

Sources

[1] Spring Framework Javadoc — AbstractTransactionSupportingCacheManager#setTransactionAware (default false; after-commit semantics) (docs.spring.io)
[2] Spring Framework Javadoc — TransactionAwareCacheDecorator (put/evict/clear performed after commit) (docs.spring.io)

Citations:


🏁 Script executed:

# Search for ProductFacade and its use of `@CacheEvict`
fd -t f "ProductFacade" | head -20

Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java

Length of output: 167


🏁 Script executed:

# Search for `@CacheEvict` usage in the codebase
rg "@CacheEvict" --type java -A 2 -B 2

Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java

Length of output: 1869


🏁 Script executed:

# Check CaffeineCacheManager inheritance and setTransactionAware availability
rg "CaffeineCacheManager" --type java -A 5 -B 1

Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java

Length of output: 1640


트랜잭션 커밋 전에 캐시가 비워져 오래된 목록이 다시 적재될 수 있다.

현재 구조에서는 ProductFacaderegisterProduct, updateProduct, deleteProduct 메서드가 @Transactional@CacheEvict를 함께 사용하므로, 캐시 무효화가 커밋 이후로 지연되지 않는다. 기본값으로는 메서드 반환 직후 캐시가 즉시 비워지지만 여전히 트랜잭션 커밋 전이므로, 이 사이에 다른 요청이 "products"와 "products:brand" 캐시를 조회하면 이전 DB 상태를 캐시에 재적재하게 된다. 운영에서는 수정·삭제 직후에도 최대 TTL(5분) 동안 stale 목록이 제공되는 문제로 이어진다.

CaffeineCacheManagersetTransactionAware(true)를 설정하여 커밋 후 단계에서만 무효화가 수행되도록 보장해야 한다. 이와 함께 register/update/delete 중 하나를 의도적으로 지연시키는 통합 테스트를 추가하여, 동시 조회가 커밋 전 상태를 캐시에 재적재하지 않는지 검증해야 한다.

수정 예시
 `@Bean`
 public CacheManager cacheManager() {
     CaffeineCacheManager cacheManager = new CaffeineCacheManager("products", "products:brand");
+    cacheManager.setTransactionAware(true);
     cacheManager.setCaffeine(Caffeine.newBuilder()
             .expireAfterWrite(5, TimeUnit.MINUTES)
             .maximumSize(100)
     );
     return cacheManager;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/commerce-api/src/main/java/com/loopers/config/CacheConfig.java` around
lines 16 - 23, In CacheConfig update the cacheManager() bean so the
CaffeineCacheManager is transaction-aware by calling setTransactionAware(true)
on the CaffeineCacheManager instance (inside the cacheManager() method in class
CacheConfig), ensuring cache evictions occur after transaction commit; also add
an integration test targeting ProductFacade methods registerProduct,
updateProduct, and deleteProduct that simulates a concurrent read during a
modifying transaction to verify the "products" and "products:brand" caches are
not reloaded with pre-commit state.

@loopers-len

Copy link
Copy Markdown
Contributor

@coderabbitai 아래 체크리스트에 충족될 수 있도록 구현되었는지 확인해.

🔖 Index

  • 상품 목록 API에서 brandId 기반 검색, 좋아요 순 정렬 등을 처리했다
  • 조회 필터, 정렬 조건별 유즈케이스를 분석하여 인덱스를 적용하고 전 후 성능비교를 진행했다

❤️ Structure

  • 상품 목록/상세 조회 시 좋아요 수를 조회 및 좋아요 순 정렬이 가능하도록 구조 개선을 진행했다
  • 좋아요 적용/해제 진행 시 상품 좋아요 수 또한 정상적으로 동기화되도록 진행하였다

⚡ Cache

  • Redis 캐시를 적용하고 TTL 또는 무효화 전략을 적용했다
  • 캐시 미스 상황에서도 서비스가 정상 동작하도록 처리했다.

@coderabbitai

coderabbitai Bot commented Mar 13, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check overall project structure
find apps/commerce-api/src -name "*.java" | sort

echo "=== ProductFacade ==="
find . -name "ProductFacade.java" | xargs cat

echo "=== CacheConfig ==="
find . -name "CacheConfig.java" | xargs cat

echo "=== ProductService ==="
find . -name "ProductService.java" | xargs cat

echo "=== ProductRepository ==="
find . -name "ProductRepository.java" | xargs cat

echo "=== ProductQueryService ==="
find . -name "ProductQueryService.java" | xargs cat

echo "=== Product entity ==="
find . -name "Product.java" | xargs cat

echo "=== Like/Heart related files ==="
find . -name "*Like*.java" -o -name "*Heart*.java" -o -name "*like*.java" -o -name "*heart*.java" | sort
find . -name "*Like*.java" -o -name "*Heart*.java" | xargs cat 2>/dev/null

echo "=== Migration/DDL files ==="
find . -name "*.sql" | xargs cat 2>/dev/null
find . -name "V*.sql" | xargs cat 2>/dev/null

echo "=== build.gradle.kts ==="
find . -name "build.gradle.kts" | xargs cat

echo "=== Check for likes_count field ==="
rg -n "likes" --type java

echo "=== Check for Redis dependency ==="
rg -n "redis" -i 

echo "=== Check for `@Cacheable` usage ==="
rg -n "Cacheable\|CacheEvict" --type java

Length of output: 78375


@loopers-len 체크리스트 기준으로 구현 현황을 분석한 결과다.


🔖 Index

항목 상태 근거
brandId 기반 검색, 좋아요 순 정렬 처리 ProductV1Controllerlikes_desc 정렬 파라미터 처리, ProductFacade.getProductsByBrandId() 구현 확인
인덱스 적용 및 전/후 성능 비교 ⚠️ 부분 충족 PR 설명에 3개 인덱스(idx_products_deleted_created, idx_products_brand_deleted_price, idx_products_deleted_likes) 및 23s → 0.003s 성능 비교가 기술되어 있으나, 코드베이스 내 SQL 마이그레이션 파일(Flyway/Liquibase 등)이 존재하지 않는다. DDL이 수동 적용된 것으로 보이며, 재현 가능한 형태로 관리되지 않고 있다.

❤️ Structure

항목 상태 근거
상품 목록/상세에 좋아요 수 조회 및 정렬 Product 엔티티에 likes_count 컬럼 존재, ProductInfo/ProductV1DtolikesCount 포함
좋아요 적용/해제 시 상품 좋아요 수 동기화 ProductLikeFacade에서 registerLike()increaseLikes(), cancelLike()decreaseLikes() 호출. ProductService에서 비관적 락(findByIdWithLock)으로 동시성 제어, ProductLikeConcurrencyTest(50명 동시 테스트)로 검증 완료

⚡ Cache

항목 상태 근거
Redis 캐시 적용 및 TTL/무효화 전략 미충족 CacheConfig에서 Caffeine 로컬 캐시(TTL 5분, max 100)를 사용하고 있다. modules:redis 의존성이 프로젝트에 존재함에도 캐시에 Redis를 사용하지 않았다. 체크리스트 요구사항인 Redis 캐시 조건을 충족하지 못한다.
캐시 미스 시 정상 동작 ⚠️ 부분 충족 Spring Cache 추상화(@Cacheable)로 캐시 미스 시 자동으로 DB 조회가 수행되어 기능적으로는 동작한다. 그러나 캐시 미스 시나리오를 명시적으로 검증하는 테스트가 없다.

종합

  • 주요 미충족 항목: 캐시 구현에 Redis가 아닌 Caffeine을 사용한 점이 체크리스트 요구사항과 다르다.
  • 보완 필요 사항:
    1. CacheConfigRedisCacheManager 기반으로 교체하고 TTL/무효화 전략을 Redis 설정으로 전환해야 한다.
    2. SQL 마이그레이션 파일로 인덱스 DDL을 코드베이스에 포함해야 재현 가능하다.
    3. 캐시 미스 상황에서 서비스가 정상 동작하는지 확인하는 테스트를 추가해야 한다.

🧠 Learnings used
Learnt from: ukukdin
Repo: Loopers-dev-lab/loop-pack-be-l2-vol3-java PR: 203
File: apps/commerce-api/src/main/java/com/loopers/application/product/ProductQueryService.java:53-55
Timestamp: 2026-03-13T05:44:03.072Z
Learning: In Loopers-dev-lab/loop-pack-be-l2-vol3-java PR `#203`, the PRODUCT_LIST cache eviction strategy in ProductService.evictProductListAfterCommit() intentionally uses cache.clear() (full cache invalidation) rather than selective eviction by brandId. This is a deliberate trade-off: TTL is 1 minute and admin write frequency is currently low, so full invalidation is considered acceptable. Selective eviction by brandId is deferred until admin modifications become frequent or cache hit rates are measured to be low via Micrometer metrics.

@move-wook
move-wook merged commit dbe01a0 into Loopers-dev-lab:move-wook Mar 16, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants