[volume-10] Spring Batch 랭킹 집계 - 김평숙#404
Open
katiekim17 wants to merge 146 commits into
Open
Conversation
- MemberModel 엔티티 및 MemberRepository 인터페이스 추가 - MemberService: 로그인 ID 중복 검증, 비밀번호 규칙 검증, 암호화 저장 - MemberV1Controller: POST /api/v1/members API - PasswordEncoderConfig: BCrypt 설정 - ApiControllerAdvice: @Valid 검증 예외 핸들러 추가 - 단위 테스트 6개 추가 (Service 4개, Controller 2개) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- AuthMember 어노테이션 및 AuthMemberResolver 추가 - 헤더 기반 인증 (X-Loopers-LoginId, X-Loopers-LoginPw) - GET /api/v1/members/me API 추가 - 이름 마스킹 로직 (홍길동 → 홍길*) - ErrorType.UNAUTHORIZED 추가 - 단위 테스트 5개 추가 (Controller 3개, DTO 2개) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- MemberModel에 changePassword() 메서드 추가 - MemberService에 비밀번호 변경 로직 구현 - 현재 비밀번호 검증 - 동일 비밀번호 사용 불가 - 비밀번호 규칙 검증 (8~16자, 영문/숫자/특수문자) - 생년월일 포함 불가 - MemberV1Controller에 PATCH /me/password 엔드포인트 추가 - CLAUDE.md를 .gitignore에 추가 (git 추적 제외) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- MemberModel → Member 엔티티 리네이밍 (DDD 네이밍) - Value Object 도입: LoginId, Email, BirthDate, Password (@embeddable, 자가 검증) - Gender enum 추가 및 회원가입 시 성별 필수 처리 - PasswordPolicy 도메인 정책 분리 (순수 함수) - Service 얇은 조율 계층으로 리팩토링 (검증 로직 VO/Policy로 이동) - 포인트 조회 API 신규 구현 (GET /api/v1/points) - AuthMemberResolver 보안 에러 메시지 통일 - 단위 테스트 (LoginIdTest, EmailTest, BirthDateTest 등) - 통합 테스트 (MemberServiceIntegrationTest, @spybean) - E2E 테스트 (MemberV1ApiE2ETest, PointV1ApiE2ETest) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
DDD 리팩토링 + Value Object 도입 + 포인트 조회 구현
기능 요구사항에 해당하지 않는 Gender enum, 포인트 조회 API를 제거한다. Member 엔티티에서 gender/point 필드를 제거하고 관련 테스트를 정리한다. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- 01-requirements.md: 액터 정의, 미결정 사항 섹션 추가 - 02-sequence-diagrams.md: 트랜잭션 경계 rect 블록, 읽는 법, 잠재 리스크 추가 - 03-class-diagram.md: 다이어그램 읽는 법, 잠재 리스크 추가 - 04-erd.md: 잠재 리스크 섹션 추가 브랜드/상품/좋아요/주문 도메인의 요구사항, 시퀀스, 클래스, ERD 설계 완료 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
판단 기준을 명확히 함: "주문 상세 화면을 독립적으로 렌더링할 수 있는가?" - 필수/권장/제외 항목 분류 및 근거 추가 - image_url 제외 이유 명시 (현재 상품 스펙에 없음, 오버엔지니어링 방지) - 트레이드오프 설명 추가 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- 주문 취소 API 명세 추가 (POST /api/v1/orders/{orderId}/cancel)
- 대고객 브랜드 목록 API 추가 (GET /api/v1/brands)
- order_item 삭제 정책 수정 (Order와 생명주기 공유)
- 시퀀스 다이어그램 URI prefix 통일 (/api-admin/v1)
- 상품 삭제 유스케이스 추가 (US-P05)
- 좋아요 목록 N+1 의도 명시
- 주문 취소 시 삭제된 상품 처리 리스크 추가
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- 주문 취소 API 제거 (요구사항에 없음) - US-O04 유스케이스 제거 - 시퀀스 다이어그램 8번 (주문 취소) 제거 - 대고객 브랜드 목록 API 제거 (요구사항에 없음) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- 도메인 & 객체 설계 전략 (Entity/VO/Domain Service 구분 기준) - 아키텍처 & 패키지 전략 (DIP 실무 타협 기준, 의존 방향) - DIP 인사이트: 정석 vs 실무 타협 정리 (DDD 저자 명언 포함) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- domain/member/MemberService → application/member/MemberFacade - 레이어드 아키텍처 원칙에 맞게 유스케이스 조율을 Application Layer에서 담당 - Controller, 통합 테스트 import 경로 수정 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Brand: Entity + CRUD (삭제 시 연관 상품 cascade soft delete) - Product: Entity + Price/Stock VO + CRUD (N+1 해결: JPQL JOIN) - Like: Entity (hard delete) + 좋아요 등록(멱등)/취소 - Order: Aggregate Root + OrderItem 스냅샷 + 재고 차감 - DIP 적용: Repository Interface(Domain) ← Impl(Infrastructure) - ProductWithBrand 조회 전용 모델로 읽기/쓰기 관심사 분리 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- VO 테스트: Price, Stock (생성 검증, 비즈니스 규칙) - Entity 테스트: Brand, Product, Order, OrderItem, Like - Facade 테스트: Fake Repository 기반 순수 단위 테스트 - BrandFacadeTest, ProductFacadeTest, LikeFacadeTest, OrderFacadeTest - DIP 이점 활용: Spring 컨텍스트 없이 도메인 로직 검증 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
P0 요구사항 미구현 수정:
- 브랜드/상품 삭제 시 좋아요 hard delete 연쇄 처리
- 주문 취소 + 재고 복원 API 추가 (POST /orders/{id}/cancel)
- 좋아요 목록/주문 상세 조회 권한 검증 추가 (FORBIDDEN)
- 좋아요 취소 멱등성 보장 (예외 → 조기 리턴)
P1 설계 결함 수정:
- 상품 목록 정렬 지원 (latest/price_asc/likes_desc)
- findByIdWithBrand LEFT JOIN 조건 버그 수정
P2 테스트 품질 보강:
- Fake Repository soft delete 필터링 반영
- 주문 취소, 연쇄 삭제, 멱등성 등 누락 테스트 추가
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Order.ItemSnapshot record 도입, Order.create()가 스냅샷을 받아 내부에서 OrderItem 생성 - OrderItem 생성자를 package-private로 변경하여 외부 패키지에서 직접 생성 차단 - OrderFacade는 더 이상 OrderItem을 직접 생성하지 않고 ItemSnapshot만 전달 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 레이어드 아키텍처 Mermaid 다이어그램 추가 (Facade별 책임 명시) - 전체 클래스 다이어그램 갱신 (ItemSnapshot, package-private 반영) - Aggregate 라이프사이클 통제 점검 결과 및 Entity vs VO 통제 기준 정리 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
findByIdWithBrand() JOIN 쿼리를 제거하고, ProductFacade에서 Product와 Brand를 각각 조회 후 조합하도록 리팩토링. 목록 조회는 성능을 위해 기존 JOIN 방식 유지. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Order.create()에 빈 주문 방지 guard 추가 (도메인 불변식) - Price, Stock에 @EqualsAndHashCode 추가 (VO 값 동등성 보장) - OrderTest에 빈 항목/null 항목 테스트 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 상품 일괄 비관적 락 쿼리 도입 (DB 라운드트립 N→1) - 데드락 방지를 위한 락 순서 보장 및 동시성 테스트 추가 - Order 금액 불변식 검증 (discountAmount <= originalTotalPrice) - Clock 주입으로 도메인 시간 의존성 제거 (테스트 안정성) - 쿠폰 주문 연동 로직을 CouponFacade로 위임 (OrderFacade 의존성 5→4) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
좋아요 — Product.likeCount 제거, UNIQUE 제약 + COUNT(*) 파생으로 락 불필요 구조 전환 쿠폰 — 비관적 락 제거, 조건부 UPDATE(markAsUsed) + affected rows 검증으로 원자적 상태 전이 재고 — 비관적 락 유지 (다중 자원 원자성 + 높은 경합 특성) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- BrandFacade.deleteBrand(): 좋아요 루프 삭제 → deleteAllByProductIdIn() 배치 삭제 - ProductFacade.enrichWithLikeCount(): 개별 COUNT → countByProductIds() GROUP BY 배치 조회 - LikeRepository/LikeJpaRepository: 배치 메서드 추가 - LikeRepositoryImpl: Object[] → Map 변환 위임 구현 - FakeLikeRepository: 테스트용 배치 구현 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 01-requirements: 쿠폰 유저스토리, 동시성 전략, 쿠폰 API 추가 - 02-sequence-diagrams: 주문 생성(비관적 락+쿠폰), 좋아요(UNIQUE 기반), 쿠폰 발급/주문 취소 신규 추가 - 03-class-diagram: Coupon Aggregate 추가, Product likeCount 제거, Order 쿠폰 필드 추가 - 04-erd: coupon/coupon_issue 테이블, DDL, FK 정책, 잠재 리스크 갱신 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
고객 단순변심 취소 시 이미 만료된 쿠폰까지 AVAILABLE로 복원되던 문제 수정. cancelUse(ZonedDateTime now)로 시그니처 변경하여 만료 여부를 판단한 뒤, 만료됐으면 EXPIRED, 아니면 AVAILABLE로 분기 처리. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
비관적 락 + 엔티티 변경(read-modify-write) 방식에서 조건부 UPDATE(SET stock = stock - :qty WHERE stock >= :qty)로 전환. 쿠폰(조건부 UPDATE), 좋아요(UNIQUE + COUNT)와 함께 모든 동시성 제어가 read-modify-write를 피하는 구조로 통일됨. 동시성 테스트 threadCount를 10 → 100으로 강화. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
added 11 commits
April 16, 2026 21:24
- Chunk Best Practice를 이론/우리 설계/배치 프로젝트 3관점으로 비교 - ExponentialBackOffPolicy, allowStartIfComplete 설계 반영 - Cursor Reader 멀티스레드 제약 명시 - Writer skip 시 chunk scan 문제 분석 - 배치 90개 Job의 retry/skip 미사용에 대한 해석
- GROUP BY 집계 쿼리에서 PagingReader의 치명적 문제 (페이지마다 집계 재실행) - CursorReader 멀티스레드 불가 원인 (ResultSet 공유 상태) - 커넥션 점유 해법: Replica DataSource 분리 (배치 프로젝트 패턴) - 병렬화 시 전환 경로: PagingReader가 아닌 Partitioning - 설계 문서 Best Practice 테이블에 Cursor 선택 근거 반영
- Job 구조를 3-Step(cleanup → partitioned aggregate → merge)으로 변경 - Partitioner: product_id 범위 분할, Worker별 독립 CursorReader - 스테이징 테이블 도입: 병렬 집계 결과 수집 → mergeStep에서 Global TOP 100 - 성능 산정: 상품 100만 기준 단일 30초 → Partitioning 12초 (3배) - 블로그 소재: 고민 흐름(Chunk→Cursor→멀티스레드 한계→Partitioning) 기록
- Partitioning 도입 후 멱등성: cleanup에서 스테이징도 함께 DELETE → 전체 재실행이 안전 - RunIdIncrementer 결정: 파라미터 보존 + run.id 증가로 재실행 허용 - 설계 결정 요약 테이블 갱신: Partitioning, 스테이징, RunIdIncrementer 반영 - 블로그 소재 10, 11번 추가
- 소재 1~11 모두 대화 흐름에서 어떤 질문/반론이 고민을 촉발했는지 기록 - 소재 1: 전시 기간 편향 보정 논의에서 시작 - 소재 2: carry-over vs 캘린더 기간 질문에서 시작 - 소재 3: "Chunk가 보편적이라는데?" 반론으로 시각 교정 - 소재 4: "Redis에 이미 있는데 왜 MV를 만드는가?" - 소재 5: "전부 INSERT하고 삭제하는 게 비효율 아닌가?" - 소재 6: "사전 집계가 있어야 Chunk가 유용한 건가?" - 소재 7: Best Practice 문서 받고 3관점 교차 분석 - 소재 8: "CursorReader를 선택한 이유가 뭐야?"
- weekly/monthly를 MV 단일 소스로 변경 (Redis fallback 제거) - 다른 공식(감쇠 vs 균등)의 결과를 fallback으로 쓰면 데이터 불일치 - API 구조: daily→Redis, weekly/monthly→MV (fallback 없이 빈 결과 반환) - 블로그 소재 4에 단일 소스 원칙 판단 과정 기록
- 전체 재계산 유지 근거: Late-Arriving Fact(지연 취소)로 과거 데이터 변경 발생
- 증분 계산의 전제("과거 불변")가 이커머스에서 깨지는 이유 분석
- 전일 MV fallback: 같은 공식의 1일 stale 결과 (데이터 불일치 아닌 시간 지연)
- 데이터 보존 정책: 3일분 보존, 이전 데이터 정리
- Redis weekly/monthly: 검증 후 제거, daily carry-over만 유지
- ProductRankingMvJobConfig: cleanup → partitioned aggregate → merge - CleanupTasklet: 당일 MV/staging DELETE + 3일 이전 데이터 정리 - Partitioner: product_id 범위 분할 (gridSize=4) - Worker: JdbcCursorItemReader(GROUP BY + LOG10 score) → staging INSERT - mergeStep: ROW_NUMBER() OVER → Global TOP 100 → MV INSERT - faultTolerant + retry(3) + ExponentialBackOffPolicy - DDL: mv_product_rank_weekly/monthly + staging 테이블 - 설계 문서 Phase 업데이트
- MvProductRank 엔티티 (MappedSuperclass + Weekly/Monthly 분리) - MvProductRankRepository 인터페이스 + JPA 구현체 - RankingFacade 수정: daily→Redis, weekly/monthly→MV 단일 소스 - 전일 MV fallback: 당일 데이터 없으면 전일 period_key로 조회 - Redis weekly/monthly prefix 제거 (daily만 유지)
- 주간 Job 정상 실행: 150개 상품 시드 → TOP 100 적재 검증 - 월간 Job 정상 실행: 30일 데이터 집계 검증 - 멱등성: 같은 파라미터 2회 실행 → 데이터 2배 아닌 동일 - 엣지케이스: 데이터 없음, 7일 미만, 100개 미만 상품 - 취소 반영: cancel_amount가 score에 반영되어 순위 변동
- Phase 2, 3 상태 ✅로 업데이트 (구현 완료, 테스트 코드 작성) - 테스트 실행 가이드: 사전 조건, 명령어, 실패 시 확인사항 - 시나리오 검증 절차: 인프라 기동 → 시드 → 배치 실행 → API 검증 → 멱등성/fallback 확인 - PR 리뷰 포인트 후보 3개 - 블로그 구조 가이드: 소재 문서 → 블로그 섹션 매핑
|
Important Review skippedToo many files! This PR contains 297 files, which is 147 over the limit of 150. ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (3)
📒 Files selected for processing (297)
You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
- 10-technical-writing-plan.md: 과제 가이드 기준 방향 조정, 글 구조, 소재 배치 - velog-techwriting-vol10.md: 일간/주간/월간 랭킹 배치 설계기 초안
- DISTINCT product_id를 사전 조회하여 행 수 기준으로 파티션 경계 결정 - product_id에 gap이 있어도 파티션 간 처리량이 균등하게 분배 - 기존 MIN/MAX ID 범위 균등 분할은 gap 비율에 따라 불균형 발생
- PR Summary에서 "실환경 검증" 표현 제거 - 리뷰 포인트에서 참고자료/사례를 PR 본문으로 이동 - 블로그 소재 문서에 참고자료 4건 + 참고사례 5건 + 작성 참고 2건 정리 - 10만 건 대규모 테스트 프롬프트 작성 (다른 환경에서 실행용)
- 100,000 상품 + 3,000,000 메트릭 행 벌크 시드 (batchUpdate 1,000건 단위) - 6가지 트렌드 패턴 (급상승/장기강자/하락/바이럴/취소높음/일반) - Weekly 2,205ms, Monthly 2,564ms — 4 Partition 균등 분배 확인 - Weekly 1위=급상승(p=5000), Monthly 1위=장기강자(p=15000) 시간 윈도우 검증 - Testcontainers innodb-buffer-pool-size=256M, Gradle -Xmx2g 설정 - BeforeEach DELETE→TRUNCATE 전환 (대규모 테스트 후 정리 성능)
- gddp: 47→49 Job (SapItemSync 추가), Stub 5개 식별 - mbod: 43→48 Job, 통계 11개, memberSyncJob(3 Step) 추가 - UniqueRunIdIncrementer: addLong(timestamp) → addString(UUID+timestamp) - 비교 테이블/부록 수치 갱신, MV 단일 소스 원칙 반영 - 캡처 파일 경로 수정
- new JdbcTemplate(dataSource) → 필드 주입 jdbcTemplate으로 통일 - Javadoc을 기존 RankingCorrectionJobConfig 수준으로 간소화
- gridSize=1 vs gridSize=4 성능 비교 테스트 프롬프트 작성 - PR 테스트 시나리오 8개로 정리 (시각화를 대규모 테스트에 통합) - 성능 예시를 실제 측정값(10만 상품 기준)으로 교체
- Summary: 배경/목표/결과 구조로 변경 - Context & Decision: 문제 정의 + 선택지 5개(대안→결정→트레이드오프) - Design Overview: 변경 범위, 컴포넌트 책임 - Flow Diagram: Mermaid 2개 (배치 Job + API 조회) - 리뷰 포인트 내용 갱신
- 선택지 순서: Chunk/Tasklet → Reader/병렬 → fallback → 재계산 → Score - 지수 감쇠의 전시 기간 희석 특징 추가 - 문제 정의에서 Redis 언급 제거 (주간/월간 집계는 이번에 신규)
…s), 2.1x 향상 - GRID_SIZE를 @value로 외부 주입 가능하게 변경 (ReflectionTestUtils로 테스트 내 동적 변경) - partitionBenchmark 테스트 추가: 동일 데이터(10만×30일)에서 gridSize만 교체하여 측정 - PR draft, batch-test-results, blog 문서에 벤치마크 결과 반영 (10/10 PASSED)
- 성능 테이블을 규모별 비교표(150 / 1,020 / 100,000)로 교체 - 섹션 6에 10만 건 테스트의 1위 검증 결과 추가 - 1,020개 데이터는 실환경 API 검증 맥락으로 유지
4곳에 분산된 Score 공식을 ScoreFormula 1곳으로 통일. MV Job SQL에서 누락된 categoryPriority를 Java Processor로 반영.
- 수식 Javadoc을 ScoreFormula.java에만 유지, 나머지 3곳은 @see 참조로 축소 - 테스트 클래스 Javadoc 제거 (DisplayName으로 충분) - 미사용 within import 제거 (RankingScoreUpdaterTest) - 블로그에 ScoreFormula 중앙화 내용 반영
- RankingCarryOverScheduler: buildWeeklyRanking/buildMonthlyRanking 제거 (MV가 담당) - RankingScoreUpdater: weeklyKey/monthlyKey, RANKING_WEEKLY/MONTHLY_PREFIX, AGGREGATED_TTL 제거 - RankingRedisRepository: 미사용 RANKING_WEEKLY/MONTHLY_PREFIX 상수 제거 - 관련 테스트 정리 (WeeklyRanking/MonthlyRanking 테스트 클래스 등) - 블로그: Lambda Architecture 용어 제거 → scope별 데이터 소스 분리로 수정 - 블로그: Score 공식 중앙화를 섹션 3으로 이동 (이미 구현된 내용) - 블로그: Speed Layer/Batch Layer → 실시간 경로/배치 경로 - PR: Summary 수치 정확도 수정 (weekly 1.7초, monthly 2.2초) - PR: 벤치마크와 중복되는 성능 테이블 제거, 리뷰 포인트 정리 - 벤치마크: monthly 측정 추가 (weekly 2.1x, monthly 1.8x) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 제목: '테크니컬 라이팅 소재 모음' → '설계 판단 근거 모음' - 참고자료 섹션: 블로그 관련 문구 정리, 토스 라이팅 가이드 삭제 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
DAILY,WEEKLY,MONTHLY랭킹을 모두 다시 집계해 하나의 Materialized View 에 적재하고, API 도 같은 구조를 기준으로 조회하도록 정리한다.ranking_materialized_view공통 테이블을 도입했고, Spring Batch 가 기간별 랭킹을 파라미터 기반으로 적재하며, Ranking API 가periodType기준으로 일간/주간/월간 랭킹을 일관되게 제공하도록 구현했다.Context & Decision
1. 왜 기간별 랭킹 저장소를 하나의 MV 로 통합했는가?
ranking_materialized_view(채택)period_type,target_date로 통합ranking_materialized_view(period_type, target_date, product_id, score, rank_no)구조 채택period_type,target_date,rank_no와period_type,target_date,product_id인덱스를 두었다.2. 주간/월간 랭킹 점수를 무엇으로 계산할 것인가?
product_metrics.sales_count누적값ranking:all:{yyyyMMdd}를 읽어 기간 범위 합산3. 기간 기준일은 어떻게 정규화할 것인가?
202604172026-04-172026041720260417RankingPeriodDateResolver로 기간별 기준일 정규화20260415,20260416,20260417요청마다 다른 키를 조회하면 안 된다. 저장과 조회가 같은 규칙을 써야 한다.4. 배치 구현은 Chunk 가 아니라 Tasklet 이어도 되는가?
RankingMaterializedViewJob,weeklyRankingJob,monthlyRankingJob모두 Tasklet 기반Reader/Processor/Writer or Tasklet이다. 이번 작업은 Redis 기간 범위 집계 후 일괄 적재이므로 Tasklet 이 더 직접적이다.5. 참고 문서 반영 범위
ranking-flush-batch-followup.md하나였다.ranking-flush-batch-class-design.md,ranking-flush-batch-easy-explanation.md는 현재 워크스페이스에서 확인되지 않아 직접 반영 여부를 문서상 단정하지 않았다.Design Overview
변경 범위
periodType,date기반 조회 지원periodType,targetDate,items응답 구조 정리MV 적재 흐름
API 조회 흐름
API 응답 예시
{ "periodType": "WEEKLY", "targetDate": "2026-04-14", "items": [ { "rank": 1, "productId": 101, "productName": "Example Product", "brandName": "Example Brand", "price": 10000, "score": 12.4 } ] }Tests
테스트 코드는 현재 구조에 맞게 함께 수정했다.
RankingV1ApiE2ETestProductRankingE2ETestWeeklyRankingJobE2ETestMonthlyRankingJobE2ETestRankingMaterializedViewJobE2ETest다만 이번 요청에서는 테스트 결과를 포함하지 않았다. 이전에 실행을 시도했지만 완료 전에 중단되어, 이 문서는 구현 기준 정리 문서다.
Flow Diagram
sequenceDiagram actor User participant API as commerce-api participant Redis participant Batch as commerce-batch participant DB as MySQL Note over Redis: ranking:all:{yyyyMMdd} 에 일간 점수 누적 Batch->>Redis: 기간 범위 ZSET 조회 Redis-->>Batch: (productId, score) 목록 Batch->>Batch: 기간별 점수 합산 및 rank 계산 Batch->>DB: DELETE ranking_materialized_view where period_type=?, target_date=? Batch->>DB: INSERT period_type, target_date, product_id, score, rank_no User->>API: GET /api/v1/rankings?periodType=WEEKLY&date=20260417 API->>API: targetDate = weekly normalize(2026-04-17) API->>DB: SELECT * FROM ranking_materialized_view WHERE period_type='WEEKLY' AND target_date='2026-04-14' ORDER BY rank_no DB-->>API: ranked rows API->>DB: 상품/브랜드 정보 조회 API-->>User: RankingPageResponseChecklist
Spring Batch
Ranking API
판정 근거
rankingMaterializedViewJob,weeklyRankingJob,monthlyRankingJob이 모두 파라미터 기반으로 동작한다.ranking_materialized_view구조를 설계했고, Redis 일간 랭킹을 기간별로 재집계해 적재한다.periodType=DAILY|WEEKLY|MONTHLY를 지원하며, 기간 기준일 정규화 후 MV 를 조회한다.Review Points
주간/월간 랭킹 집계 방식
현재는
RankingMaterializedViewBatchService가 기간 범위의 Redis 일간 키ranking:all:{yyyyMMdd}를 순회하면서 상품별 점수를 합산해 주간/월간 랭킹을 계산하도록 구현했습니다. 이 방식이 도메인적으로 적절한지 리뷰 부탁드립니다.Materialized View 모델링
현재는
ranking_materialized_view(period_type, target_date, product_id, score, rank_no)단일 테이블에 일간/주간/월간 데이터를 함께 저장하고,period_type + target_date + rank_no중심으로 조회하도록 구현했습니다. 이 구조와 인덱스 구성이 조회/확장 관점에서 적절한지 확인 부탁드립니다.배치 적재 전략
현재는 배치 실행 시 같은
period_type,target_date조합의 기존 데이터를 먼저 삭제한 뒤 새 집계 결과를 일괄 insert 하는 방식으로 구현했습니다. 이DELETE + INSERT전략이 운영상 안전한지, 또는 upsert 나 스왑 테이블 방식이 더 적절한지 의견 부탁드립니다.