[Redis] 레디스란? 특징, 활용예시, 비교 정리

반응형

레디스란?

Redis는 Remote Disctionary Server의 약자로 키(Key) : 값(Value) 해시 맵과 같은 구조를 가진 NoSQL 데이터베이스이다.

MySQL, Oracle같은 일반 DB와 다르게 하드 디스크(SSD, HDD)에서 데이터를 처리 하는 것이 아니라, 

Redis는 In-Memory(인메모리) 기반의 DB로 메인 메모리(DRAM)에서 데이터를 처리하기 때문에 작업 속도가 월등히 빠르다.

또한 Key-Value 형식으로 데이터가 저장되기 때문에 보다 빠르게 데이터에 접근할 수 있다.

(평균 작업속도가 1 ms 보다 작다고 한다.)

 

DRAM과 SSD, HDD의 비교

 

메모리 계층 구조는 다음과 같이 여러 계층이 있지만 인메모리DB와 일반 DB를 비교하기 위해 2가지에 대해서만 비교 하고 넘어 가고자 한다.

특징 DRAM (메인 메모리) SSD, HDD (하드 디스크)
가격 하드 디스크 보다 비쌈 제일 저렴
지속성 휘발성 비휘발성
속도 하드 디스크 보다 빠름 제일 느림
저장 용량 하드 디스크 보다 작음 제일 큼

(메인 메모리는 휘발성이지만 Redis는 영속성을 지원한다. 그 이유는 아래 특징 목차에서 설명한다.)

 

이처럼 일반 DB에 비해 인메모리DB가 더 가격이 비싸고 용량이 작기 때문에 모든 데이터를 인메모리DB에 저장해서는 안된다.

주로 저장하는 데이터자주 사용되고 수정이 빈번하지 않은 데이터를 저장한다.

 


레디스 특징

 

다양한 데이터 타입 지원 (Redis 컬렉션)

Redis는 Key에 위 처럼 다양한 데이터 타입을 저장할 수 있도록 지원한다.

이 데이터 타입들을 활용하여 개발 편의성을 극대화 할 수 있다.

이 데이터 타입들을 활용하는 방법은 아래의 블로그에 정말 잘 설명되어 있어 링크를 첨부한다.

 

[REDIS] 📚 자료구조 명령어 종류 & 활용 사례 💯 총정리

Redis 데이터 타입 (Collection) Redis의 장점 중 하나는 Key-Value 스토리지에서 Value는 단순한 Object가 아니라 다양한 자료구조를 갖기 때문이다. String, Set, Sorted Set, Hash, List 등 다양한 타입을 지원한다.

inpa.tistory.com

 

영속성(Persistence) 지원

Redis는 휘발성인 인메모리에 데이터를 저장하기 때문에 데이터를 영구적으로 저장할 수 없다.

cache 용도로만 사용한다면 상관없겠지만,

Redis를 cache 이외의 용도로 사용한다면 적절한 데이터 백업이 필요할 수도 있다.

따라서 Redis는 데이터를 영속화하기 위한 "RDB"와 "AOF" 2가지 방법을 제공한다.

 

RDB(Redis Database)

설정한 일정 시간 단위로 레디스 DB의 스냅샷을 백업하고, 필요 시 특정한 시점의 스냅샷으로 롤업할 수 있다.

(Redis의 메모리 데이터를 바이너리 형태로 통째로 저장.)

기본값으론 다음과 같이 설정되어 있다.

RDB 방식은 메모리의 스냅샷을 그대로 저장하기 때문에 서버를 재시작할 때 스냅샷을 다시 읽어 속도가 빠르다.

하지만 스냅샷을 추출하는데 시간이 오래 걸리고 도중에 서버가 꺼지면 이후의 데이터가 모두 사라진다.

 

AOF(Append Onl y File)

각각의 쓰기 명령어들을 개별적으로 특정 로그 파일에 기록해두고, 이 로그 파일을 기반으로 서버 재시작/재구성 시 복구가능하다.

