7 분 소요

[Redis]Redis 톺아보기

1. Redis란?

Redis는 메모리 기반의 키-값 저장소로, 단순한 캐시를 넘어서 다양한 역할을 수행할 수 있음.

image

  • 메모리 기반이라 속도 빠름
  • 다양한 자료구조 지원 (String, List, Hash, Set, ZSet 등)
  • Pub/Sub, Stream, 분산 락 기능까지 지원
  • 싱글 스레드 기반 이벤트 루프 구조

장점: 빠름, 단순함, 유연함

단점: 휘발성, 메모리 용량 제한


2. 백업 방식 (Persistence)

메모리에 데이터를 관리하므로 매우 빠른 속도로 데이터를 저장 및 조회할 수 있다. 하지만 메모리 특성상 저장된 데이터는 사라질(휘발성) 가능성이 있다. 이를 보완하고자 레디스는 관리하고 있는 데이터에 영속성을 제공함.

즉, 메모리에 있는 데이터를 디스크에 백업하는 기능을 제공하며, RDB 방식이나, AOF 방식으로 백업할 수 있다.

image (1)

RDB (Redis Database)

: 메모리에 있는 데이터 전체에서 스냅샷을 작성하고, 이를 디스크로 저장하는 방식

  • 특정 시간마다 여러 개의 스냅샷을 생성하고, 데이터를 복원해야 한다면 스냅샷 파일을 그대로 로딩만 하면 됨.
  • 하지만, 스냅샷 이후 변경된 데이터는 복구할 수 없음. → 데이터 유실(loss)

AOF (Append Only File)

: 데이터가 변경되는 이벤트가 발생하면 이를 모두 로그에 저장하는 방식

  • 거의 실시간 복구 가능, 하지만 복구 속도 느림, 파일 크기 큼
  • 데이터를 생성, 수정, 삭제하는 이벤트를 초 단위로 취합 및 로그 파일에 작성
  • 모든 데이터의 변경 기록들을 보관하고 있으므로 최신 데이터 정보를 백업 가능
  • RDB 방식에 비해 데이터 유실량이 적음(초 단위 데이터는 유실 가능).
  • RDB 방식보다 로딩 속도가 느리고 파일 크기가 큰 것이 단점

⇒ 일부 데이터 손실에 영향을 받지 않는 경우(캐시로만 사용할 때), RDB

⇒ 장애 상황 직전까지의 모든 데이터가 보장되어야 할 경우, AOF

⇒ 강력한 내구성이 필요한 경우, RDB + AOF

⇒ 레디스는 일반적으로 AOF와 RDB를 동시에 사용하여 데이터를 백업!

  • 예를 들면, 매일 7시마다 RDB 스냅샷을 생성하고, RDB 생성 이후에 변경되는 데이터는 AOF로 백업.

Redis 백업 방식 설정

1) RDB

  • 자동 : redis.conf 파일 → SAVE 옵션 설정 (시간 기준)
  • 수동 : redis-cli에서 BGSAVE 커맨드를 이용하여 수동으로 RDB 파일 저장
    • SAVE 커맨드는 절대 사용 X (레디스는 싱글 스레드, 저장하는 동안 다른 작업 수행 불가)

2) AOF

  • 자동 : redis.conf 파일 → auto-aof-rewrite-percentage 옵션 설정 (크기 기준)
  • 수동 : redis-cli에서 BGREWRITEAOF 커맨드를 이용하여 수동으로 AOF 파일 재작성

3. 싱글 스레드 아키텍처

image (2)

레디스의 코어스레드는 싱글스레드!

레디스는 사용자들이 실행한 명령어들을 이벤트 루프(event loop) 방식으로 처리한다. 즉, 클라이언트가 실행한 명령어들을 Event Queue에 적재하고 싱글 스레드로 하나씩 처리한다.

메모리를 사용하기 때문에 싱글 스레드로 데이터를 빠르게 처리할 수 있다!

