백엔드 데이터 인프라 62편 — Redis 패턴 9가지 한눈에 매핑

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

백엔드 데이터 인프라 62편. Redis 자주 쓰는 패턴 9가지 — cache-aside·distributed lock·rate limiting·leaderboard·session·counting·secondary indexing·pub/sub·bulk loading 을 *어느 자료구조 + 어떤 명령 조합으로* 풀어내는지 한눈에 매핑한 학습 노트.

📚 백엔드 데이터 인프라 · 62편 — Redis 패턴 9가지 한눈에 매핑

이 글은 백엔드 데이터 인프라 시리즈 130편 중 62편이에요. 61편 까지 Redis 명령어·프로그래밍 도구 를 다 잡았다면, 이번 62편부터 Part 4-4 — 패턴 (4편) 으로 들어가요. 첫 글은 Redis 자주 쓰는 패턴 9가지 매핑 — 49~54편의 자료구조 와 55~61편의 상호작용 도구어떤 조합으로 어떤 문제를 푸는지 한 표로 정리해요.

패턴 학습이 어렵게 느껴지는 이유

명령어를 다 외워도 "내 문제는 어떤 패턴을 써야 하나" 가 안 잡힐 때가 많아요. 처음 헷갈리는 자리가 두 군데 있어요.

첫째, 같은 문제를 여러 패턴으로 풀어요. "사용자 30명 마지막 활동 기록" 만 봐도 List 캡 리스트, Stream MAXLEN, Sorted Set 셋 다 답이 돼요. 어느 게 자연스러운지는 요구사항의 미세한 차이에서 갈려요.

둘째, 공식 문서가 패턴을 한 곳에 모아 두지 않아요. cache-aside 는 어디에도 명시적으로 안 나오고, distributed lock 은 별도 페이지, rate limiting 은 Sorted Set 페이지 안에 묻혀 있어요. 그래서 "어떤 패턴이 있는지" 전체 그림 이 안 보여요.

이 글에서 자주 쓰는 9가지 패턴을 한 표로 묶어 매핑하고, 63~65편에서 그중 3가지(distributed lock·twitter clone·secondary indexing) 를 깊이 풀어 가요.

9가지 패턴 한눈에

# 패턴 자료구조 핵심 명령 다룬 편
1 Cache-aside String / Hash GET·SET ... EX·DB fallback 49·50편
2 Write-through / Write-behind String / Hash SET 동시 DB 업데이트 (이 글)
3 Distributed Lock String SET NX EX + Lua 해제 49·63편
4 Rate Limiting Sorted Set / String ZADD+ZREMRANGEBYSCORE 또는 INCR+EXPIRE 53편
5 Leaderboard / Ranking Sorted Set ZADD·ZREVRANGE·ZREVRANK 53편
6 Session Management String / Hash SET sess EX·HSET sess 47·50편
7 Counting (정확) String / Hash INCR·HINCRBY 49·50편
7' Counting (근사, 메모리 절약) HyperLogLog PFADD·PFCOUNT 48편
8 Secondary Indexing Sorted Set / Set / Hash ZADD+조회·SADD+필터 65편
9 Real-time Messaging Pub/Sub / List / Stream PUBLISH/SUBSCRIBE·LPUSH/BLPOP·XADD/XREADGROUP 51·54·58편
10 Bulk Loading (모든 타입) Pipelining·--pipe 모드 57편

핵심 — 대부분의 패턴이 49~54편 자료구조 + 55~61편 도구의 조합이에요. 한 번 외워두면 새 문제도 같은 빌딩 블록으로 풀어요.

1. Cache-aside (Lazy Loading)

가장 흔한 캐싱 패턴. DB 가 진실, Redis 는 사본.

def get_user(uid):
    cached = r.get(f"user:{uid}")
    if cached:
        return json.loads(cached)
    user = db.query("SELECT * FROM users WHERE id = ?", uid)
    if user:
        r.setex(f"user:{uid}", 3600, json.dumps(user))
    return user

흐름을 보면 캐시를 먼저 조회해서 hit 면 즉시 반환, miss 면 DB를 조회하고 그 결과를 Redis에 TTL 박아 저장한 뒤 반환해요.

장점은 읽기 부하를 즉시 절약하고, 데이터 일관성 모델도 단순해요. 단점은 첫 요청이 cache miss → DB hit 으로 느리고, Thundering herd(캐시 만료 시 DB로 요청이 한꺼번에 쏠리는 현상) 위험이 있어요.

2. Write-through / Write-behind

쓰기 시점에 Redis 와 DB 둘 다 갱신 하는 패턴.

Write-through