기본 값은 다음과 같이 꺼져있다.

 

기록되는 주기는 아래 설정에 따라 다르다.

- appendfsync always
모든 쓰기 명령마다 즉시 디스크에 동기화
가장 안전하지만 성능 저하가 심함
초당 수백 번의 fsync 발생 가능
Redis 성능이 극단적으로 저하될 수 있음

- appendfsync everysec (기본값)
1초마다 디스크에 동기화
최대 1초 사이의 데이터 유실 가능
성능과 안정성의 적절한 균형
대부분의 경우 권장되는 설정

- appendfsync no
OS가 판단하여 디스크에 동기화
보통 30초 정도마다 동기화
가장 빠른 성능
장애 발생 시 상당한 데이터 유실 가능

AOF는 같은 데이터셋이더라도 모든 쓰기 명령어들을 저장하기 때문에 RDB 파일에 비해 용량이 크고

재시작시 모든 명령어를 다시 실행해야 하므로 RDB 대비 느리다.

AOF 파일 사이즈가 너무 커지면 OS 파일 사이즈 제한에 걸러서 기록이 중단될 수도 있다고 한다.

따라서 파일 크기를 주기적으로 압축하여 재작성되는 과정을 거쳐야한다.

다음과 같이 파일의 크기를 기준으로 압축되는 시점을 지정할 수 있다.

// 다시 쓰기 시점 설정
auto-aof-rewrite-percentage 100

 

No persistence

영속성을 사용하지 않는 것으로 순수하게 캐시로만 사용.

 

영속화 선택하기

RDB, AOF 두 방식들마다 장단점이 있지만 간단하게 언제 어떤 방식을 사용할지에 대해 설명하면 다음과 같다.

  • RDB + AOF : 성능은 포기하고, 하드 디스크 DB와 같은 제일 강력한 내구성이 필요할 경우. 
  • RDB만 사용 : 장애 발생 시, 백업은 필요하지만 어느 정도의 데이터 손실이 발생해도 괜찮은 경우
  • AOF만 사용 : 장애 발생 직전 까지 모든 데이터가 보장되어야 할 경우

Redis 공식 문서에서는 AOF를 기본으로 하고, RDB를 Option으로 하는 것을 권장하고 있다.

(Copy On Wirte로 인해 둘 다 사용하지 않는게 가장 좋은 퍼포먼스를 내긴한다. -> 이유는 'Redis의 Copy On Wirte' 목차에서 설명)

AOF를 효율적으로 사용하기 위해선 시간 설정은 everysec로 하고, rewrite 설정으로 파일 사이즈를 줄여줘야 한다.

유의사항만 지킨다면 AOF를 사용해도 성능에 거의 영향을 미치지 않는다고 한다.

좀 더 깊은 내용들은 아래 문서에서 확인할 수 있다.

http://redisgate.kr/redis/configuration/persistence.php

 

설정 확인 및 변경 Redis가 설치된 폴더의 etc/redis.conf 파일을 확인하고 변경 또는, redis-cli로 명령어를 이용해 확인하고 변경할 수 있다.

redis-cli

# RDB 설정 확인
CONFIG GET save

# RDB 설정 수정 (save보단 bgsave 명령어로 background 자식 프로세스를 통해 백업 작업을 수행하도록 권장)
CONFIG SET bgsave "900 1"  # 900초(15분)마다 1개 이상의 키가 변경되었을 때 RDB 파일 재작성

# AOF 설정 확인
CONFIG GET appendonly

# AOF 설정 수정
CONFIG SET appendonly yes  # AOF 활성화

 

Single Thread 방식

Redis는 싱글 쓰레드이기 때문에, 1번에 1개의 명령어만 실행할 수 있다.  (멀티 스레드를 지원하는 맴캐쉬드와 자주 비교된다.)