장점

  1. 멀티 스레드 환경이 아니라 Context Switch 발생 X → 효율적인 시스템 리소스 사용 가능
  2. 1번과 같은 이유로 Deadlock 발생 X

단점

  1. 싱글 스레드 이므로 전체 데이터 스캔과 같은 오버헤드가 큰 명령어를 처리하는 동안 다른 명령어를 처리 불가.

  2. 이 때, 다른 명령어들은 이벤트 큐에 저장되어 있는 시간이 길어짐. → 응답 속도 저하

    또한, 멀티 스레드 환경이 아니라 Context Switch가 발생하지 않으므로 효율적으로 시스템 리소스를 사용할 수 있다. 또한 Dead lock 같은 현상이 발생하지 않는다.

근데 누구는 redis가 멀티스레드라고 하던데요?;;;

Single thread vs Multi Thread

사실 Redis는 6.0부터 Multi Thread라고 할 수 있음..

왜냐하면 ThreadedIO가 추가되면서 사용자 명령이 Multi Thread로 동작하기 때문!

그렇다면 Single thread가 아닌데 redis의 장점이였던 Atomic을 어떻게 보장하지?라는 의문이 들 수 있는데

하지만 걱정할 필요 없다! redis는 Single thread이기 때문입니다..^^ (??)

이게 무슨 말이냐면 redis는 부분적으로 Single thread와 Multi Thread를 함께 사용한다.

클라이언트로 부터 전송된 네트워크를 읽는 부분과 전송하는 부분Multi Thread로 구현되어있으며

우리가 redis에 요청한 명령을 실행하는 부분은 Single thread로 구현되어 있다.

때문에 single thread의 장점인 Atomic한 요청 처리가 가능한 것이다.

image (3)

  • I/O : 네트워크 connection
  • Event Loop : 명령 실행부분

→ 결과적으로 명령을 실행하는 부분이 Single thread라면 성능에는 큰 영향이 없는거 아닌가?

위와 같은 방식을 활용한다면 클라이언트가 네트워크 패킷을 여러번 보냈을 때 Redis는 각 요청의 응답을 기다리지 않고 일괄 처리하게 되는데 이는 네트워크 오버헤드를 줄이면서 시스템의 전체 처리량을 향상시킨다는 이유였다. (이러한 방식을 Pipelining이라 부름.)

해당 구조를 택하면서 Redis의 성능이 2배이상 좋아졌다고 한다.

아마 기존 Redis의 속도가 워낙 빠르다보니 네트워크 대기시간만 줄여도 엄청난 성능향상이 나타난 것 같다..

이전 redis 구조

img

redis 6.0 이후 구조

img

관련 레퍼런스 : https://www.linkedin.com/pulse/why-heck-single-threaded-redis-lightning-fast-beyond-in-memory-kapur/


4. Redis는 주로 어따 씀?

주 데이터 저장소

  • AOF, RDB 백업 기능과 레디스 아키텍처를 사용하여 주 저장소로 데이터를 저장할 수 있음!
  • 하지만 메모리 특성상 용량이 큰 데이터 저장소로는 적절하지 않다.

캐시 (Cache)

  • DB 조회 결과 캐싱
  • EXPIRE로 TTL 설정 가능
  • Cache Aside 패턴으로 일관성 보장

분산락 (Distributed Lock)

  • 분산 환경에서 여러 시스템이 동시에 데이터를 처리 할 때는 특정 공유 자원의 사용 여부를 검증하여 데드 락을 방지할 필요가 있음 → 그래서 분산락으로 활용
  • SET lock_key value NX EX 10 → Redlock 구현 가능

순위 계산

  • 레디스에서 제공하는 ZSet(Sorted Set) 자료 구조를 이용하여 순위 계산 용도로 사용 → 순위계산 용이하다네요~
  • ZSet은 정렬 기능이 포함된 Set 자료 구조이므로 쉽고 빠르게 순위를 계산가능