def update_user(uid, data):
    db.execute("UPDATE users SET ... WHERE id = ?", uid, data)    # 1. DB
    r.setex(f"user:{uid}", 3600, json.dumps(data))                 # 2. Cache

장점은 cache 와 DB 가 항상 동기화돼요. 단점은 DB 쓰기 + Redis 쓰기 둘 다 기다려야 해서 응답 시간이 올라가요.

Write-behind (Write-back)

def update_user(uid, data):
    r.setex(f"user:{uid}", 3600, json.dumps(data))                 # 1. Cache 즉시
    queue.push({"op": "update_user", "uid": uid, "data": data})    # 2. DB 는 비동기

장점은 쓰기 응답이 매우 빨라요. 단점은 Redis 와 DB 가 일시 불일치 상태가 되고, 큐 시스템이 실패하면 데이터 손실 위험이 있어요.

선택은 — 일관성이 결정적이면 write-through, 지연 시간이 결정적이면 write-behind.

3. Distributed Lock

여러 인스턴스가 같은 자원에 동시 작업 못 하게.

> SET lock:order:99 my-uuid NX EX 30
OK
# 작업 ...
# Lua 로 안전 해제 (49·60편)

자세히는 63편 distributed-lock 에서 Redlock(여러 Redis 노드 다수결로 락 보장) 알고리즘까지 다뤄요.

4. Rate Limiting — 두 가지 알고리즘

Fixed Window — 단순

def is_allowed(user_id, limit=60):
    minute = int(time.time() // 60)
    key = f"rate:{user_id}:{minute}"
    count = r.incr(key)
    if count == 1:
        r.expire(key, 60)
    return count <= limit

장점은 극도로 단순해요. 단점은 경계 시점에 2배 트래픽 이 통과해 버릴 위험이 있어요.

Sliding Window — 정확 (Lua + Sorted Set)

53편에서 본 그 패턴. 정확하지만 메모리가 더 들어요.

선택은 — 대략적 제한이면 Fixed, 정확한 제한이면 Sliding.

5. Leaderboard

> ZADD board <score> <user>
> ZREVRANGE board 0 9 WITHSCORES    # Top 10
> ZREVRANK board user42              # 내 순위

53편에서 깊이 풀어요.

6. Session Management

단순 형 (JSON String)

> SET session:abc '{"uid":42,"role":"user"}' EX 3600
> GET session:abc

부분 갱신형 (Hash)

> HSET session:abc uid 42 role user last_access "2026-05-17"
> HGET session:abc uid
> HSET session:abc last_access "2026-05-17 13:00"   # 한 필드만 갱신
> EXPIRE session:abc 3600                            # TTL 갱신

세션 만료 후속 처리는 Keyspace Notifications(키 이벤트 구독 기능, 56편)에서 다뤄요.

7. Counting

정확 (INCR 계열)

> INCR pv:2026-05-17                  # 페이지뷰
> HINCRBY user:42:stats logins 1       # 사용자별 통계
> ZINCRBY trending 1 "search-term"     # trending (자동 정렬)

근사 (HyperLogLog) — 대량 unique 카운팅

> PFADD visitors:2026-05-17 user42 user99 user88
> PFCOUNT visitors:2026-05-17
(integer) 3
# 수억 unique 사용자도 ~12KB 메모리로 표현, 오차 ~0.81%

"오늘 고유 방문자 약 1억 명" 처럼 정확한 N이 중요하지 않은 카운팅에서는 메모리 효율이 압도적이에요.

8. Secondary Indexing

RDB 의 세컨더리 인덱스 를 Redis 자료구조로 모방해요. price·age·created_at 처럼 주 키가 아닌 속성으로 조회하고 싶을 때 써요.

Sorted Set 기반 (범위·정렬)

# 사용자 ID → 생일 timestamp 인덱스
> ZADD users:by:birthday 946684800 user42
> ZADD users:by:birthday 1577836800 user99

# 2000년 1월~2010년 1월 출생 사용자
> ZRANGEBYSCORE users:by:birthday 946684800 1262304000

Set 기반 (필터·집합 연산)

> SADD users:by:country:KR user42 user99
> SADD users:by:role:admin user99

# KR 국가 + admin 권한
> SINTER users:by:country:KR users:by:role:admin
1) "user99"

자세히는 65편 pattern-indexes 에서 풀어요.

9. Real-time Messaging — 3가지 선택

도구 영속성 다중 컨슈머 보장 자주 쓰는 자리
Pub/Sub X △ (broadcast) at-most-once 채팅·broadcast
List X (1:1) at-most-once (LMOVE 로 at-least) 단순 작업 큐
Stream ◯ (Consumer Group) at-least-once durable queue, event sourcing

