Skip to content

[Volume 5] 인덱스와 캐시를 활용한 조회 읽기 성능 최적화#223

Open
dd-jiny wants to merge 5 commits intoLoopers-dev-lab:dd-jinyfrom
dd-jiny:volume-5
Open

[Volume 5] 인덱스와 캐시를 활용한 조회 읽기 성능 최적화#223
dd-jiny wants to merge 5 commits intoLoopers-dev-lab:dd-jinyfrom
dd-jiny:volume-5

Conversation

@dd-jiny
Copy link

@dd-jiny dd-jiny commented Mar 13, 2026

📌 Summary

  • 배경: 10만건 상품 데이터에서 주요 쿼리가 풀스캔 + filesort로 동작하고, 반복 조회 시 매번 DB 접근이 발생하여 성능 저하 우려
  • 목표: (1) EXPLAIN 기반 12건 쿼리 실험으로 최적 인덱스 확정, (2) L2(Redis) + L1(Caffeine) 2-Level 캐시 구현 및 부하 테스트 검증
  • 결과:
    • 인덱스: 9개 확정 / 6개 불필요 판정. 주요 쿼리 최대 109배 개선
    • 캐시: 10개 캐시 구성 (4개 L1+L2, 6개 L2-only). k6 부하 테스트로 L2 vs L1+L2 비교 분석 완료

🧭 Context & Decision

문제 정의

인덱스

  • 현재 동작/제약: PK/UNIQUE 외 보조 인덱스가 없는 상태. 고객 조회 쿼리(WHERE del_yn + display_status + sale_status + ORDER BY)가 매번 풀스캔 94,664건 + filesort 수행
  • 문제(또는 리스크): 데이터 증가 시 응답 시간 선형 악화. 배치(만료 주문 조회), NFR-007(PENDING 건수 제한) 등 운영 쿼리도 풀스캔
  • 성공 기준: 주요 쿼리의 EXPLAIN type이 ALL → ref/range로 개선, filesort 제거 확인

캐시

  • 현재 동작/제약: 동일 상품 반복 조회, 인증 정보 반복 검증 등 매 요청마다 DB 접근. 트래픽 증가 시 DB 커넥션 풀 포화 위험
  • 문제(또는 리스크): Hot Key(인기 상품) 집중 조회 시 DB 부하 집중. 목록 쿼리의 keyword 무한 조합으로 인한 캐시 키 폭발 가능성
  • 성공 기준: 캐시 hit 시 DB 접근 제거, Redis 장애 시에도 서비스 정상 동작, 오버셀(초과 판매) 없음

선택지와 결정

인덱스

  • 고려한 대안:
    • A: WHERE 컬럼만 인덱스 (3컬럼) — filesort 잔존
    • B: WHERE + ORDER BY 컬럼 포함 복합 인덱스 (4컬럼) — filesort 제거
    • C: 정렬별 전용 인덱스 3개 (created_at, like_count, price) — 쓰기 비용 증가
  • 최종 결정: 정렬 인덱스 2개(created_at, like_count) + price는 filesort 감수 (후보 B 기반, C의 price 인덱스 제외)
  • 트레이드오프:
    • price 정렬은 47,332건 top-N filesort 허용 (likeCount INCREMENT 고빈도 → 인덱스 3개의 쓰기 부담 회피)
    • 실험 3/5에서 결과 행 수 만건 이상일 때 인덱스 Random I/O가 순차 풀스캔보다 느린 현상 확인 → 시드 데이터 분포 특성이며 운영에서는 개선 예상
  • 추후 개선 여지: price 정렬 트래픽 급증 시 전용 인덱스 추가 검토