하지만 멀티 스레드를 사용하면서 발생하는 컨텍스트 스위칭과 메모리 사용을 줄일 수 있어 빠르게 응답할 수 있고 Atomic(원자성)을 보장할 수 있다.

 

Pub / Sub 

Publish / Subscribe 란 특정한 주제(topic)에 대하여 해당 topic을 구독한 모두에게 메시지를 발행하는 통신 방법으로 

채널을 구독한 수신자(클라이언트) 모두에게 메세지를 전송 하는것을 의미한다.

하나의 Client가 메세지를 Publish하면, 이 Topic에 연결되어 있는 다수의 클라이언트가 메세지를 받을 수 있는 구조이다.

Pub - Channel - Sub 구조

publisher가 Channel에 메시지를 발행하면 해당 Channel을 구독하는 Subscriber들에 메세지를 전송한다.

 

장점
  • 비동기 통신 - 발행자와 구독자는 서로를 알 필요가 없다.
  • 하나의 메세지를 여러 구독자에게 동시 전달 가능
  • 모든 데이터를 메모리에서 처리해 빠른 처리 및 실시간 처리 가능

 

단점
  • 한번 발행된 메시지는 저장되지 않는다.
  • Subscriber(구독자)가 없으면 메세지는 유실된다.
  • At Most Once 전달 - 수신을 확인하지 않기 때문에 메세지 전달이 보장되지 않는다.

 

활용

이 기능은 Redis Pub / Sub사용의 목적에 맞다면 실시간 채팅, 실시간 알림, SNS 피드 등에 사용할 수 있다.

예를 들어 실시간 채팅의 경우에는 지난 채팅 기록도 볼 수 있어야한다.

하지만 Pub / Sub는 메세지를 저장하지 않기 때문에 Redis의 List 자료형으로 제한된 메세지를 저장하고 조회하도록 구현할 수도 있다.

만약 메세지의 영속성이 보장되어야 한다면 MongoDB와 같이 사용할 수도 있을 것이다.

Redis를 사용한 채팅을 구현한다면 '읽지 않은 메시지 수'도 효율적으로 조회할 수 있도록 구현할 수 있을 것 같다.


Redis를 왜 싱글 스레드로 만들었을까?

왜 멀티 스레드가 아닌 싱글 스레드인지 궁금증이 생겼다.

 

Redis 공식 문서에서는 아래와 같이 설명하고 있엇다.

https://redis.io/docs/latest/operate/oss_and_stack/management/optimization/latency/

 

 

 

위 글에선 I/O가 주된 병목이라 설명하는데 확실히 메모리에 대한 접근은 빠르지만

이 데이터를 전송하기 위한 통신 시간이 훨씬 길어 I/O가 주된 병목을 차지한다고 생각된다.

 

싱글 스레드를 사용한 이유를 정리해보면

I/O 스레드가 주된 병목이며, 메모리 작업이 대부분이고 내부적으로 최적화된 String, List, Sorted Set 등의 자료구조를 사용해

복잡한 연산이 없이 빠르게 처리가능해 CPU 사용량이 낮아 싱글 스레드로 충분히 커버가 가능하기 때문이라고 정리되는 것 같다.

 

CPU 작업이 많을 경우에는 스레드 수가 많으면 일꾼이 많아진 것이기 때문에 큰 효율을 볼 수 있지만

I/O 작업이 많을 때는 가용 가능한 스레드만 작업을 하고 그 외에는 대기 큐에서 대기를 하기 때문에

스레드가 많아져도 의미가 없기 때문이다.


Redis의 Multiplexing(멀티 플렉싱)과 Event-Loop (이벤트 루프) 방식

그렇다면 redis는 싱글 스레드임에도 어떻게 여러 요청을 빠르게 처리해 응답할 수 있을까?

 

위 공식 문서 사진에서도 I/O 작업을 처리하는데에는 백그라운드로 여러 개의 쓰레드를 사용해 처리한다고 한다.

