본문 바로가기

[개발자를 위한 레디스] 5장 레디스를 캐시로 사용하기

@bum0w02025. 11. 10. 01:49

레디스와 캐시

캐시란?

캐시는 데이터 원본보다 더 빠르고 효율적으로 접근할 수 있는 임시 저장소를 의미한다.

사용자가 동일한 정보를 반복적으로 요청할 때, 원본이 아닌 캐시에서 데이터를 가져옴으로써 시스템 리소스를 절약할 수 있다.

 

캐시 도입이 효과적인 조건

  1. 원본 데이터 접근 시간이 길 때
  2. 검색이나 계산에 많은 시간이 소요되는 경우
  3. 캐시 접근이 원본보다 빠를 때
  4. 데이터 변동이 적고 안정적일 때
  5. 데이터가 자주 조회될 때

 

캐시로서의 레디스

 

특징

  1. 단순한 키-값 저장
    • 데이터를 저장하고 반환하는 과정이 간단함
  2. 다양한 자료 구조 지원
    • 리스트(List), 해시(Hash) 등 애플리케이션 자료 구조를 그대로 저장 가능
  3. 빠른 성능
    • 모든 데이터를 메모리에 저장하므로 검색과 반환 속도가 매우 빠름
  4. 고가용성 지원
    • 자체적으로 장애 복구 및 고가용성 기능 제공
  5. 확장성 용이
    • Redis 클러스터를 사용하면 캐시의 스케일 아웃(수평 확장) 용이

 

캐싱 전략

레디스를 캐시로 사용할 때 레디스를 어떻게 배치할 것인지에 따라 서비스의 성능에 큰 영향을 끼칠 수 있다.

읽기 전략 - look aside

Redis를 캐시로 사용할 때 가장 일반적인 배치 방식이다.

 

동작 방식

  • 애플리케이션이 데이터를 요청할 때 먼저 Redis에서 조회
  • Redis에 데이터가 없으면 원본 데이터베이스에서 조회 후 Redis에 저장
  • 필요할 때만 캐시에 데이터를 적재하므로 Lazy Loading이라고도 함

장점

  • Redis에 문제가 생겨도 즉시 서비스 장애로 이어지지 않음
  • 데이터베이스에서 직접 데이터를 가져올 수 있음

단점

  • 애플리케이션의 Redis 연결이 많을 경우, 캐시 미스 시 모든 요청이 한 번에 데이터베이스로 몰려 부하 증가

look aside 전략 (1)

  • 애플리케이션은 찾는 데이터가 먼저 캐시에 있는지를 확인한 뒤, 캐시에 데이터가 있으면 캐시에서 데이터를 읽어온다. (캐시 히트)

 

look aside 전략 (2)

 

  • 찾는 데이터가 없으면, 캐시 미스가 발생한다.
  • 애플리케이션은 직접 데이터베이스에 접근해 찾는 데이터를 가져온 후, 이를 다시 캐시에 저장하는 과정을 거친다.

 

 

캐시 워밍

  • 초기에 부하가 몰리는 것을 막기 위해, 미리 데이터베이스에서 캐시로 데이터를 밀어넣어주는 작업을 한다.
  • 이를 캐시 워밍이라고 한다.

 

쓰기 전략과 캐시의 일관성

캐시 불일치

  • 데이터가 변경될 때 원본 데이터베이스에만 업데이트되어 캐시에는 반영되지 않는다면 데이터 간 불일치가 일어난다. (캐시 불일치)
  • a라는 값은 데이터베이스에 28로 업데이트됐지만, 레디스에는 아직 3인 데이터가 저장되어 있다.

 

1. write through

write through

 

데이터베이스에 업데이트할 때마다 캐시에도 동시에 업데이트하는 방식이다.
이 방식을 사용할 경우, 캐시 저장 시 만료 시간(TTL) 설정을 권장한다.

 

장점

  • 캐시는 항상 최신 데이터를 유지할 수 있음

단점 

  • 데이터가 매번 두 곳(DB + 캐시)에 저장되므로, 쓰기 성능에 부담 발생

 

2. cache invalidation

cache invalidation

데이터베이스에 값을 업데이트할 때마다 캐시에서는 해당 데이터를 삭제하는 방식이다.


장점

  • 캐시에서 데이터를 삭제하는 것은, 새로운 데이터를 저장하는 것보다 리소스를 적게 사용

 

3. write behind(write back)

write behind

쓰기가 빈번한 서비스에서 고려할 수 있는 방식이다.

 

동작 방식

  1. 데이터를 캐시에 먼저 업데이트하여 빠른 접근성 확보
  2. 이후 일정 건수나 시간 간격에 따라 비동기적으로 데이터베이스에 반영

단점

  • 캐시에 문제가 생기면, 일정 기간 동안의 데이터 손실 가능성 존재

 

 

캐시에서의 데이터 흐름

Redis 캐시 관리 포인트

 

1. 메모리 기반 저장

  • Redis는 모든 데이터를 메모리에 저장하므로, 서버 스토리지보다 보관 용량이 제한됨