캐시

  • 고려한 대안:
    • A: L2(Redis)-only — 단순한 구조, 모든 캐시가 Redis 경유
    • B: L1(Caffeine) + L2(Redis) 2-Level — Hot Key에서 Redis RTT 제거, 복잡도 증가
    • C: 상품 목록 전체 캐싱 — keyword × brandId × sort × page 조합으로 키 폭발 (22,950+ 키)
  • 최종 결정:
    • L1+L2 2-Level 캐시: productDetail, statsOverview, statsTopLiked, statsTopOrdered 4개만 L1 적용
    • 상품 목록은 "대표 캐싱": 무필터 + 첫 페이지 + 3개 정렬만 캐시 (최대 150키). keyword 검색은 캐시 미적용
    • 상품(content)과 재고(stock) 캐시 분리: 재고 변경(30초 TTL)이 상품 캐시(10분 TTL)를 무효화하지 않음
  • 트레이드오프:
    • L1은 단일 인스턴스에서만 일관성 보장. 멀티 인스턴스 배포 시 Redis Pub/Sub 기반 무효화 필요
    • stockAvailable은 L2-only (30초 TTL) — 실시간 재고 정합성과 캐시 효율의 균형
    • authUser는 L2-only — 보안 민감 데이터의 L1 캐시 stale 위험 방지
  • 추후 개선 여지: 멀티 인스턴스 시 Redis Pub/Sub L1 무효화, Elasticsearch로 keyword 검색 분리

🏗️ Design Overview

변경 범위

  • 영향 받는 모듈/도메인:
    • 인덱스: products, orders, brands, order_items 테이블 (DDL)
    • 캐시: Product, Brand, Stock, User, Stats 도메인 서비스 + ProductFacade
  • 신규 추가:
    • 9개 보조 인덱스 (DDL)
    • CacheConfig, TwoLevelCache, TwoLevelCacheManager (infrastructure/cache)
    • RedisCacheIntegrationTest, TwoLevelCacheIntegrationTest, CacheBenchmarkTest
  • 제거/대체: 기존 설계 문서의 중복 인덱스 6개 불필요 판정

확정 인덱스 목록 (9개)

우선순위 인덱스명 설명 활용 메서드 개선율
CRITICAL idx_products_status_created 고객 상품 목록 최신순 정렬 조회 ProductJpaRepository.findAllForCustomerPaged() 72x
CRITICAL idx_products_status_like 고객 상품 목록 좋아요순 정렬 조회 ProductJpaRepository.findAllForCustomerPaged() 109x
CRITICAL idx_orders_status_del_expires 배치 만료 주문 조회 (PENDING + 만료시간 초과) OrderJpaRepository.findExpiredPendingOrders()
HIGH idx_products_brand_del 브랜드별 상품 필터링 ProductJpaRepository.findAllByBrandId() 24x
HIGH idx_orders_user_created 사용자 주문 목록 (기간별, 최신순) OrderJpaRepository.findAllByUserIdAndPeriod() 43x
HIGH idx_orders_user_status 사용자 PENDING 주문 건수 제한 (Covering Index) OrderJpaRepository.countByUserIdAndStatus() 57x
HIGH idx_orders_del_created 관리자 통계 기간별 주문 집계 OrderJpaRepository.findAllByPeriod() 1.5x
MEDIUM idx_brands_del_display 고객 브랜드 목록 조회 BrandJpaRepository.findAllByDelYnAndDisplayStatus()
MEDIUM idx_order_items_del_product 관리자 통계 인기 주문 상품 TOP OrderItemJpaRepository.findAllByOrderIdIn()

캐시 적용 클래스 및 메서드

