Redis를 "캐시"가 아닌 "저장소"로 바라보기
이번 주에는 상품 랭킹 기능을 구현하면서 Redis를 단순한 결과 캐시가 아니라 랭킹 전용 데이터베이스로 활용했다.
ZSET의 정렬 특성 덕분에 추가 연산 없이 Top-N, 순위, 점수를 바로 조회할 수 있었고, 이는 실시간성과 고동시 동접 처리 요구에 적합했다.
배치 리스너 + Zip-Map-Sink
메시지를 단건으로 처리하면 동일 상품에 대한 반복 업데이트로 불필요한 쓰기가 발생한다.
이를 개선하기 위해 배치 리스너를 도입했고, 배치 내 이벤트를 상품 단위로 집계(Zip), 가중치 점수로 변환(Map), 최소 연산으로 저장(Sink) 하는 패턴을 적용해 효율을 높였다.
집계 책임의 분리: Metrics vs. Rank
product_metrics 집계와 product_rank 집계를 같은 컨슈머에서 처리하면 장애 전파와 코드 결합도가 높아진다.
컨슈머를 분리해 단일 책임 원칙(SRP)을 지켰고, 각 파이프라인이 독립적으로 장애 대응·확장·운영되도록 했다.
가중치 설계: 수학적 최적화가 아닌 비즈니스 정합성
조회(0.1)·좋아요(0.3)·판매(0.7)의 가중치는 신호 강도, 희소성, 조작 난이도, 서비스 목표를 기준으로 정했다.
판매는 전환을 직접 반영하므로 가장 높은 가중치를 두었고, 변동성과 조작 가능성이 큰 조회는 낮췄다.
또한 판매 스코어에는 로그 함수를 적용해 값의 편차를 완화했다.
Carry-Over 전략: 연속성과 최신성의 균형
데이터가 희소한 구간에서 랭킹이 공백으로 보이지 않도록, 이전 주기의 상위 N 데이터를 **정규화 계수(예: 1%)**를 적용해 반영했다.
이 방식은 과거 데이터가 현재를 압도하지 않으면서도 랭킹의 연속성을 보장했다.
TTL 보정: Duration이 아닌 Anchor Time 기반 만료
Carry-Over 실행 시각을 TTL 기준으로 삼으면 만료 시각 불일치로 공백이 생길 수 있다.
이를 막기 위해 일 단위는 정오 기준 +2/3일 자정, 시간 단위는 30분 반올림 등 고정 기준(anchor)을 적용해 TTL을 계산했다.
TTL은 단순한 시간 설정이 아니라 운영 스케줄과 정렬된 정책이어야 한다.
백업 전략: 상위 N 스냅샷
TTL 만료 이후에도 분석과 회고를 위해 이력이 필요하다. 하지만 모든 랭킹을 저장하는 것은 비용 대비 효용이 낮다.
그래서 일별·시간별 상위 N개(또는 상위 N%)만 정규화된 스냅샷으로 RDB에 보관했다.
앞으로 더 해볼 것들
- 가중치 실시간 조정
현재는 조회·좋아요·판매 가중치를 고정 값으로 두고 있다.
이를 실시간으로 가중치를 조절할 수 있는 기능을 구현해본다.
- Zip-Map-Sink + Redis 파이프라인화
배치 + Zip-Map-Sink로 중복 연산은 줄였지만, 여전히 Redis에 개별 호출이 발생한다.
향후에는 Redis 파이프라인 기능을 활용해 쓰기 연산을 일괄 처리하고 네트워크 RTT를 줄이는 최적화를 적용할 수 있다.
'Loopers' 카테고리의 다른 글
| Loop:Pak - 10주간의 회고 (0) | 2025.09.19 |
|---|---|
| WIL - 8주차 회고 (0) | 2025.09.05 |
| WIL - 7주차 회고 (1) | 2025.08.29 |
| WIL - 6주차 회고 (1) | 2025.08.24 |
| WIL - 5주차 회고 (2) | 2025.08.17 |
댓글