백엔드 데이터 인프라 53편 — Redis Sorted Set + 랭킹·Sliding Window Rate Limiter 패턴

2026-05-17백엔드 데이터 인프라

백엔드 데이터 인프라 53편. Redis Sorted Set — 점수 기준 정렬되는 Set. ZADD·ZRANGE·ZRANGEBYSCORE·ZRANK·ZINCRBY 같은 핵심 명령어와 Top N 랭킹·실시간 인기 검색어·sliding window rate limiter 패턴까지 풀어쓴 학습 노트.

📚 백엔드 데이터 인프라 · 53편 — Redis Sorted Set + 랭킹·Sliding Window Rate Limiter 패턴

이 글은 백엔드 데이터 인프라 시리즈 130편 중 53편이에요. 52편 에서 Redis Set 의 집합 연산 강점을 잡았다면, 이번 53편은 Sorted Set(정렬된 집합) — 점수(score) 가 붙은 Set. 랭킹·실시간 인기·sliding window rate limiter(슬라이딩 윈도우 호출 제한) 의 표준 도구로, Redis 자료구조 중에서도 가장 강력한 비밀 무기 중 하나예요.

Sorted Set이 어렵게 느껴지는 이유

Sorted Set 은 Set + Hash + 정렬 의 하이브리드라 처음 보면 기능이 너무 많아서 헷갈려요.

첫째, 이름이 Sorted Set 인데 단순 Set 의 정렬판이 아닙니다. 각 멤버에 score 가 추가로 붙고, score 기준으로 자동 정렬 되는 별도 자료구조예요. Set 처럼 중복 X 라는 점만 닮았고, 사실상 (member, score) 쌍 의 컬렉션.

둘째, 명령어가 가장 많아요. Z* 계열만 30개 이상. ZRANGE·ZREVRANGE·ZRANGEBYSCORE·ZRANGEBYLEX·ZRANK·ZSCORE·ZADD·ZINCRBY·ZPOPMIN·ZPOPMAX·ZINTERSTORE·... 어디서부터 외워야 할지 막막.

해법 — 자주 쓰는 8개만 먼저 손에 익히면 일상 90% 해결돼요. 나머지는 "이런 게 있구나" 만 알고 검색.

이 글에서 Sorted Set 의 핵심 8개 명령어 와, 랭킹·인기 검색어·rate limiter 라는 빛나는 3가지 패턴을 풀어 가요.

핵심 명령어 8종

ZADD — 멤버 + 점수 추가

> ZADD leaderboard 1500 "user42"
(integer) 1
> ZADD leaderboard 2300 "user99" 1800 "user77"
(integer) 2

복잡도 = O(log N). 옵션 다양 (NX·XX·GT·LT·CH·INCR):

> ZADD leaderboard GT 1600 "user42"     # 기존 점수보다 크면 갱신
> ZADD leaderboard INCR 50 "user42"     # 기존 점수에 +50

ZRANGE / ZREVRANGE — 순위 기준 조회

> ZADD board 100 "A" 200 "B" 150 "C"
> ZRANGE board 0 -1 WITHSCORES          # 오름차순 전체
1) "A"
2) "100"
3) "C"
4) "150"
5) "B"
6) "200"
> ZREVRANGE board 0 9 WITHSCORES        # 내림차순 Top 10
1) "B"
2) "200"
3) "C"
4) "150"
5) "A"
6) "100"

ZREVRANGE 0 9 = Top 10 랭킹의 표준 한 줄. 점수 같은 멤버는 사전순(lexicographical) 으로 정렬.

여기서 시험 함정 — Redis 6.2+ 에서 ZRANGEREV·BYSCORE·BYLEX 옵션을 흡수해 통합됐어요. 새 코드라면 ZRANGE ... REV 사용 권장, 옛 명령어 ZREVRANGE·ZRANGEBYSCORE 는 deprecated(권장 폐기) 흐름.

ZRANGEBYSCORE — 점수 범위 조회

> ZRANGEBYSCORE board 150 200
1) "C"
2) "B"
> ZRANGEBYSCORE board (150 +inf      # (150 = 150 제외, +inf = 무한대
1) "B"

( = 제외, +inf·-inf = 무한대. "점수 1,000~2,000 사이 사용자" 같은 쿼리.

ZRANK / ZREVRANK — 멤버의 순위 (O(log N))

> ZRANK board "C"                       # 오름차순 순위 (0-based)
(integer) 1
> ZREVRANK board "B"                    # 내림차순 순위 (Top 1)
(integer) 0

"내가 전체 몇 등인가" 가 한 줄. 1억 명 중에서도 μs 단위 응답.

ZSCORE — 멤버 점수

> ZSCORE board "B"
"200"

ZINCRBY — 점수 증감 (atomic, 중간 끼어듦 없는 원자 연산)

> ZINCRBY board 50 "A"
"150"                                   # 기존 100 + 50

카운터를 점수에 박으면 자동 정렬되는 카운터 — 인기 검색어·trending 같은 곳의 핵심.

ZCARD — 멤버 수

> ZCARD board
(integer) 3

ZPOPMIN / ZPOPMAX — 최저·최고 점수 멤버 빼기

> ZPOPMAX board                         # 최고 점수 빼서 반환 + 제거
1) "B"
2) "200"
> ZPOPMIN board                         # 최저 점수