메세지 큐 / Pub-Sub

  • PUBLISH, SUBSCRIBE 명령으로 채널 기반 메시징 가능

5. 아키텍처 구성

image (5)

Standalone

  • 단일 인스턴스 운영
  • 실습, 소규모에 적합

Replication

  • Master와 Replica로 구성

  • 단순한 복제 연결 시 사용한다. (relicaof 커맨드 이용)

  • 비동기식 복제 (복제가 잘 됐는지 확인하지 않음)

  • HA(고가용성) 기능이 없으므로 장애 상황 시 수동으로 복구

    한다.

    • 장애 상황 시 스프링에서 새로운 Redis 서버의 연결 정보를 변경 해주어야 함.

Sentinel

  • Sentinel, Master, Replica로 구성, 자동 Failover가 가능한 HA(High Availability) 구성

  • 레디스 센티넬은 레디스 서버들(Master, Replica)을 관리한다. 센티넬은 주기적으로 레디스 서버들을 모니터링한다. 마스터 서버가 서비스할 수 없는 상태가 되면 다른 레플리카를 마스터 서버로 변경한다.

    • Sentinel 노드가 Redis 마스터와 레플리카 노드를 감시

    • Master가 비정상 상태일 때 자동으로 Failover(자동 복구)

    • 애플리케이션은 Sentinel과 연결하기 때문에, 장애 상황 발생 시 연결 정보 변경 필요 없음

    • Sentinel 노드도 장애 상황이 발생할 수 있기 때문에

      반드시 3대 이상의 홀수로 존재

      해야 함.

      • 과반수 이상의 Sentinel이 동의(Quorum based)가 있어야 Failover 진행
      • 많은 리소스가 필요하므로 Sentinel과 Master 혹은 Replica를 같은 서버에 올려 사용하기도 함.

Cluster

image (6)

  • Redis 3.0+ 부터 지원, 클러스터에 포함된 노드들이 서로 통신하는 구조
  • 레디스 클러스터는 클러스터에 포함된 노드들이 서로 통신하면서 HA를 유지한다.
  • 거기에 샤딩 기능까지 기본 기능으로 사용할 수 있다. 클러스터 내부에는 센티넬과 동일하게 마스터와 레플리카는 짝을 이루어 데이터를 복제한다. 클러스터 내부의 모든 노드는 모두 서로 연결되어 있는 메시(Mesh) 구조로 되어 있으며, 가십 프로토콜(gossip protocol)을 사용하여 서로 모니터링 한다.
  • 센티넬과 동일하게 Master와 Replica는 짝을 이루어 데이터를 복제
  • 클러스터를 구성하기 위해 세 개의 마스터 노드는 반드시 필요.
  • 레플리카 노드의 개수는 0개 혹은 그 이상으로 설정 가능.
    • 고가용성을 위해 반드시 한 개 이상의 레플리카를 설정하는 것이 좋음
  • 데이터를 마스터 노드들에 샤딩
    • 해시 함수를 사용하여 데이터 분배, 데이터의 키 값을 해시 함수로 넘긴 후 리턴 값을 사용하여 어떤 노드에 저장할지 결정한다. * 리턴 값은 항상 0 ~ 16383 값을 리턴
    • 클러스터에 노드를 추가하거나 제거할 경우 레디스 명령어를 사용하여 해시 함수 값 범위를 조정 가능
  • 리밸런싱(Rebalancing) & 리샤딩(Re-shard)
    • 조정된 범위에 포함되는 레디스 데이터들은 자동으로 재분배되어 설정된 위치로 이동한다.
  • 애플리케이션은 레디스 클러스터의 노드 중 하나라도 연결되면 클러스터의 전체 상태 정보를 확인 가능
    • 장애 상황 발생 또는 노드 확장(Scale out) 시 애플리케이션의 Redis 서버 연결 정보 변경 필요 X

