[Volume-9] 상품 랭킹 기능 구현 - #396
Conversation
|
Important Review skippedToo many files! This PR contains 138 files, which is 38 over the limit of 100. To get a review, narrow the scope: Upgrade to a paid plan to raise the limit. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (2)
📒 Files selected for processing (138)
You can disable this status message by setting the Use the checkbox below for a quick retry:
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 |
🧭 Context & Decision
문제 정의
현재 동작/제약:
문제(또는 리스크):
ZINCRBY점수가 중복 가산될 수 있습니다.성공 기준(완료 정의):
전체 구조
flowchart LR EVENT["상품 조회 · 좋아요 · 결제 완료"] --> KAFKA["Kafka<br/>catalog-events"] KAFKA --> RC["Ranking Consumer<br/>중복 확인 · 점수 계산"] KAFKA --> MC["Metrics Consumer<br/>상품 · 시간 단위 사전 집계"] RC --> REDIS[("Redis ZSET<br/>조회용 최종 점수")] MC --> MYSQL[("MySQL<br/>product_metric_hourly<br/>가중치 적용 전 지표")] REDIS --> API["Ranking API"] API --> USER["사용자"] MYSQL -.->|"장애 복구 · 가중치 변경"| JOB["Ranking Rebuild Job"] JOB -.->|"임시 키 완성 후 원자적 교체"| REDIS선택지와 결정
조회 수 × 0.1 + 좋아요 증감 × 0.2 + (주문 금액 / 10,000) × 0.7로 계산합니다.product_metric_hourly에는 가중치 적용 전 시간별 지표를 저장합니다.랭킹 재생성 흐름
sequenceDiagram participant API as 랭킹 API participant LIVE as 현재 Redis 랭킹 participant JOB as Rebuild Job participant DB as MySQL 시간별 지표 participant TEMP as Redis 임시 랭킹 API->>LIVE: 기존 랭킹 조회 JOB->>DB: 대상 날짜의 시간별 지표 조회 DB-->>JOB: 가중치 적용 전 집계값 JOB->>TEMP: 점수 재계산 JOB->>TEMP: 전날 랭킹 10% 반영 JOB->>TEMP: 생성 결과 검증 alt 검증 성공 JOB->>LIVE: 임시 키를 원자적으로 교체 API->>LIVE: 완성된 새 랭킹 조회 else 검증 실패 Note over LIVE: 기존 랭킹 유지 end트레이드오프:
추후 개선 여지(있다면):
🤔 고민한 점 / 막혔던 부분
product_metric_hourly를 SOT라고 부를 수 있는지 고민했습니다. 현재 랭킹을 재현하기에는 충분하지만 원본 정보 전체를 보존하지 않으므로, 시스템 전체의 SOT보다는 현재 유스케이스의 복구 기준 데이터 또는 Materialized View에 가깝다고 판단했습니다.Kafka의 at-least-once 전달과
ZINCRBY는 자연스럽게 멱등하지 않습니다. Redis에서는 Lua Script와 handled event 집합을 사용하고, MySQL에서는event_handled기록과 지표 반영을 같은 트랜잭션으로 묶었습니다.Kafka의 배치 크기 3,000은 이벤트가 모두 모일 때까지 기다린다는 의미가 아닙니다. 한 번의 poll에서 가져올 수 있는 최대 개수이며, 실제로 가져온 레코드는 개수와 관계없이 즉시 처리합니다.
재생성 전에 기존 Redis 키를 삭제하면 사용자에게 빈 랭킹이 노출될 수 있습니다. 임시 키에서 새 랭킹을 완성하고 검증한 뒤 원자적으로 교체하도록 설계했습니다.
🙋 기타
null을 반환합니다.