캐시명 설명 L1 TTL @Cacheable @CacheEvict
productDetail 상품 상세 조회 (Hot Key, 인기 상품 집중) 10분 ProductService.findById() updateProduct(), deleteProduct(), changeSaleStatus(), incrementLikeCount(), decrementLikeCount()
stockAvailable 가용 재고 조회 (변경 빈번, 정합성 중요) 30초 StockService.findByProductId() hold(), release(), commit()
brandDetail 브랜드 상세 조회 (변경 드묾) 30분 BrandService.findById() updateBrand(), deleteBrand()
authUser 로그인 인증 사용자 조회 (보안 민감) 1분 UserService.findByLoginId() changePassword()
productList 상품 목록 대표 캐싱 (키 폭발 방지) 5분 ProductFacade.getCachedProductList() createProduct(), updateProduct()allEntries=true
statsOverview 주문 현황 통계 (읽기 전용) 5분 StatsService.getOverview() TTL 만료만
statsDaily 일별 주문 통계 (읽기 전용) 5분 StatsService.getDailyOrderStats() TTL 만료만
statsTopLiked 좋아요 TOP 상품 (읽기 전용) 5분 StatsService.getTopLikedProducts() TTL 만료만
statsTopOrdered 주문 TOP 상품 (읽기 전용) 5분 StatsService.getTopOrderedProducts() TTL 만료만
statsLowStock 재고 부족 상품 (읽기 전용) 5분 StatsService.getLowStockProducts() TTL 만료만

주요 컴포넌트 책임

  • CacheConfig: 캐시 매니저 구성 (Redis + Caffeine), 캐시별 TTL 설정, CacheErrorHandler로 Redis 장애 시 graceful degradation
  • TwoLevelCache: L1 → L2 순차 조회, 양방향 put/evict 동기화
  • TwoLevelCacheManager: 캐시 이름 기반으로 L1+L2 또는 L2-only 라우팅

🔍 리뷰 포인트

캐시 전략에 대해 리뷰 부탁드립니다. 각 조회 패턴별로 어떤 전략을 적용했는지 정리하고, 판단이 어려웠던 부분을 질문으로 남겼습니다.

조회 패턴별 캐시 전략 요약

조회 패턴 적용 전략 선택 이유
상품 상세 (Hot Key, 반복 조회 높음) L1(Caffeine 2분) + L2(Redis 10분) Zipf 분포로 인기 상품에 트래픽 집중 → L1으로 Redis RTT 제거
가용 재고 (변경 빈번, 정합성 중요) L2-only (Redis 30초) hold/release마다 evict 발생 → 짧은 TTL + L1 미적용
상품 목록 (파라미터 조합 多) L2-only (Redis 5분), 대표 키만 캐싱 keyword 포함 시 키 폭발 → 무필터+첫 페이지+3정렬만 캐싱
브랜드 상세 (변경 드묾) L2-only (Redis 30분) 변경 빈도 낮아 긴 TTL로 충분, L1 불필요

리뷰 요청 사항

# 조회 패턴 질문
1 상품 상세 + 재고 같은 상품 상세 API에서 상품(10분 TTL)과 재고(30초 TTL)를 별도 캐시로 조회합니다. 재고가 만료되어 갱신되었지만 상품 정보는 이전 캐시가 남아있는 상황이 발생할 수 있는데, 이 TTL 차이로 인한 데이터 freshness 불일치가 UX 관점에서 허용 가능한지 의견 부탁드립니다.
2 상품 목록 (대표 캐싱) 키 폭발 방지를 위해 무필터+첫 페이지(page=0)+3개 정렬 조합만 캐싱하고, keyword 검색이나 2페이지 이후는 캐시를 적용하지 않았습니다. 또한 상품 1개 수정 시 allEntries=true로 목록 캐시를 전부 날리는데, 관리자 수정이 잦을 경우 hit rate가 떨어질 수 있습니다. 이 전략이 적절한지 피드백 부탁드립니다.

|

🔁 Flow Diagram

인덱스 실험 프로세스

flowchart TD
    A[인덱스 없는 상태에서 EXPLAIN] --> B[후보 A 생성 → EXPLAIN → 드랍]
    B --> C[후보 B 생성 → EXPLAIN → 드랍]
    C --> D{후보 비교}
    D -->|filesort 제거 + filtered 100%| E[최적 인덱스 확정]
    D -->|트레이드오프 존재| F[쓰기 비용 vs 읽기 성능 판단]
    F --> E
    E --> G[SHOW PROFILES로 실행시간 비교]