즉, redis가 싱글 스레드라는 것은 클라이언트의 요청인 redis의 명령어를 처리하는 것을 의미한다고 볼 수 있다.

 

어떻게 처리하는지 처리 방식을 그림으로 보자.

  1. 클라이언트가 연결을 요청하면 클라이언트의 Socket과 redis의 Socket이 연결된다.
  2. I/O Multiplex는 멀티 스레드를 사용하고 비동기로 여러 클라이언트의 요청을 동시에 받는다.
  3. Event Loop는 비동기로 클라이언트 연결 이벤트를 감지하고 Task Queue 처리 작업을 전달한다.
  4. Task Queue는 전달받은 이벤트들을 순서대로 Queue에 저장한다.
  5. Event Dispatcher는 Task Queue에 저장된 작업을 가져와 적절한 이벤트 처리기로 전달한다.
  6. Event Processors는 실제 데이터를 처리하고 결과를 반환한다.

즉, 병목의 원인인 I/O는 멀티 스레드를 사용하고 비동기로 처리해 병목을 줄이고 데이터 처리는 단일 스레드로 동기 처리하여 원자성을 보장한다.

비동기, 동기 그리고 멀티 스레드, 단일 스레드의 장점들을 모두 활용한 구조이다.


Redis 아키텍처 종류

 

Standalone (단일 구성)

Redis 서버 1대로 구성한다.

이를 Source 노드라고 하며 서버 다운 시 AOF 또는 RDB 파일을 이용해서 재시작한다.

 

Replication (복제)

레디스 서버 2대(Source - Replica)로 구성한다.

(한 Source에 여러 Replica를 구성할 수 있고, Replica에 또 다른 Replica를 둘 수도 있다.)

Replica는 Source의 데이터를 실시간으로 전달받아 보관한다.

이 구조 뿐 아니라 Redis의 모든 구조에서의 복제는 비동기식으로 동작한다.

때문에 Source에서 Replica에 데이터가 잘 전달됐는지 확인할 수 없어 정합성을 보장하진 못한다.

HA(High Availability) 기능이 없어 Source 장애 시 Replica가 자동으로 승격되진 않기 때문에

수동으로 Replica 노드에 접속해 복제를 끊고, 애플리케이션에서도 연결 설정을 변경해 배포해줘야 한다.

 

복제에 대해 더 자세한 내용과 설정 방법은 아래 문서를 통해 확인할 수 있다.

http://redisgate.kr/redis/configuration/replication.php

 

Sentinel (감시자)

Source / Replica 구성에 센티널을 추가해 센티널 자신을 제외한 각 서버들을 감시하는 구조이다.

(센티널은 데이터를 처리하지 않고 감시만 한다.)

HA 기능을 제공하여 감시 중 Source가 다운되면 센티널끼리 다수결로 선정된 Replica를 Source로 승격시킨다. (failover)

또한 애플리케이션에서 연결 정보를 변경할 필요 없이 Sentinel이 변경된 Source 정보로 연결시켜준다.

Sentinel 구조를 사용하기 위해선 Sentinel 프로세스를 추가로 띄워야하는데

장애 판단을 다수결로 결정할 수 있도록 Sentinel은 항상 최소 3대이상의 홀수로 구성되어야 한다.

 

센티넬에 대해 더 자세한 내용과 설정 방법은 아래 문서를 통해 확인할 수 있다.

http://redisgate.kr/redis/sentinel/sentinel.php

 

Cluster (클러스터)

데이터가 여러 Source에서 자동으로 분할되어 저장되는 샤딩 기능을 제공한다.

그리고 이 데이터를 나누는 방식은 Hash를 사용하고 전체 16384개의 슬롯을 노드들이 나누어 관리하는 Hash Slot 방식을 사용한다.

