[Daily morning study] Redis 데이터 구조와 캐싱 전략

#daily morning study

Image


Redis란?

Redis(Remote Dictionary Server)는 인메모리(in-memory) 기반의 키-값 저장소다. 데이터를 RAM에 올려두기 때문에 디스크 I/O 없이 읽고 쓸 수 있어 속도가 극단적으로 빠르다. 일반적으로 캐시, 세션 저장소, 메시지 브로커, 실시간 랭킹 등에 쓰인다.


핵심 데이터 구조

String

가장 기본적인 타입. 텍스트뿐 아니라 정수나 직렬화된 객체도 저장할 수 있다.

SET user:name "Alice"
GET user:name         # "Alice"

SET counter 0
INCR counter          # 1
INCRBY counter 5      # 6
  • SETNX key value: 키가 없을 때만 저장 (분산 락 구현에 자주 사용)
  • SETEX key seconds value: TTL과 함께 저장

List

내부적으로 이중 연결 리스트(doubly linked list) 구조. 양 끝에서의 push/pop이 O(1)이라 큐나 스택 구현에 적합하다.

RPUSH queue "job1" "job2" "job3"
LPOP queue              # "job1"
LRANGE queue 0 -1       # ["job2", "job3"]
  • 작업 큐, 최근 방문 목록 등에 활용
  • 중간 인덱스 접근은 O(n)이므로 랜덤 접근이 잦은 경우는 비효율적

Hash

필드-값 쌍의 집합. 객체를 표현할 때 String에 JSON을 통째로 저장하는 것보다 구조화해서 다룰 수 있다.

HSET user:1001 name "Alice" age 30 email "alice@example.com"
HGET user:1001 name          # "Alice"
HGETALL user:1001
HINCRBY user:1001 age 1      # 31
  • 사용자 프로필, 설정값 저장에 적합
  • 개별 필드만 업데이트할 수 있어 String보다 효율적

Set

중복을 허용하지 않는 문자열 집합. 교집합, 합집합, 차집합 연산을 지원한다.

SADD tags:post:1 "redis" "database" "cache"
SADD tags:post:2 "redis" "nosql"

SINTER tags:post:1 tags:post:2   # ["redis"]
SUNION tags:post:1 tags:post:2   # ["redis", "database", "cache", "nosql"]
SISMEMBER tags:post:1 "redis"    # 1 (true)
  • 팔로워/팔로잉 관계, 태그, 좋아요 목록 등에 활용

Sorted Set (ZSet)

각 요소에 score(실수)를 부여하고 score 기준으로 정렬된 집합. 실시간 랭킹 시스템의 핵심 자료구조다.

ZADD leaderboard 1500 "Alice"
ZADD leaderboard 2300 "Bob"
ZADD leaderboard 1800 "Charlie"

ZRANGE leaderboard 0 -1 WITHSCORES        # 오름차순
ZREVRANGE leaderboard 0 2 WITHSCORES      # 상위 3명
ZRANK leaderboard "Alice"                 # 0 (0-indexed)
ZINCRBY leaderboard 200 "Alice"           # score += 200

TTL과 만료 정책

키마다 만료 시간(TTL)을 설정할 수 있다.

SET session:abc123 "user_data" EX 3600   # 1시간 후 만료
TTL session:abc123                        # 남은 초
PERSIST session:abc123                    # 만료 제거

Redis의 키 만료 처리 방식:

  • Lazy expiration: 만료된 키에 접근할 때 그때서야 삭제
  • Active expiration: 주기적으로 랜덤 샘플링해서 만료 키 삭제

두 방식을 병행하기 때문에 메모리 낭비와 CPU 부하 사이의 균형을 맞춘다.


캐싱 전략

Cache-Aside (Lazy Loading)

가장 흔히 쓰이는 패턴. 애플리케이션이 캐시를 직접 관리한다.

읽기:
1. 캐시에서 조회
2. 없으면(cache miss) DB에서 조회
3. DB 결과를 캐시에 저장 후 반환