Loading

캐시 조회 흐름 (L1+L2)

sequenceDiagram
    autonumber
    participant Client
    participant Controller
    participant Service
    participant L1 as L1 (Caffeine)
    participant L2 as L2 (Redis)
    participant DB as MySQL

    Client->>Controller: GET /api/v1/products/{id}
    Controller->>Service: findById(id)

    Note over Service,L1: @Cacheable("productDetail")
    Service->>L1: get(key)
    alt L1 Hit
        L1-->>Service: cached value
    else L1 Miss
        Service->>L2: get(key)
        alt L2 Hit
            L2-->>Service: cached value
            Service->>L1: put(key, value)
        else L2 Miss
            Service->>DB: SELECT * FROM products
            DB-->>Service: result
            Service->>L2: put(key, value)
            Service->>L1: put(key, value)
        end
    end
    Service-->>Controller: ProductModel
    Controller-->>Client: ApiResponse
Loading

캐시 무효화 흐름

sequenceDiagram
    autonumber
    participant Admin
    participant Controller
    participant Facade
    participant Service
    participant L1 as L1 (Caffeine)
    participant L2 as L2 (Redis)

    Admin->>Controller: PUT /api-admin/v1/products/{id}
    Controller->>Facade: updateProduct(id, command)

    Note over Facade,L2: @CacheEvict("productList", allEntries=true)
    Facade->>Service: updateProduct(id, command)

    Note over Service,L2: @CacheEvict("productDetail", key="#productId")
    Service->>L1: evict(productDetail::id)
    Service->>L2: evict(productDetail::id)
    Facade->>L1: clear(productList)
    Facade->>L2: clear(productList)

    Facade-->>Controller: updated
    Controller-->>Admin: ApiResponse
Loading

📊 성능 비교 결과

인덱스 실행 시간 (Before → After)

products 쿼리

쿼리 Before (ms) After (ms) 개선율 비고
실험 1 (최신순 LIMIT 20) 340.9 4.7 72x filesort 제거, 인덱스 순서 20건만 읽음
실험 2 (좋아요순 LIMIT 20) 207.8 1.9 109x filesort 제거, 인덱스 순서 20건만 읽음
실험 3 (가격순 LIMIT 20) 289.5 906.7 -3.1x (악화) 전용 인덱스 미생성 — Random I/O > 순차 스캔
실험 4 (브랜드 필터 LIMIT 20) 170.0 7.2 24x brand_id 인덱스로 ~984건 축소

orders 쿼리

쿼리 Before (ms) After (ms) 개선율 비고
실험 5 (만료 배치) 99.3 513.3 -5.2x (악화) 시드 데이터 PENDING 비율 과다 — 운영에서는 소수이므로 개선 예상
실험 6 (사용자 주문 목록) 89.8 2.1 43x user_id + created_at 인덱스 22건 직접 조회
실험 7 (PENDING 건수) 51.3 0.9 57x Covering Index — 테이블 접근 없음
실험 8 (통계 기간 집계) 92.7 60.9 1.5x range 스캔으로 23,868건 축소

악화 원인

  • 실험 3/5 공통: 결과 행 수가 만건 이상일 때, 인덱스 경유 Random I/O가 순차 풀스캔보다 느림
  • 실험 3: 전용 price 인덱스 미생성 결정(쓰기 비용 회피). 55,010건 인덱스 ref → 테이블 랜덤 접근이 풀스캔보다 느림
  • 실험 5: 시드 데이터 status 균등 분포(PENDING 33%)가 비현실적. 운영에서 PENDING은 소수이므로 인덱스 효과 기대

k6 부하 테스트 — L2-only vs L1+L2 비교

환경: WSL2 Ubuntu, Docker MySQL/Redis/k6, 시드 10만 상품