2. 캐시 용량 관리

  • 캐시는 가득 차지 않도록 관리해야 하며, 새로운 데이터가 들어오면 기존 데이터를 삭제하는 방식으로 유지

3. TTL(Time-To-Live) 설정

  • 데이터를 캐시에 저장할 때, 적절한 만료 시간(TTL)을 지정하여 자동 삭제되도록 설정하는 것이 좋음

 

만료 시간

  • TTL은 데이터가 Redis에 얼마나 오래 저장될지를 설정하는 시간이다.
  • 만료 시간이 지나면, 해당 키와 관련된 데이터는 자동으로 Redis에서 삭제된다.

Redis TTL 동작 예시 및 특징

1. 기본 TTL 설정

SET a 100       # 키 a에 100 저장
EXPIRE a 60     # 만료 시간 60초 설정
TTL a           # 남은 TTL 확인 → 58초

키를 생성하고 TTL을 설정하면, 지정된 시간 후 자동 삭제됨

 

 

2. 데이터 조작 시 TTL 유지

INCR a          # 값 증가 → 101
TTL a           # TTL 유지 → 51초
RENAME a apple  # 키 이름 변경
TTL apple       # TTL 유지 → 41초

값을 증가시키거나 키 이름을 바꿔도 TTL은 그대로 유지

 

 

3. 키 덮어쓰기 시 TTL 삭제

SET b 100       # 키 b 생성
EXPIRE b 60     # TTL 60초 설정
SET b banana    # 기존 키 덮어쓰기
TTL b           # TTL 사라짐 → -1

기존 키에 새 값을 저장하면 이전 TTL은 제거됨

 

 

메모리 관리와 maxmemory-policy 설정

1. 기본 개념

  • Redis는 메모리 용량을 초과하면 내부 정책에 따라 어떤 키를 삭제할지 결정한다.
    • maxmemory: 데이터 최대 저장 용량 설정
    • maxmemory-policy: 용량 초과 시 처리 방식 설정

2. 정책 종류

 

(1) Noeviction

  • 기본값
  • 데이터가 가득 차면 에러 반환 (데이터 삭제 X)
  • 관리자가 직접 삭제 필요
  • 캐시 용도로는 권장되지 않음

(2) LRU eviction (Least Recently Used)

  • 최근에 사용되지 않은 데이터부터 삭제
    • 설정값:
    •   - volatile-lru: 만료 시간이 설정된 키만 LRU 방식으로 삭제
    •   - allkeys-lru: 모든 키에 대해 LRU 방식으로 삭제

(3) LFU eviction (Least Frequently Used)

  • 자주 사용되지 않은 데이터부터 삭제
    • 설정값:
    •   - volatile-lfu: 만료 시간이 설정된 키만 LFU 방식으로 삭제
    •   - allkeys-lfu: 모든 키에 대해 LFU 방식으로 삭제

(4) RANDOM eviction

  • Redis에 저장된 키 중 하나를 임의로 삭제
  • 부하가 상대적으로 적지만, 자주 사용하는 캐시 삭제 시 비효율
    • 설정값:
    •   - volatile-random: 만료 시간이 설정된 키만 랜덤 삭제
    •   - allkeys-random: 모든 키에 대해 랜덤 삭제

(5) volatile-ttl

  • 만료 시간이 가장 짧은 키를 삭제
  • 근사 알고리즘 사용으로 간단하게 키 선택 가능

 

캐시 스탬피드 현상

캐시 스탬피드 현상

  • 여러 애플리케이션이 동일한 키를 조회하는데, 키가 만료되어 삭제되면 모두 데이터베이스에서 동시에 데이터를 조회 → 중복 읽기
  • 읽은 데이터를 Redis에 다시 저장하면서 여러 번 기록 → 중복 쓰기

    해결 방법: 적절한 TTL 설정
    • 만료 시간을 너무 짧게 설정하지 않음
    • 여러 애플리케이션에서 동시에 접근하고 반복적으로 사용되는 데이터는 충분히 긴 TTL(Time-To-Live)설정 권장

 

선 계산

def fetch(key):
    value = redis.get(key)
    if(!value):
    	value = db.fetch(key)
        redis.set(value)
    return value
  • Look-Aside 방식에서는 애플리케이션이 먼저 캐시를 확인하고, 데이터가 없으면 DB에서 가져와 캐시에 저장한다.  
  • 만료 전에 데이터를 미리 갱신하면, 여러 애플리케이션이 동시에 DB에 접근하는 중복 요청을 줄일 수 있다.

 

def fetch(key, expiry_gap):
    ttl = redis.ttl(key)
    
    if ttl - (random() * expiry_gap) > 0:
    	return redis.get(key)
    else:
    	value = db.fetch(key)
        redis.set(value, KEY_TTL)
        return value

# Usage
fetch('hello', 2)
  • Redis는 만료 전에 랜덤 확률로 DB에 접근해 캐시를 미리 갱신할 수 있다.  
  • 이를 통해 캐시 스탬피드 현상을 줄이고 전체 성능을 개선할 수 있으며, expiry_gap 값은 불필요한 갱신을 방지하도록 적절히 설정하는 것이 중요하다.

 