image (9)


6. 자료구조 정리

redis는 근본적으로 키-밸류 스토어므로 무조곤 key를 가져야 함!

즉, 어떤 형태의 자료 구조를 사용하더라도 키는 반드시 필요하다는 말씀

image (10)

자료구조 설명 활용 예시
String 단일 값 카운터, 토큰 저장
List 연결 리스트 채팅 로그, 큐
Hash 필드-값 구조 사용자 프로필
Set 중복 없는 집합 태그, 친구 목록
Sorted Set (ZSet) 정렬된 집합 순위, 점수 저장
Bitmap 비트 저장 방문 여부 체크, 일간 활성 유저
HyperLogLog 근사 카운팅 UV 추정, 검색어 수
Stream 로그형 메시지 이벤트 스트리밍, Kafka 대안

7. Redis 유효 기간

레디스에 저장되는 모든 데이터는 유효 기간을 설정할 수 있다. 유효 기간이 지난 데이터는 레디스가 해당 데이터를 메모리에서 삭제한다. ⇒ 메모리를 효율적으로 사용 가능

레디스는 다양한 방법으로 유효기간을 설정할 수 있음.

  1. EXPIRE 명령어를 사용하여 이미 생성된 데이터에 유효 기간을 설정.
  2. 데이터를 생성할 때 EX 옵션을 사용하여 생성과 동시에 유효 기간을 설정.

레디스는 메모리에 데이터를 저장하므로 저장 공간이 한정적이어서 레디스에 데이터를 저장할 때는 데이터의 유효 기간을 설정하는 것을 권장함.

만약, 유효기간을 설정하지 않는다면 직접 데이터를 삭제할 때까지 영원히 유지됨..


8. 캐시 패턴 (Cache Aside)

흐름

  1. 클라이언트가 데이터 요청
  2. Redis 캐시에 먼저 조회
  3. 캐시에 없으면 → DB 조회 후 Redis에 저장
  4. 데이터 수정 시에는 DB → 캐시 삭제 or 갱신
# 조회
value = redis.get(key)
if not value:
    value = db.query(key)
    redis.set(key, value, ex=60)
return value

# 수정
db.update(key, new_value)
redis.delete(key)

9. Redlock – 고급 분산락 구현

Redis의 단일 인스턴스로는 락 보장이 불완전할 수 있음 → Redlock 알고리즘 사용

  • 여러 Redis 인스턴스에 락을 동시에 설정 (다수 과반수 성공 시 성공)
  • 시간 동기화 및 TTL 필수
  • SET key value NX PX 10000 활용

→ 고가용 환경에서는 분산락 보장에 Redlock 추천

Redlock에 대한 야무진 블로그 : https://velog.io/@ohjinseo/Redis-분산락-알고리즘-Redlock


10. Eviction Policy (메모리 부족 시 처리 방식)

Redis는 메모리가 가득 차면 다음 중 하나를 기준으로 데이터를 제거함

  • noeviction (기본값): 새 데이터 저장 실패
  • allkeys-lru: 가장 오랫동안 사용되지 않은 키 제거
  • volatile-lru: TTL 설정된 키 중 LRU(Least Recently Used) 제거
  • allkeys-random, volatile-random: 무작위 제거
  • volatile-ttl: 가장 TTL이 짧은 키 제거

캐시 용도라면 보통 allkeys-lru 또는 volatile-lru가 적절

https://velog.io/@wlsgur1533/효율적인-캐시-띄우기를-위한-Redis-Eviction-정책


마무으리

  • Redis는 단순 캐시를 넘어 다양한 목적에 사용 가능
  • 빠르고 유연하지만 메모리 기반의 한계도 존재
  • 아키텍처 선택, 자료구조 활용, 일관성 유지까지 모두 고려해야 함
  • 캐시/락/순위/메세지 큐 등 실무 전방위 활용 가능함

댓글남기기