시나리오 메트릭 L2-only L1+L2 L1+L2 이점
Steady (10VU, 3분) p50 33ms 27ms 17% 개선
p95 1,130ms 1,090ms 3.5% 개선
RPS 35.2 37.4 6.3% 향상
Spike (200VU, 55초) p50 183ms 178ms 2.8% 개선
p95 829ms 1,050ms L2-only 우위
RPS 427 375 L2-only 우위
Mixed (50VU, 5분) readers p50 91ms 82ms 10% 개선
readers p95 205ms 227ms L2-only 우위
5xx 에러 0 0 동일
List-Cache (10VU, 2분) p50 813ms 727ms 11% 개선
p95 1,820ms 1,640ms 10% 개선

비교 분석 요약

  • p50 (중앙값): L1+L2가 모든 시나리오에서 9~17% 빠름 — Caffeine L1 hit로 Redis RTT 제거 효과
  • p95 (꼬리 지연): 200VU 스파이크에서 L2-only가 더 안정적 — 단일 인스턴스 JVM 메모리 압박 + GC 오버헤드
  • 권장안: 현재 규모(단일 인스턴스, <100 RPS)에서는 L2-only 충분. 멀티 인스턴스 100+ RPS부터 L1+L2 도입 유의미

테스트 환경 제약 (WSL2)

  • WSL2는 Hyper-V 기반 경량 VM으로 네이티브 Linux 대비 네트워크 RTT 25배, 디스크 I/O 310배 느림
  • VU 수를 계획 대비 축소 실행 (Steady: 50→10, Spike: 1000→200, List-Cache: 30→10)
  • Soak 테스트(100VU × 30분) 미실행 — 10VU에서도 p95=1초 초과하여 100VU 실행 시 OOM/커넥션 풀 고갈 위험
  • Grafana 스크린샷 미수집 — CLI 환경에서 GUI 브라우저 접근 불가

@coderabbitai
Copy link

coderabbitai bot commented Mar 13, 2026

Important

Review skipped

Too many files!

This PR contains 191 files, which is 41 over the limit of 150.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f2926346-0549-41bc-aa05-ae5d9bf6f8e0

📥 Commits

Reviewing files that changed from the base of the PR and between da02b23 and eb3b92c.

⛔ Files ignored due to path filters (2)
  • .claude/skills/anaylize-query/SKILL.md is excluded by !**/*.md and included by **
  • CLAUDE.md is excluded by !**/*.md and included by **