PER(Probabilistic Early Recalculation) 알고리즘

  • 캐시 값이 만료되기 전에 언제 DB에서 값을 갱신할지 최적으로 계산
  • GET 대신 PER 함수를 사용하면 캐시 스탬피드 현상 감소 및 성능 최적화
공식 : currentTime - (timeToCompute * beta * log(rand())) > expiry
  • currentTime: 현재 남은 만료 시간  
  • timeToCompute: 값을 다시 계산하는 데 걸리는 시간  
  • beta: 1.0보다 큰 값으로 설정 가능  
  • rand(): 0~1 사이 랜덤 값  
  • expiry: 키를 재설정할 때 새로 설정할 TTL

 

 

세션 스토어로서의 레디스

세션이란?

서비스를 사용하는 클라이언트의 상태 정보를 의미한다.  

애플리케이션은 현재 로그인한 사용자가 누구인지, 어떤 활동을 하고 있는지를 세션에 저장하며, 사용자가 서비스를 떠나면 세션에서 해당 정보를 삭제한다.  

 

Ex.

- 쇼핑몰에서 사용자가 장바구니에 담은 상품이나 최근 본 아이템을 세션에 저장  

- 사용자가 로그인한 동안에는 해당 정보가 계속 유지됨  

- 사용자가 각 페이지에서 보내는 시간을 기록해 사용자 행동 분석 및 비즈니스 개선에 활용 가능

 

세션 스토어가 필요한 이유

하나의 세션 스토어

 

- 초기 단계나 프로토타입용 서비스에서는 굳이 세션 스토어가 필요하지 않음

 

세션 스토어 - sticky session

 

- 여러 웹 서버에 트래픽을 분산하면 더 많은 사용자를 수용 가능  

- 각 서버가 세션을 개별적으로 관리하면, 사용자는 특정 서버에 종속됨  

- Ex. 사용자가 장바구니에 아이템을 담았는데 다른 서버로 접속하면 아이템 정보가 사라짐 → 서비스 이용 불가  

- 따라서 여러 서버 환경에서는 중앙 세션 스토어를 사용해 세션 정보를 통합 관리하는 것이 필요

 

세션 스토어 - all-to-all

 

- 사용자를 여러 웹 서버에 분산시킬 수 있지만, 세션 데이터가 모든 서버에 복사되어 불필요한 저장 공간을 차지  

- 대부분의 사용자는 한 번에 한 서버에만 접속하므로 다른 서버에 저장된 세션 정보는 무의미  

- 세션 데이터 복제 과정에서 불필요한 네트워크 트래픽도 많이 발생

 

세션 스토어 - 데이터베이스를 이용

 

- 활성화된 세션 동안 사용자들은 세션 스토어를 지속적으로 액세스  

- 세션 스토어의 응답 속도가 느리면, 곧바로 클라이언트 응답 속도 저하로 이어짐  

- 사용자 수가 많아질수록, 데이터베이스 대신 세션 스토어를 사용하는 것이 서비스 전체 응답 속도를 떨어뜨릴 수 있음

 

세션 스토어 - 레디스를 이용

👍👍

- 세션 스토어를 서버와 데이터베이스에서 분리하고, 여러 서버가 세션 스토어를 바라보도록 구성  

- 사용자는 어떤 서버에 연결되더라도 동일한 세션 데이터를 조회 가능  

- 트래픽을 효율적으로 분산시킬 수 있고, 데이터 일관성을 고려할 필요가 없음  

- 레디스는 관계형 데이터베이스보다 빠르고 접근이 간편하며, 데이터를 가볍게 저장 가능

 

hash 자료 구조를 이용한 세션 데이터 저장

  • 레디스의 hash 자료 구조는 세션 데이터를 저장하기에 알맞은 형태다.
> HMSET usersession:1 Name Garimoo IP 10:20:104:30 Hits 1
OK

> HINCRBY usersession:1 Hits 1
1) "2"
  • 레디스는 key-value 형태의 저장소이고, 세션 또한 ID로 데이터를 저장하기 때문에 변환 없이 데이터를 그대로 저장할 수 있다.

 

캐시와 세션의 차이

  • 캐시: 데이터베이스의 완전한 서브셋으로 동작 (look-aside 전략)  
     → 여러 애플리케이션에서 공유 가능
      
  • 세션 스토어: 특정 사용자 ID에만 유효
     → 사용자 간 데이터 공유되지 않음

  • 세션 활성화 중에는 유저 데이터가 세션 스토어에만 저장됨 (DB는 사용하지 않음)  
  • 로그아웃 시 데이터 종류에 따라 DB에 영구 저장(Ex. 장바구니) 또는 삭제(Ex. 최근 본 상품) 결정  

    주의점 : 세션 스토어 장애 시 데이터 손실 가능 
     → 레디스를 세션 스토어로 사용할 때는 캐시보다 더 신중한 운영 필요
bum0w0
@bum0w0 :: bum0w0 님의 블로그

bum0w0 님의 블로그 입니다.

목차