쓰기:
1. DB에 먼저 저장
2. 캐시 무효화(invalidate) 또는 업데이트
  • 장점: 실제로 필요한 데이터만 캐시에 올라감
  • 단점: 첫 요청은 항상 cache miss (cold start)

Write-Through

DB에 쓸 때마다 캐시도 동시에 업데이트.

쓰기:
1. 캐시에 저장
2. DB에 저장
  • 장점: 캐시와 DB가 항상 일치
  • 단점: 쓰기 지연 증가, 자주 읽히지 않는 데이터도 캐시에 쌓임

Write-Behind (Write-Back)

캐시에 먼저 쓰고, DB 반영은 비동기로 나중에 처리.

  • 장점: 쓰기 성능 극대화
  • 단점: 캐시 장애 시 데이터 유실 위험

Cache Stampede 문제

TTL이 만료된 동일한 키에 대해 다수의 요청이 동시에 DB로 쏟아지는 현상. 해결 방법:

  • Mutex lock: 첫 번째 요청만 DB 조회, 나머지는 대기
  • Probabilistic early expiration: 만료 직전에 확률적으로 미리 갱신
  • Background refresh: 별도 프로세스가 만료 전에 갱신

메모리 관리와 Eviction 정책

Redis 메모리 한도에 도달하면 설정된 eviction policy에 따라 키를 제거한다.

정책설명
noeviction메모리 초과 시 오류 반환 (기본값)
allkeys-lru모든 키 중 LRU 순으로 제거
volatile-lruTTL 있는 키 중 LRU 순으로 제거
allkeys-lfu모든 키 중 LFU(가장 적게 사용된) 순으로 제거
volatile-ttlTTL 짧은 키부터 제거
allkeys-random무작위 제거

캐시 용도로 쓸 때는 allkeys-lru 또는 allkeys-lfu가 일반적으로 적합하다.


영속성 (Persistence)

Redis는 인메모리지만 디스크에 데이터를 저장하는 방식을 지원한다.

RDB (Redis Database)

특정 시점의 스냅샷을 .rdb 파일로 저장.

# redis.conf
save 900 1      # 900초 안에 1번 이상 변경 시 저장
save 300 10     # 300초 안에 10번 이상 변경 시 저장
  • 장점: 파일 크기가 작고 복구 속도 빠름
  • 단점: 스냅샷 간격 사이의 데이터 유실 가능

AOF (Append Only File)

모든 쓰기 명령을 로그 파일에 순서대로 기록.

# redis.conf
appendonly yes
appendfsync everysec   # 1초마다 fsync (권장)
  • 장점: 데이터 유실 최소화
  • 단점: 파일 크기가 크고 재시작 복구 시간이 길어질 수 있음

실제 운영 환경에서는 RDB + AOF를 함께 사용하는 경우가 많다.


Redis Cluster와 복제

Replication

Master-Replica 구조로 읽기 부하를 분산하고 가용성을 높인다.

  • Master: 쓰기 처리
  • Replica: 읽기 처리 + Master 장애 시 failover 후보

Redis Cluster

데이터를 16384개의 슬롯으로 나눠 여러 노드에 분산 저장(샤딩). 키의 CRC16 값 % 16384로 슬롯을 결정한다.

노드 A: 슬롯 0 ~ 5460
노드 B: 슬롯 5461 ~ 10922
노드 C: 슬롯 10923 ~ 16383

각 노드는 Master + Replica 쌍으로 구성해 고가용성을 확보한다.


실전 활용 사례

  • 세션 저장소: TTL 기반으로 세션 자동 만료 처리
  • API 요청 제한(Rate Limiting): INCR + EXPIRE 조합
  • 실시간 랭킹: Sorted Set으로 점수 집계
  • 분산 락: SET key value NX EX seconds로 원자적 락 획득
  • Pub/Sub 메시징: 간단한 이벤트 브로드캐스트 (단, 메시지 보장 없음)
# 분산 락 예시
SET lock:resource1 "owner_id" NX EX 30
# NX: 키가 없을 때만 성공
# EX 30: 30초 후 자동 해제 (데드락 방지)