Priority Queue(우선순위 큐) 의 표준 구현. 점수에 우선순위·timestamp·ETA(예상 처리 시간) 박고 ZPOPMIN 으로 다음에 처리할 작업 꺼내기.

한 줄 정리 — Sorted Set 핵심 8개 = ZADD·ZRANGE·ZRANGEBYSCORE·ZRANK·ZSCORE·ZINCRBY·ZCARD·ZPOPMIN/MAX. 이것만 익혀도 일상 90%.

패턴 1: 랭킹 / 리더보드

게임·앱의 Top N 점수 — Sorted Set의 가장 클래식한 활용처.

# 점수 업데이트
> ZADD leaderboard 1500 "user42"
> ZADD leaderboard 2300 "user99"
> ZADD leaderboard 1800 "user77"

# Top 10
> ZREVRANGE leaderboard 0 9 WITHSCORES

# 내 순위
> ZREVRANK leaderboard "user42"

# 내 점수 ±100 안의 사용자 (주변 경쟁자)
> my_score=$(ZSCORE leaderboard "user42")    # 1500
> ZRANGEBYSCORE leaderboard 1400 1600 WITHSCORES

# 점수 증가 (atomic)
> ZINCRBY leaderboard 50 "user42"

핵심 효율 — 1,000만 명 랭킹 중 내 순위 조회 가 O(log N) ≈ 24번 연산 만에 끝남. DB로 같은 쿼리 (SELECT COUNT(*) WHERE score > my_score) 는 인덱스 잘 박혀 있어도 ms 단위, Sorted Set 은 μs 단위.

패턴 2: 실시간 인기 검색어 / Trending

"최근 1시간 인기 검색어 Top 10" — 점수에 검색 횟수 박고 ZRANGE 하나면 끝.

# 검색이 들어올 때마다
> ZINCRBY trending:search 1 "redis"
> ZINCRBY trending:search 1 "spring boot"

# Top 10
> ZREVRANGE trending:search 0 9 WITHSCORES

# 1시간마다 reset (cron 또는 키 TTL)
> EXPIRE trending:search 3600

확장 — 1시간 단위 윈도우 가 필요하면 키에 시간 prefix:

> ZINCRBY trending:2026-05-17-12 1 "redis"      # 12시 윈도우
> ZINCRBY trending:2026-05-17-13 1 "redis"      # 13시 윈도우

여러 시간 윈도우를 합쳐 "최근 3시간 trending"ZUNIONSTORE 로 가능.

패턴 3: Sliding Window Rate Limiter

여기가 정말 중요한 자리. "분당 60회 API 호출 제한" 같은 rate limit 을 Sorted Set 점수에 timestamp 박아 풀어요.

알고리즘

  1. 요청 들어올 때마다 현재 timestamp 를 score 로 ZADD
  2. 현재 시간 - 60초 이전 항목을 ZREMRANGEBYSCORE 로 제거 (윈도우 밖)
  3. 남은 항목 수 ZCARD 로 카운트
  4. 카운트가 60 이상 → 차단, 아니면 통과

Lua 스크립트 한 줄 구현

Lua(레디스 서버에서 도는 임베디드 스크립트 언어) 로 위 4단계를 한 번에 묶으면 race condition 없이 끝나요.

-- KEYS[1] = "rate:user42"
-- ARGV[1] = 현재 timestamp (밀리초)
-- ARGV[2] = 윈도우 (밀리초, 예: 60000)
-- ARGV[3] = 최대 허용 횟수 (예: 60)

local key = KEYS[1]
local now = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local limit = tonumber(ARGV[3])

redis.call("ZREMRANGEBYSCORE", key, 0, now - window)
local count = redis.call("ZCARD", key)
if count >= limit then
  return 0
end
redis.call("ZADD", key, now, now)
redis.call("EXPIRE", key, window / 1000)
return 1

장점 — 정확한 sliding window. "고정 윈도우(분 단위 카운터)"경계 시점에 2배 트래픽이 통과 하는 버그가 있는데, Sliding Window 는 항상 정확히 마지막 60초 를 본다는 게 차이.