📒 Files selected for processing (191)
  • .claude/settings.local.json
  • apps/commerce-api/build.gradle.kts
  • apps/commerce-api/src/main/java/com/loopers/application/brand/BrandAppService.java
  • apps/commerce-api/src/main/java/com/loopers/application/brand/BrandInfo.java
  • apps/commerce-api/src/main/java/com/loopers/application/cart/CartAppService.java
  • apps/commerce-api/src/main/java/com/loopers/application/cart/CartFacade.java
  • apps/commerce-api/src/main/java/com/loopers/application/cart/CartInfo.java
  • apps/commerce-api/src/main/java/com/loopers/application/like/LikeAppService.java
  • apps/commerce-api/src/main/java/com/loopers/application/like/LikeInfo.java
  • apps/commerce-api/src/main/java/com/loopers/application/order/OrderAppService.java
  • apps/commerce-api/src/main/java/com/loopers/application/order/OrderFacade.java
  • apps/commerce-api/src/main/java/com/loopers/application/order/OrderInfo.java
  • apps/commerce-api/src/main/java/com/loopers/application/product/ProductAppService.java
  • apps/commerce-api/src/main/java/com/loopers/application/product/ProductCreateCommand.java
  • apps/commerce-api/src/main/java/com/loopers/application/product/ProductFacade.java
  • apps/commerce-api/src/main/java/com/loopers/application/product/ProductInfo.java
  • apps/commerce-api/src/main/java/com/loopers/application/product/ProductRevisionInfo.java
  • apps/commerce-api/src/main/java/com/loopers/application/product/ProductUpdateCommand.java
  • apps/commerce-api/src/main/java/com/loopers/application/stats/StatsAppService.java
  • apps/commerce-api/src/main/java/com/loopers/application/stats/StatsInfo.java
  • apps/commerce-api/src/main/java/com/loopers/application/user/UserAppService.java
  • apps/commerce-api/src/main/java/com/loopers/application/user/UserInfo.java
  • apps/commerce-api/src/main/java/com/loopers/batch/OrderExpiryScheduler.java
  • apps/commerce-api/src/main/java/com/loopers/domain/brand/BrandModel.java
  • apps/commerce-api/src/main/java/com/loopers/domain/brand/BrandRepository.java
  • apps/commerce-api/src/main/java/com/loopers/domain/brand/BrandService.java
  • apps/commerce-api/src/main/java/com/loopers/domain/cart/CartItemId.java
  • apps/commerce-api/src/main/java/com/loopers/domain/cart/CartItemModel.java
  • apps/commerce-api/src/main/java/com/loopers/domain/cart/CartItemRepository.java
  • apps/commerce-api/src/main/java/com/loopers/domain/cart/CartService.java
  • apps/commerce-api/src/main/java/com/loopers/domain/coupon/CouponModel.java
  • apps/commerce-api/src/main/java/com/loopers/domain/coupon/CouponRepository.java
  • apps/commerce-api/src/main/java/com/loopers/domain/coupon/CouponService.java
  • apps/commerce-api/src/main/java/com/loopers/domain/coupon/UserCouponModel.java
  • apps/commerce-api/src/main/java/com/loopers/domain/coupon/UserCouponRepository.java
  • apps/commerce-api/src/main/java/com/loopers/domain/like/LikeId.java
  • apps/commerce-api/src/main/java/com/loopers/domain/like/LikeModel.java
  • apps/commerce-api/src/main/java/com/loopers/domain/like/LikeRepository.java
  • apps/commerce-api/src/main/java/com/loopers/domain/like/LikeService.java
  • apps/commerce-api/src/main/java/com/loopers/domain/order/OrderCartRestoreModel.java
  • apps/commerce-api/src/main/java/com/loopers/domain/order/OrderCartRestoreRepository.java
  • apps/commerce-api/src/main/java/com/loopers/domain/order/OrderItemCommand.java
  • apps/commerce-api/src/main/java/com/loopers/domain/order/OrderItemId.java
  • apps/commerce-api/src/main/java/com/loopers/domain/order/OrderItemModel.java
  • apps/commerce-api/src/main/java/com/loopers/domain/order/OrderItemRepository.java
  • apps/commerce-api/src/main/java/com/loopers/domain/order/OrderItemSnapshot.java
  • apps/commerce-api/src/main/java/com/loopers/domain/order/OrderModel.java
  • apps/commerce-api/src/main/java/com/loopers/domain/order/OrderRepository.java
  • apps/commerce-api/src/main/java/com/loopers/domain/order/OrderService.java
  • apps/commerce-api/src/main/java/com/loopers/domain/product/ProductModel.java
  • apps/commerce-api/src/main/java/com/loopers/domain/product/ProductRepository.java
  • apps/commerce-api/src/main/java/com/loopers/domain/product/ProductRevisionId.java
  • apps/commerce-api/src/main/java/com/loopers/domain/product/ProductRevisionModel.java
  • apps/commerce-api/src/main/java/com/loopers/domain/product/ProductRevisionRepository.java
  • apps/commerce-api/src/main/java/com/loopers/domain/product/ProductService.java
  • apps/commerce-api/src/main/java/com/loopers/domain/product/ProductStockModel.java
  • apps/commerce-api/src/main/java/com/loopers/domain/product/ProductStockRepository.java
  • apps/commerce-api/src/main/java/com/loopers/domain/product/StockService.java
  • apps/commerce-api/src/main/java/com/loopers/domain/stats/StatsProjection.java
  • apps/commerce-api/src/main/java/com/loopers/domain/stats/StatsService.java
  • apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java
  • apps/commerce-api/src/main/java/com/loopers/domain/user/UserRepository.java
  • apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/brand/BrandJpaRepository.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/brand/BrandRepositoryImpl.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/cache/CacheConfig.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/cache/TwoLevelCache.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/cache/TwoLevelCacheManager.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/cart/CartItemJpaRepository.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/cart/CartItemRepositoryImpl.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/coupon/CouponJpaRepository.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/coupon/CouponRepositoryImpl.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/coupon/UserCouponJpaRepository.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/coupon/UserCouponRepositoryImpl.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/like/LikeJpaRepository.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/like/LikeRepositoryImpl.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/order/OrderCartRestoreJpaRepository.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/order/OrderCartRestoreRepositoryImpl.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/order/OrderItemJpaRepository.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/order/OrderItemRepositoryImpl.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/order/OrderJpaRepository.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/order/OrderRepositoryImpl.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductJpaRepository.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductRepositoryImpl.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductRevisionJpaRepository.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductRevisionRepositoryImpl.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductStockJpaRepository.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductStockRepositoryImpl.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserJpaRepository.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserRepositoryImpl.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/api/AuthUser.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/api/AuthUserArgumentResolver.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/api/CustomerAuthInterceptor.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/api/PageResponse.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/api/brand/BrandV1Controller.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/api/brand/BrandV1Dto.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/api/cart/CartV1Controller.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/api/cart/CartV1Dto.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/api/coupon/CouponV1Controller.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/api/coupon/CouponV1Dto.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/api/like/LikeV1Controller.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/api/like/LikeV1Dto.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/api/order/OrderV1Controller.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/api/order/OrderV1Dto.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/api/product/ProductV1Controller.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/api/product/ProductV1Dto.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Dto.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/apiadmin/AdminBrandV1Controller.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/apiadmin/AdminBrandV1Dto.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/apiadmin/AdminCartV1Controller.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/apiadmin/AdminCartV1Dto.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/apiadmin/AdminCouponV1Controller.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/apiadmin/AdminCouponV1Dto.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/apiadmin/AdminOrderV1Controller.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/apiadmin/AdminOrderV1Dto.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/apiadmin/AdminProductV1Controller.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/apiadmin/AdminProductV1Dto.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/apiadmin/AdminStatsV1Controller.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/apiadmin/AdminStatsV1Dto.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/config/WebMvcConfig.java
  • apps/commerce-api/src/main/java/com/loopers/support/enums/DiscountType.java
  • apps/commerce-api/src/main/java/com/loopers/support/enums/UserCouponStatus.java
  • apps/commerce-api/src/main/java/com/loopers/support/error/ErrorType.java
  • apps/commerce-api/src/main/java/com/loopers/support/page/PageQuery.java
  • apps/commerce-api/src/main/java/com/loopers/support/page/PagedResult.java
  • apps/commerce-api/src/test/java/com/loopers/application/cart/CartAppServiceTest.java
  • apps/commerce-api/src/test/java/com/loopers/application/cart/CartFacadeTest.java
  • apps/commerce-api/src/test/java/com/loopers/application/order/OrderAppServiceTest.java
  • apps/commerce-api/src/test/java/com/loopers/application/order/OrderFacadeTest.java
  • apps/commerce-api/src/test/java/com/loopers/application/product/ProductAppServiceTest.java
  • apps/commerce-api/src/test/java/com/loopers/application/product/ProductFacadeTest.java
  • apps/commerce-api/src/test/java/com/loopers/batch/OrderExpirySchedulerTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/brand/BrandServiceTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/cart/CartItemModelTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/cart/CartServiceTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/coupon/CouponIssuanceConcurrencyTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/coupon/CouponModelTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/coupon/CouponServiceTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/coupon/OrderCouponConcurrencyTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/coupon/UserCouponModelTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/like/LikeConcurrencyTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/like/LikeModelTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/like/LikeServiceTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/order/OrderCartRestoreIdempotencyTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/order/OrderCartRestoreModelTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/order/OrderItemModelTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/order/OrderModelTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/order/OrderServiceTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/product/ProductModelTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/product/ProductRevisionModelTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/product/ProductServiceTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/product/ProductStockModelTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/product/StockConcurrencyTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/product/StockServiceTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/stats/StatsServiceTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceTest.java
  • apps/commerce-api/src/test/java/com/loopers/infrastructure/brand/BrandRepositoryImplTest.java
  • apps/commerce-api/src/test/java/com/loopers/infrastructure/cache/CacheBenchmarkTest.java
  • apps/commerce-api/src/test/java/com/loopers/infrastructure/cache/RedisCacheIntegrationTest.java
  • apps/commerce-api/src/test/java/com/loopers/infrastructure/cache/TwoLevelCacheIntegrationTest.java
  • apps/commerce-api/src/test/java/com/loopers/infrastructure/cart/CartItemRepositoryImplTest.java
  • apps/commerce-api/src/test/java/com/loopers/infrastructure/coupon/CouponRepositoryImplTest.java
  • apps/commerce-api/src/test/java/com/loopers/infrastructure/like/LikeRepositoryImplTest.java
  • apps/commerce-api/src/test/java/com/loopers/infrastructure/order/OrderRepositoryImplTest.java
  • apps/commerce-api/src/test/java/com/loopers/infrastructure/product/ProductRepositoryImplTest.java
  • apps/commerce-api/src/test/java/com/loopers/infrastructure/product/ProductStockRepositoryImplTest.java
  • apps/commerce-api/src/test/java/com/loopers/infrastructure/stats/StatsRepositoryImplTest.java
  • apps/commerce-api/src/test/java/com/loopers/infrastructure/user/UserRepositoryImplTest.java
  • apps/commerce-api/src/test/java/com/loopers/integration/FullOrderFlowIntegrationTest.java
  • apps/commerce-api/src/test/java/com/loopers/interfaces/api/ExampleV1ApiE2ETest.java
  • apps/commerce-api/src/test/java/com/loopers/interfaces/api/brand/BrandV1ApiE2ETest.java
  • apps/commerce-api/src/test/java/com/loopers/interfaces/api/coupon/CouponV1ApiE2ETest.java
  • apps/commerce-api/src/test/java/com/loopers/interfaces/api/like/LikeV1ApiE2ETest.java
  • apps/commerce-api/src/test/java/com/loopers/interfaces/apiadmin/AdminCartV1ApiE2ETest.java
  • apps/commerce-api/src/test/java/com/loopers/interfaces/apiadmin/AdminCouponV1ApiE2ETest.java
  • apps/commerce-api/src/test/java/com/loopers/interfaces/apiadmin/AdminOrderV1ApiE2ETest.java
  • apps/commerce-api/src/test/java/com/loopers/interfaces/apiadmin/AdminProductV1ApiE2ETest.java
  • apps/commerce-api/src/test/java/com/loopers/interfaces/apiadmin/AdminStatsV1ApiE2ETest.java
  • build.gradle.kts
  • docs/DDL/index.sql
  • k6/lib/helpers.js
  • k6/scripts/product-list-cache.js
  • k6/scripts/product-mixed.js
  • k6/scripts/product-soak.js
  • k6/scripts/product-spike.js
  • k6/scripts/product-steady.js
  • k6/seed.sh
  • modules/.claude/settings.local.json
  • modules/redis/build.gradle.kts
  • modules/redis/src/main/java/com/loopers/config/redis/RedisConfig.java

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

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.

Tip

CodeRabbit can use Trivy to scan for security misconfigurations and secrets in Infrastructure as Code files.

Add a .trivyignore file to your project to customize which findings Trivy reports.

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