각 노드는 16384개의 슬롯 중 일부 범위들을 담당하고 키를 받으면 CRC16 알고리즘으로 해싱(key % 16384)해 슬롯 번호를 만들고

해당 슬롯 번호를 갖고 있는 노드에게 데이터를 균등하게 분산한다.

Slot 단위로 데이터를 관리하기 때문에 특정 키를 어디에 전송할지를 명시적으로 전송가능하다.

하지만 각 노드가 자신 외에 클러스터 운영에 필요한 추가 정보를 모두 메모리에 유지해야 하므로 메모리 사용량이 더 많다는 것이 단점이다.

 

이 구성에서도 모든 노드가 PING/PONG 메시지 교환으로 서로를 감시하고 있다가

Source 장애 발생 시 자동으로 Replica를 Source로 승격시킨다. (failover)

최소 3대 이상의 Source가 필요하며 Source 하나당 1개 이상의 Replica를 가져야 한다.

 

클러스터에 대해 더 자세한 내용과 설정 방법은 아래 문서를 통해 확인할 수 있다.

http://redisgate.kr/redis/cluster/cluster.php

 

아키텍처 선택 플로우 차트

 


Redis의 Copy On Write (COW)

만약 16GB RAM을 사용하고 있고 사용 중인 메모리는 12 GB일 경우 안전할까?

결론부터 말하면 추가 메모리를 고려해야할 수 있다.

이유는 바로 Redis의 Copy On Write 때문이다.

이 Copy On Write가 언제 발생하는지? 어떻게 대응할 수 있을지 알아보자.

 

Copy On Write란?

Redis는 데이터를 복사할 때 fork()기능을 사용한다.

문제는 여기서 시작된다.

좌 : 자식 프로세스를 생성(fork)한 직후 프로세스와 메모리 모습 우 : 부모 프로세스가 Page C를 수정한 후 프로세스와 메모리 모습

출처 : http://redisgate.kr/redis/configuration/copy-on-write.php

 

좌측의 사진을보면 fork한 직후 모습으로 자식 프로세스가 생성되었다.

이때 실제로 메모리를 복사하지 않고 같은 물리 메모리 Page A, B, C를 공유한다.

(마치 한 책을 두 사람이 같이 보는 것과 같은 형태)

지금까지는 복사가 발생하지 않아 메모리를 효율적으로 사용하고 있다.

 

우측 사진을 보면 데이터 수정이 일어난 이후 모습이다.

부모 프로세스가 Page C의 데이터를 수정하려할 때 원본을 수정하지 않고 복사본(copy)을 만들어 수정(write)한다.

이 때 바로 Copy On Write가 발생하고 평소보다 많은 메모리가 필요하게 된다.

보통 이 상황에서 메모리를 두 배로 사용하는 경우가 발생하기도 한다고 한다.

만약 메모리 부족으로 이어진다면 Redis가 죽어버릴 수 있다.

 

Copy On Write는 언제 발생할까?

그렇다면 COW는 언제 발생할까?

결론부터 말하면 주로 영속화 작업(RDB 파일을 생성할 때와 AOF 파일을 재작성할 때)에서 이 문제가 발생한다.

Replication시에도 Source / Replica가 연결되면 RDB 파일이 생성되어 COW가 발생한다.

 

Copy On Write 대응법

그럼 이 문제를 대응하기 위한 방법들을 알아보자.

가장 좋은 방법은 영속화를 적용하지 않는 방법이다.

하지만 백업이 필요하다면 쓰기 작업이 적은 시간대를 선택하는 방법으로 어느정도 메모리 문제를 해결할 수 있을 것이다.

메모리도 사용량의 2배 이상의 메모리 여유 공간 확보하는 것이 가용성을 높일 수 있다.

또한 auto-aof-rewrite-percentage 파라미터를 0(disable)으로 설정해 AOF 파일을 재작성하지 않도록 하는 방법도 있다.