비교 — Token Bucket / Leaky Bucket / Fixed Window / Sliding Window 4가지 알고리즘 중 Sliding Window 가 가장 정확하고 Sorted Set 으로 자연스럽게 구현돼요.

패턴 4: Priority Queue

작업 큐에서 우선순위가 높은 작업이 먼저 처리되어야 할 때.

# 작업 추가 (점수 = 우선순위, 낮을수록 우선)
> ZADD jobs 1 "urgent-job-001"
> ZADD jobs 5 "normal-job-002"
> ZADD jobs 1 "urgent-job-003"
> ZADD jobs 10 "low-job-004"

# 다음 작업 꺼내기 (가장 우선)
> ZPOPMIN jobs
1) "urgent-job-001"
2) "1"
> ZPOPMIN jobs
1) "urgent-job-003"
2) "1"

ZPOPMIN최저 점수 멤버 를 빼서 반환 — 낮은 점수가 더 우선 이라는 규칙으로 우선순위 큐 가 자연스러움.

Scheduled Job(예약 작업) — 점수에 예정 실행 시각(timestamp) 박고, 현재 시각 이전 항목만 ZRANGEBYSCORE 로 가져와 처리하면 지연 큐(delayed queue) 가 됨.

# 10분 후 실행 작업
> ZADD scheduled $(($(date +%s) + 600)) "send-email-123"
# 워커: 현재 시각 이전 모두 가져오기
> ZRANGEBYSCORE scheduled 0 $(date +%s)

집합 연산 — ZINTERSTORE·ZUNIONSTORE

여기까지 따라오셨다면 한 가지 의문 — "Set 의 SINTER 처럼 Sorted Set 도 교집합 가능?". 정답 — 가능하고 점수도 합쳐져요.

> ZADD scores:math 90 "Alice" 85 "Bob"
> ZADD scores:english 80 "Alice" 95 "Bob"
> ZINTERSTORE total 2 scores:math scores:english
> ZRANGE total 0 -1 WITHSCORES
1) "Alice"
2) "170"             # 90 + 80
3) "Bob"
4) "180"             # 85 + 95

기본 = 점수 합산. AGGREGATE MIN/MAX 로 변경 가능. 여러 카테고리 점수를 합쳐 종합 랭킹 같은 자리에 자연스러움.

시험 직전 한 번 더 — Sorted Set 함정 압축 노트

  • Sorted Set = Set + score + 자동 정렬 (점수 기준)
  • 모델 = (member, score) 쌍 의 컬렉션
  • Set 정렬판 이 아니라 별도 타입
  • 핵심 8개 = ZADD·ZRANGE·ZRANGEBYSCORE·ZRANK·ZSCORE·ZINCRBY·ZCARD·ZPOPMIN/MAX
  • ZADD = O(log N), 옵션 NX·XX·GT·LT·CH·INCR
  • ZRANGE REV 0 9 WITHSCORES = Top 10 랭킹의 한 줄
  • 점수 같은 멤버 = 사전순 정렬
  • Redis 6.2+ = ZRANGEREV·BYSCORE·BYLEX 옵션 흡수, ZREVRANGE·ZRANGEBYSCORE deprecated 흐름
  • ZRANGEBYSCORE 범위 = ( 제외, +inf·-inf 무한대
  • ZRANK·ZREVRANK = O(log N) 순위 조회, 1억 명 중에서도 μs
  • ZSCORE = 멤버 점수 조회
  • ZINCRBY = atomic 점수 증감 — 인기 검색어의 핵심
  • ZPOPMIN/MAX = Priority Queue 의 표준
  • 자주 쓰는 4 패턴 = 랭킹 / Trending / Sliding Window Rate Limiter / Priority Queue
  • 랭킹 = ZADD + ZREVRANGE 0 N + ZREVRANK me
  • 1,000만 명 내 순위 조회 = O(log N) ≈ 24번 연산
  • Trending = ZINCRBY + 시간 prefix 키 + EXPIRE
  • Sliding Window Rate Limiter = score에 timestamp + ZREMRANGEBYSCORE + ZCARD
  • Rate Limit 4 알고리즘 중 = Sliding Window 가 가장 정확
  • Priority Queue = score에 우선순위 + ZPOPMIN
  • Delayed Queue = score에 실행 timestamp + ZRANGEBYSCORE 0 now
  • 집합 연산 = ZINTERSTORE·ZUNIONSTORE, 기본 score 합산
  • 내부 구조 = listpack(작은) · skiplist + hashtable(큰) 자동 전환
  • 설정 = zset-max-listpack-entries·zset-max-listpack-value

공식 문서: Redis Sorted Sets 에서 Sorted Set 명령어 전체 reference 를 확인할 수 있어요.

시리즈 다른 편 (앞뒤 글 모음)

이전 글:

다음 글:

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!