You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
TL;DR — 조회, 좋아요, 주문 이벤트를 Kafka에서 독립 consumer group으로 소비해, event-time 기준 Redis ZSET에 일별 랭킹을 미리 구축했다. Redis는 현재 랭킹을, MySQL ranking_snapshot은 확정된 과거와 재구축 기준을 맡는다.
본문
Introduction & Goals
Context / Background: 인기 상품은 조회량이 많고 항상 정렬이 필요하다. 요청마다 RDB에서 집계·정렬하면 DB 부하가 커지고, 전체 누적값만 쓰면 오래된 상품이 상단을 점유한다.
Goals:
이벤트가 들어올 때 점수를 누적해 랭킹 조회를 빠르게 만든다.
늦게 소비된 이벤트도 실제 발생일의 랭킹에 반영한다.
Redis 장애·자정 콜드 스타트·과거 날짜 조회의 정책을 명확히 한다.
Detailed Design
System Architecture
flowchart LR
API[commerce-api]
K[Kafka<br/>catalog-events · order-events]
S[commerce-streamer<br/>ranking consumer group]
R[(Redis ZSET<br/>ranking:all:yyyyMMdd)]
P[(MySQL<br/>product · like_count)]
SCH[Ranking Scheduler]
SNAP[(MySQL<br/>ranking_snapshot)]
API -->|VIEW / LIKE / PAID<br/>occurredAt| K
K -->|batch poll| S
S -->|fold + pipeline ZINCRBY| R
API -->|ZREVRANGE / ZREVRANK| R
API <-->|상품 조합 · 장애 폴백| P
SCH -->|23:50 carry-over<br/>00:30 finalize| R
SCH -->|snapshot 저장| SNAP
SNAP -->|수동 rebuild 기준| SCH
Loading
commerce-api는 이벤트를 발행하고, 완성된 ZSET을 읽어 상품 정보와 조합한다.
commerce-streamer는 ranking-catalog, ranking-order group으로 기존 metrics와 독립적으로 이벤트를 소비한다.
컨슈머는 event-time을 KST 날짜로 변환하고 (날짜, productId)별 점수를 fold한 뒤 Redis pipeline으로 적재한다.
TL;DR
TL;DR — 조회, 좋아요, 주문 이벤트를 Kafka에서 독립 consumer group으로 소비해, event-time 기준 Redis ZSET에 일별 랭킹을 미리 구축했다. Redis는 현재 랭킹을, MySQL
ranking_snapshot은 확정된 과거와 재구축 기준을 맡는다.본문
Introduction & Goals
Detailed Design
System Architecture
flowchart LR API[commerce-api] K[Kafka<br/>catalog-events · order-events] S[commerce-streamer<br/>ranking consumer group] R[(Redis ZSET<br/>ranking:all:yyyyMMdd)] P[(MySQL<br/>product · like_count)] SCH[Ranking Scheduler] SNAP[(MySQL<br/>ranking_snapshot)] API -->|VIEW / LIKE / PAID<br/>occurredAt| K K -->|batch poll| S S -->|fold + pipeline ZINCRBY| R API -->|ZREVRANGE / ZREVRANK| R API <-->|상품 조합 · 장애 폴백| P SCH -->|23:50 carry-over<br/>00:30 finalize| R SCH -->|snapshot 저장| SNAP SNAP -->|수동 rebuild 기준| SCHcommerce-api는 이벤트를 발행하고, 완성된 ZSET을 읽어 상품 정보와 조합한다.commerce-streamer는ranking-catalog,ranking-ordergroup으로 기존 metrics와 독립적으로 이벤트를 소비한다.(날짜, productId)별 점수를 fold한 뒤 Redis pipeline으로 적재한다.Data Models
ranking:all:{yyyyMMdd}, member:productId, score:VIEW(0.1) + LIKE(±0.2) + ORDER(0.6 × 수량).ranking_snapshot:stat_date,product_id,rank_position,score를 저장한다. 00:30에 어제 ZSET을 확정하며, 과거 조회와 재구축의 기준이다.API Design
GET /api/v1/rankings?date=yyyyMMdd&page&size: 오늘·어제 ZSET에서 페이지를 읽고 상품 요약을 붙인다.GET /api/v1/products/{id}: 오늘 ZSET의ZREVRANK를todayRank로 추가한다. 순위가 없거나 Redis 장애면null이다.POST /api/v1/admin/rankings/rebuild: 어제 스냅샷 점수의 10%를 기준으로 오늘 ZSET을 재구축한다.Constraints
like_count내림차순으로 폴백하고degraded:true를 반환한다.Alternatives Considered
선택 근거: 실시간 랭킹은 Redis ZSET의 정렬 능력을 활용하고, 확정된 과거만 MySQL에 남겨 저장소 책임을 분리했다.
product_metrics전체 누적값을 랭킹 원본으로 쓰지 않아 롱테일 문제도 피했다.Cross-cutting Concerns
findAllById로 한 번에 조합해 N+1을 피한다.Reference
No response