이때 AOF 파일이 계속 커지는 것을 방지 하기 위해, 서버 부하가 적을 때 BGREWRITEAOF 명령을 수행해 수동으로 재작성하도록 해줘야한다.


레디스 활용 예시

 

I/O가 빈번히 발생하는 데이터 캐싱

우리가 처리하는 데이터 중 찜 수, 판매상품 수 같은 카운트 쿼리 데이터가 가장 I/O가 빈번히 발생한다.

특히 내 프로젝트에서는 사용자별로 사이드바에 찜 수, 장바구니 수들을 카운트 쿼리로 반환된 데이터 수를 view에 뿌려주는데,

사이드바는 여러 페이지에서 노출되어 페이지 이동마다 카운트 쿼리를 호출하게 된다.

그렇기 때문에 매번 카운트 쿼리를 호출하지 않고 해당 데이터를 캐싱 처리하여 카운트 수가 변경되지 않을 경우에

Redis를 통해 해당 값을 반환하도록 하여 빠른 속도로 응답하고, RDB의 부담을 줄도록 할 수 있다.

 

API 제한

사용자 별로 API 사용량 제한이 필요하다면 Redis를 사용할 수 있다.

만약 사용자별 API 제한이 월 1,000건이라 가정해보자.

RDB를 사용할 경우 동시성 문제를 해결하기 위해 Lock을 사용할 수 있지만 Dead Lock 발생 위험이 있다.

수 많은 요청 시 디스크 I/O로 응답 지연이 발생할 수 있는데

이 문제를 해결하기 위해 인덱싱을 걸 수 있겠지만 인덱스도 추가적인 공간이 필요해 신중히 고려할 필요가 있다.

또한 주기적으로 30일이 지난 데이터들을 삭제해주는 관리가 필요하다.

하지만 Redis를 사용한다면 어떨까?

요청마다 TTL을 30일로 설정하여 count를 1씩 올리면서 제한을 체크할 수 있을 것이다.

또한 싱글 스레드를 사용하는 Redis의 장점이 발휘될 수 있다.

Redis는 Atomic(원자성)을 보장한다 했다.

따라서 분산된 서버들에서 API 호출 횟수를 올리더라도 race condition이 발생하지 않아 정확한 카운팅이 가능하고

TTL마다 요청을 삭제하면서 호출 횟수 관리가 용이하다.

 

Ranking 구현

RDB를 사용해 구현한다면 복잡한 쿼리나 인덱스등 많은 고려 사항이 있을 수 있다.

하지만 Redis의 Sorted Set을 사용한다면 랭킹 시스템을 간단히 구현할 수 있다.

점수 업데이트 시 자동으로 순위가 정렬되고 대규모 데이터도 빠르게 처리 가능하다.

또한 싱글 스레드로 Atomic하기 때문에 Lock 없이 여러 업데이트가 동시에 발생해도 안전하다.

 

세션, 인증 토큰 관리

서버가 한대일 경우에는 해당 서버에 인증 정보를 저장해서 사용할 수 있겠지,

만약 scale-out이 적용되어 서버가 N대가 있을 경우에는 서버마다 가지고 있다면

인증 저장소에서 세션을 공유하지 않기 때문에 세션 불일치 문제가 발생한다.

보통은 분산 서버들이 공유하는 RDB가 있겠지만 Redis를 사용하면

key-value로 빠르게 찾아 인증 정보들이 많을 경우 빠르게 인증을 처리할 수 있고 TTL 설정으로 만료 시간을 설정할 수도 있다.


효율적인 메모리 사용 팁

Redis는 In-Momory DB이기 때문에 메모리 관리가 아주 중요하다.

효율적으로 메모리를 관리할 수 있는 방법들에 대해 알아보자. 

 

O(N) 시간복잡도 명령어 사용 금지

Redis는 Single Thread 서버이기 때문에 시간 복잡도를 고려해야 한다.

싱글 쓰레드이기 때문에 동시에 단 1개의 명령어만 처리할 수 있다.

