실시간 랭킹은 Redis의 Sorted Set(ZSet)이 가장 빛을 발하는 사용 사례다. RDB에서 ORDER BY score DESC 쿼리를 매번 날리는 것과 달리, Sorted Set은 score 기반 정렬 상태를 항상 유지하므로 순위 조회가 O(log N)이다. --- | 방법 | 삽입 | 순위 조회 | 범위 조회 | |------|------|--...
실시간 랭킹은 Redis의 Sorted Set(ZSet)이 가장 빛을 발하는 사용 사례다. RDB에서 ORDER BY score DESC 쿼리를 매번 날리는 것과 달리, Sorted Set은 score 기반 정렬 상태를 항상 유지하므로 순위 조회가 O(log N)이다.
| 방법 | 삽입 | 순위 조회 | 범위 조회 |
|---|---|---|---|
RDB ORDER BY | O(1) | O(N log N) | O(N log N) |
| Redis Sorted Set | O(log N) | O(log N) | O(log N + K) |
내부적으로 Skip List + Hash Table 이중 구조로 구현되어 있다. Hash Table은 멤버 → score를 O(1)로 찾고, Skip List는 score 기준 정렬과 범위 탐색을 O(log N)으로 처리한다.
# 점수 추가 / 갱신
ZADD leaderboard 1500 "user:1001"
ZADD leaderboard NX 1500 "user:1001" # 없을 때만 추가
ZADD leaderboard XX 1500 "user:1001" # 있을 때만 갱신
ZINCRBY leaderboard 100 "user:1001" # 점수 증가 (원자적)
# 순위 조회 (0-based index)
ZRANK leaderboard "user:1001" # 오름차순 순위 (낮은 점수 = 0위)
ZREVRANK leaderboard "user:1001" # 내림차순 순위 (높은 점수 = 0위) ← 랭킹에 사용
# 범위 조회
ZREVRANGE leaderboard 0 9 WITHSCORES # 상위 10명 + 점수
ZRANGE leaderboard 0 9 WITHSCORES # 하위 10명 + 점수
ZRANGEBYSCORE leaderboard 1000 2000 # 점수 1000~2000 사이
# 점수 조회
ZSCORE leaderboard "user:1001"
# 삭제
ZREM leaderboard "user:1001"
# 전체 크기
ZCARD leaderboard# 게임 점수 기록
ZINCRBY game:leaderboard 500 "user:1001"
# 상위 10명 조회
ZREVRANGE game:leaderboard 0 9 WITHSCORES
# 내 순위 조회 (1-based로 변환하려면 +1)
ZREVRANK game:leaderboard "user:1001"
# 내 주변 순위 (내 순위 ± 2명)
# 내 순위가 5라면
ZREVRANGE game:leaderboard 3 7 WITHSCORES날짜를 키에 포함시켜 자연스럽게 기간을 분리한다.
# 일간 랭킹
ZINCRBY leaderboard:daily:20260327 100 "user:1001"
EXPIRE leaderboard:daily:20260327 86400 # 1일 후 자동 삭제
# 주간 랭킹 (주차 번호 활용)
ZINCRBY leaderboard:weekly:2026-W13 100 "user:1001"
EXPIRE leaderboard:weekly:2026-W13 604800 # 7일 후 자동 삭제
# 월간 랭킹
ZINCRBY leaderboard:monthly:2026-03 100 "user:1001"기간별 랭킹 조회 흐름:
요청: 이번 주 TOP 10 조회
↓
ZREVRANGE leaderboard:weekly:2026-W13 0 9 WITHSCORES
↓
결과 캐시 (인기 랭킹은 1~5초 TTL로 별도 캐싱)# 장르별 게임 랭킹
ZINCRBY leaderboard:genre:action 200 "user:1001"
ZINCRBY leaderboard:genre:rpg 150 "user:1001"
# 지역별 랭킹
ZINCRBY leaderboard:region:seoul 300 "user:1001"Sorted Set은 score가 같으면 멤버 이름의 사전순으로 정렬한다. 동점자를 먼저 달성한 순으로 처리하려면 score에 타임스탬프를 소수점으로 인코딩하는 방법을 쓴다.
# score = 실제점수 + (1 - 타임스탬프 정규화값)
# 같은 점수면 먼저 달성한 사람이 앞에 오도록
score = actual_score + (1 - timestamp / MAX_TIMESTAMP)또는 score를 복합값으로 인코딩한다.
# 점수 * 10^10 + (MAX_TIMESTAMP - 현재 타임스탬프)
# 점수가 같으면 먼저 달성한 사람이 더 큰 값
ZADD leaderboard 15000000000000 "user:1001"e커머스에서 자주 쓰는 패턴이다.
# 상품 조회 시마다 score 증가
ZINCRBY popular:products:hourly 1 "product:9901"
# 검색어 입력 시마다 score 증가
ZINCRBY search:keywords:daily 1 "아이폰케이스"
# 실시간 인기 검색어 TOP 10
ZREVRANGE search:keywords:daily 0 9
# 오래된 데이터 정리 (score가 낮은 것 제거)
ZREMRANGEBYRANK popular:products:hourly 0 -101 # 상위 100개만 유지기간별 랭킹을 날짜마다 만들면 키가 쌓인다. TTL을 반드시 설정하거나, 배치 작업으로 오래된 키를 정리해야 한다.
# TTL 확인
TTL leaderboard:daily:20260101 # -1이면 만료 없음 → 위험멤버가 수백만 개가 넘으면 ZRANGE, ZREVRANGE 등의 범위 조회가 느려진다. 상위 N개만 유지하는 정책을 적용하는 것이 좋다.
# 삽입 후 상위 1000개만 유지
ZINCRBY leaderboard 100 "user:1001"
ZREMRANGEBYRANK leaderboard 0 -1001 # 1001번째 이하 모두 삭제Redis 6.2부터 ZRANGE에 REV, BYSCORE, LIMIT 옵션이 추가되어 ZRANGEBYSCORE, ZREVRANGEBYSCORE를 대체한다.
# Redis 6.2+
ZRANGE leaderboard 0 9 REV WITHSCORES # 상위 10명
ZRANGE leaderboard "(1000" "+inf" BYSCORE REV # 1000점 초과 내림차순