at-most-once(최대 한 번 도착, 손실 허용) vs at-least-once(최소 한 번 도착, 중복 허용) 차이가 선택의 핵심. 54·58편에서 깊이 풀어요.

10. Bulk Loading

대량 데이터 import — Pipelining(여러 명령을 한 묶음으로 보내는 기법) + redis-cli --pipe 모드.

$ cat data.txt | redis-cli --pipe
All data transferred. Waiting for the last reply...
Last reply received from server.
errors: 0, replies: 1000000

각 라인이 RESP 프로토콜(Redis 가 쓰는 텍스트 직렬화 형식) 형태의 명령이에요. 수백만 명령을 수 초~수 분 안에 로드해요.

여기서 시험 함정이 하나 있어요 — Bulk Loading 중에는 eviction 정책(메모리 한도 도달 시 키 자동 제거 규칙)이 발동할 수 있음. maxmemory 가까이 가면 일부 키가 사라지는 사고가 나요. 도입 전에 충분한 메모리 확보, 또는 eviction 일시 disable.

안티-패턴 — 자주 보이는 함정 5가지

(1) KEYS * — 운영 환경 금지

> KEYS user:*       # 수만 키 환경에서 Redis 전체 멈춤

O(N) blocking. 운영 환경에서는 SCAN 사용:

> SCAN 0 MATCH user:* COUNT 100

(2) 큰 키 (Big Key)

수만 멤버 Hash·List·Set 은 단일 조회만 해도 수 ms 가 걸려서 Redis 전체 응답 시간에 영향을 줘요. 분할을 검토해요.

(3) Hot Key — 단일 키 부하 집중

한 키에 초당 수만 요청이 몰리면 그 키가 있는 노드(Cluster)가 병목이 돼요. 키 분산({user42}:counter:1·:counter:2...)을 검토해요.

(4) TTL 없는 임시 데이터

세션·캐시에 TTL 안 박으면 영원히 메모리 누수가 나요. 모든 임시 키는 TTL 필수.

(5) Cache Stampede

동일 키가 동시에 만료되면 수많은 요청이 동시에 DB로 직격해요. Probabilistic early refresh(만료 직전 일부 요청이 미리 갱신) 또는 mutex pattern(한 요청만 DB 조회, 나머지는 대기) 으로 완화해요.

시험 직전 한 번 더 — Redis 패턴 함정 압축 노트

  • 자주 쓰는 패턴 = 9가지 (cache-aside·write-through/behind·distributed lock·rate limit·leaderboard·session·counting·indexing·messaging·bulk loading)
  • 대부분의 패턴 = 자료구조 + 도구의 조합
  • Cache-aside = read 시 miss → DB → cache 저장 (TTL 박기)
  • Cache-aside 함정 = Thundering herd (cache 만료 시 DB 직격 폭증)
  • Write-through = 쓰기 시 DB + Cache 둘 다 (일관성 ↑, 응답 시간 ↑)
  • Write-behind = Cache 즉시 + DB 비동기 (응답 시간 ↓, 일시 불일치)
  • Distributed Lock = SET NX EX + Lua 해제 (63편)
  • Rate Limiting = Fixed Window (단순) vs Sliding Window (정확)
  • Fixed Window 함정 = 경계 시점 2배 트래픽
  • Leaderboard = Sorted Set + ZREVRANGE/ZREVRANK
  • Session = String JSON 또는 Hash (부분 갱신 필요 시)
  • 세션 만료 후속 = Keyspace Notifications (56편)
  • 정확 Counting = INCR·HINCRBY·ZINCRBY
  • 근사 Counting = PFADD·PFCOUNT (HyperLogLog, 메모리 절약)
  • Secondary Indexing = Sorted Set (범위) + Set (필터·집합 연산)
  • Real-time Messaging 3가지 = Pub/Sub · List · Stream (각각 영속성·보장 다름)
  • Bulk Loading = Pipelining + redis-cli --pipe
  • 안티패턴 — KEYS * 절대 금지, SCAN 사용
  • 안티패턴 — Big Key (수만 멤버) → 분할
  • 안티패턴 — Hot Key → 분산 (hash tag + 분할)
  • 안티패턴 — TTL 없는 임시 데이터 = 메모리 누수
  • 안티패턴 — Cache Stampede → probabilistic refresh / mutex pattern
  • 같은 문제 → 여러 패턴 가능, 자료구조 선택은 요구사항 미세한 차이

공식 문서: Redis Patterns 에서 패턴별 자세한 사양을 확인할 수 있어요.

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

이전 글:

다음 글:

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

답글 남기기

error: Content is protected !!