그렇기 때문에, 하나의 명령어를 처리하는 속도가 오래 걸릴 경우 나머지 명령어들은 대기 상태로 전환되는데

데이터의 수가 많은 경우 큰 속도 저하를 야기할 수 있다.

특히, O(N)의 시간복잡도를 갖는 모든 키를 순회하는 명령어

대표적 명령어들(keys, flushAll, flushDB, delete collections, get all collections)은 운영환경에서 사용을 금지한다.

(redis.conf에서 특정 명령어들을 disable 할 수도 있다.)

 

컬렉션 사용시 주의 사항

  • 하나의 컬렉션에 너무 많은 많은 데이터가 들어가면 조회시 성능에 큰 영향을 미쳐 최대 1만개 이하로 유지해 잘게 쪼개는 것이 좋다.
  • 컬렉션에 TTL을 걸게 되면 개별 데이터에 걸리는 것이 아니라 컬렉션 자체에 걸려 TTL 만료시 모든 데이터가 삭제되므로 주의해야한다.
  • 키에 많은 데이터가 있을 때 DEL 명령으로 삭제하면 그 키를 지우는 동안 아무런 동작을 할 수가 없어 백그라운드로 삭제하는 UNLINK 명령을 사용하는것이 좋다.

 

적절한 캐싱 전략 선택

메모리를 효율적으로 사용하기 위해서는 어떤 캐싱 전략을 사용하고 적절한 TTL을 설정하는 것에 따라 달라진다.

아래 포스팅을 통해 캐싱 설계 전략들을 알아보고 적절한 캐싱 전략과 TTL을 알아보자.

2024.11.10 - [◼ CS 기초 지식/[데이터베이스]] - [Redis] 캐싱(caching) 설계 전략에 대해 알아보자 (+TTL 설정 주의점)


 

MAXMEMORY 설정

메모리가 가득 찼을 경우에는 MAXMEMORY-POLICY 정책에 의해 데이터가 삭제 된다.

옵션은 다음과 같은 옵션들이 있다.

  • noeviction(default) : 메모리가 가득차면 더 이상 Redis에 새로운 키를 저장하지 않는다. (즉, 메모리가 가득차면 새로운 데이터 저장이 불가능.)
  • volatile : 가장 최근에 사용하지 않았던 키부터 삭제하는 정책. expire 설정에 있는 키값만 삭제. (만약 메모리에 expire 설정이 없는 키들만 남아있다면 위와 같은 문제가 발생할 수 있다.)
  • allkeys(권장) : allkeys-lru는 모든 키에 대해 LRU방식으로 데이터를 삭제한다. allkeys-lfu는 사용 빈도수가 가장 적은 데이터부터 삭제해준다. (MAXMEMORY로 인해 장애가 발생할 가능성 X)
하지만 Redis는 정확하게 자기가 사용하는 메모리를 추측할 수 없어 이 설정이 크게 유효하진 않다고 한다.
이 설정으로 방지를 한다하더라도 메모리 관리를 잘 할 수 있도록 할 필요가 있다.

 

메모리를 줄이기 위한 자료구조 - Ziplist

기본적으로 아래의 Collection들은 내부적으로 다음과 같은 자료구조를 사용한다.

Collection 자료구조
Hash HashTable을 하나 더 사용
Sorted Set Skiplist, HashTable
Set HashTable

그런데 이 자료구조들은 많은 메모리를 사용한다.

따라서 메모리 사용을 줄이고 싶으면 Ziplist를 이용할 수 있다. (속도가 중요하다면 사용 X)

Ziplist를 이용하게 되면 속도는 느려지지만 메모리는  20 ~ 30% 훨씬 적게 사용하게 된다고 한다.

Ziplist를 사용하기 위해서는 redis 설정 파일에서 설정해주어야 한다. (방법은 검색 추천)

 

RSS 값 모니터링

모니터링을 할 때 Used memory 보다는 메모리 RSS 값을 보는 게 중요하다 한다.

Used memory는 논리적으로 레디스에 얼만큼의 데이터가 저장되어 있는지를 나타내고

RSS는 실제로 OS가 레디스에 할당한 메모리 크기를 보여주기 때문이다.

실제 저장된 데이터는 적은데 RSS 값은 큰 상황이 발생할 수 있고 이 차이가 클 때 단편화(fragmentation)가 크다고 말한다.

 

메모리 단편화

메모리의 공간은 작은 조각으로 나뉘어져 있다.

사용가능한 메모리가 충분히 존재하지만 사용이 불가능한 상태를 메모리 단편화가 발생했다고 한다. 

 

주로 삭제되는 키가 많을 때 단편화가 증가하는데 이유는 다음과 같다.

-- 메모리 상태 변화 예시
초기: [A:100KB][B:100KB][C:100KB]
B 삭제 후: [A:100KB][빈공간:100KB][C:100KB]
D(150KB) 추가 시: [A:100KB][빈공간:100KB][C:100KB][D:150KB]

Redis가 메모리에 할당할 때 미리 정의된 크기의 Block 단위로 할당하고 삭제 후 새 데이터가 추가될 때

기존 블록 크기보다 큰 데이터라면 중간 중간 빈 블록이 남아 단편화가 발생한다.

그리고 실제로는 1GB의 메모리를 사용해야하지만 2GB의 메모리를 사용해버리는 일이 발생할 수도 있다.

따라서 주기적으로 RSS를 모니터링해 조치를 취할 수 있어야한다.

(redis에서 단편화를 모니터링할 수 있는 activcedefrag 옵션도 있다.)


 

Redis vs Memcached

key-value 방식의 인메모리 DB로 Redis와 Memcached를 가장 많이 비교한다.

  Redis Memcached
스레드 싱글 스레드 멀티 스레드
데이터 타입 list, string, hashes, sorted sets, bitmaps 등 다양한 자료구조 지원 string과 integer만 지원
데이터 영속화 RDB 또는 AOF를 활성화하여 영속화 가능 영속화 기능을 제공하지 않음
처리 속도  Memcached보다는 느리지만 큰 차이는 없다. 디스크를 거치지 않아 redis보다 더 빠르다.
영속성(Persistence) 지원 지원 안함

Redis 장단점

 

장점
  • 분산 환경을 지원해 여러 서버에서 동일한 캐시 데이터에 접근할 수 있다.
  • RDB/AOF를 지원해 서버가 재시작되어도 캐시 데이터 유지 가능하다. (단, 영속화에 대해선 주의가 필요)
  • 단일 스레드로 동작해 여러 프로세스/서버에서 안전하게 데이터 접근/처리 가능하다. (Atomic 보장)
  • 다양한 자료구조를 지원한다.
  • 레플리케이션, 센티넬, 클러스터 아키텍처를 지원해 가용성을 확보할 수 있다.
  • 메모리 기반 저장소로 접근 속도가 빠르다. (평균 작업속도가 1ms보다 작다고 함)

 

단점
  • 메모리에 데이터를 저장하므로 기본적으로 데이터가 휘발된다는 특징을 갖는다. (RDB/AOF로 커버 가능)
  • 메모리의 공간이 필요해 저장되는 서버의 메모리 크기가 제한요소가 된다.
  • 메모리를 사용하기 때문에 메모리 단편화에 주의해야한다. (메모리를 효율적으로 관리 필요)
  • 단일 스레드로 동작해 한 번에 많은 요청을 처리하기 힘들다. (O(n) 명령어 주의, cache warming 필요)
  • SQL 같은 복잡한 쿼리 기능을 지원하지 않는다. (별도의 로직 구현 필요)
  • 트랜잭션이 ACID 특성을 완벽하게 보장하지 않는다.

출처