
Redis 소개
레디스(Redis)는 메모리 기반의 데이터 저장소이다. 키-밸류(key-value) 데이터 구조에 기반한 다양한 형태의 자료 구조를 제공하며,
데이터들을 저장할 수 있는 저장소이다. 최신 버전의 레디스는 PUB/SUB 형태의 기능을 제공하여 메세지를 전달할 수 있다. 즉, 데이터 저장 뿐만 아니라 다양한 목적으로 사용할 수 있다. 레디스는 메모리에 데이터를 저장하기 때문에 저장 공간에 제약이 있어, 주로 보조 데이터 저장소로 사용한다. 이를 극복하기 위한 레디스 클러스터 기능도 제공하고 있어 저장 공간을 확장할 수 있다. 또한, 저장된 데이터를 영구적으로 디스크에 저장할 수 있는 백업 기능을 제공하므로 애플리케이션의 주 저장소로도 사용할 수 있다. 또한, 메모리에 데이터를 저장하기 때문에 빠른 처리 속도를 장점으로 갖는다. 레디스 내부에서 명령어를 처리하는 부분은 싱글 스레드 아키텍처로 구현되어 있다. 멀티 스레드 아키텍처보다 구조가 단단하게 설계되어 여러 장점이 있다. - "스프링 부트로 개발하는 MSA 컴포넌트 - 김병부"
장점: 빠른 처리 속도
단점: 저장 공간 제약
Redis 데이터 백업 기능

Redis
는 메모리에 데이터를 관리한다. 따라서, 매우 빠른 속도로 데이터를 저장 및 조회할 수 있다.
하지만, 우리가 알고 있듯 메모리 특성상 저장된 데이터는 휘발될 가능성이 있다.
해당 문제를 해결하고자 Redis
는 관리하고 있는 데이터에 관한 영속성을 제공한다.
즉, 메모리에 있는 데이터를 디스크에 백업하는 것이 가능하다.
이때 사용되는 디스크 백업 방식으로는 2가지가 존재한다.
- RDB
- AOF
RDB(Redis to Database)
메모리에 있는 데이터 전체에서 스냅샷을 작성하고 이를 디스크로 저장하는 방식이다.
- 특정 시간마다 여러 개의 스냅샷을 생성한다.
- 이때, 데이터를 복원해야 한다면 스냅샷 파일을 그대로 로밍만 하면 된다.
- 하지만, 스냅샷 이후 변경된 데이터는 복구할 수 없다.
- 즉, 데이터가 유실될 수 있다.
AOF(Append Only File)
데이터가 변경되는 이벤트가 발생하면 이를 모두 로그에 저장하는 방식이다.
- 데이터를 생성/수정/삭제하는 이벤트를 초 단위로 취합하고 로그 파일에 작성한다.
- 모든 데이터의 변경 기록들을 보관하고 있으므로, 최신 데이터 정보도 백업이 가능하다.
- 따라서, RDB 방식에 비해 데이터 유실량이 적다.(물론, 초 단위 데이터는 유실이 가능하다)
- RDB 방식보다 로딩 속도가 느리며 파일 크기가 큰 것이 단점이다.
두 방식에 관해 정리하자면,
일부 데이터 손실에 영향을 받지 않는 경우(캐시로만 사용할 때) : RDB
장애 상황 직전까지의 모든 데이터가 보장되어야 할 경우 : AOF
강력한 내구성이 필요한 경우, RDB + AOF
레디스는 일반적으로 AOF와 RDB를 동시에 사용하여 데이터를 백업한다.
- 예를 들어, 매일 7시보다 RDB 스냅샷을 생성하며, RDB 생성 이후에 변경되는 데이터는 AOF로 백업.
Reids 백업 방식 설정
RDB
- 자동: redis.conf 파일 -> SAVE 옵션 설정(시간 기준)
- 수동:
redis-cli
에서BGSAVE
커맨드를 이용하여 수동으로 RDB 파일 저장SAVE
커맨드는 절대 사용 X (레디스는 싱글 스레드, 저장하는 동안 다른 작업 수행 불가)
AOF
- 자동: redis.conf 파일 ->
auto-aof-rewrite-percentage
옵션 설정 (크기 기준) - 수동: redis-cli에서
BGREWRITEAOF
커맨드를 이용하여 수동으로 AOF 파일 재작성
Redis Single Thread

레디스의 코어스레드는 싱글스레드이다.
레디스는 사용자들이 실행한 명령어들을 이벤트 루프(event loop) 방식으로 처리한다.
즉, 클라이언트가 실행한 명령어들을 Event Queue
에 적재하여 싱글 스레드로 하나씩 처리한다.
메모리를 사용하기 때문에 싱글 스레드로 데이터를 빠르게 처리할 수 있다.
장점
- 멀티 스레드 환경이 아니므로 Context Switch가 발생하지 않는다. -> 효율적인 시스템 리소스 사용
- 1번의 이유로 DeadLock이 발생하지 않는다.
단점
- 싱글 스레드 이므로 전체 데이터 스캔과 같은 오버헤드가 큰 명령어를 처리하는 동안 다른 명령어를 처리할 수 없다.
- 이 때, 다른 명령어들은 이벤트 큐에 저장되어 있는 시간이 길어진다. 즉, 응답 속도 저하.
Redis 사용 용도
지금까지 설명한 특징들 덕분에 레디스를 다음 목적으로 사용할 수 있다.
- 주 데이터 저장소: AOF, RDB 백업 기능과 레디스 아키텍처를 사용하여 주 저장소로 데이터를 저장할 수 있다.
하지만, 메모리 특성상 용량이 큰 데이터 저장소로는 적절하지 않다. - 데이터 캐시: 인메모리 데이터 저장소이므로 주 저장소의 데이터를 캐시하여 빠르게 데이터를 읽을 수 있다.
캐시된 데이터는 한곳에 저장되는 중앙 집중형 구조로 구성한다. -> 데이터 일관성 유지 - 분산 락(distributed lock): 분산 환경에서 여러 시스템이 동시에 데이터를 처리할 때는 특정 공유 자원의 사용 여부를 검증하여
데드락을 방지할 필요가 있다. 이때 레디스를 분산 락으로 사용할 수 있다. - 순위 계산: 레디스에서 제공하는 ZSet(Sorted Set) 자료 구조를 이용하여 순위 계산 용도로 사용하기도 한다.
ZSet은 정렬 기능이 포함된 Set 자료구조 이므로 쉽고 빠르게 순위를 계산할 수 있다.
Redis 아키텍처
레디스는 3가지 아키텍처로 나누어서 볼 수 있다.

1. Replication 아키텍처: Master와 Replica로 구성
- 단순한 복제 연결시 사용한다. (replicaof 커맨드 이용)
- 비동기식 복제 (복제가 잘 됐는지 확인하지 않는다)
- HA(고가용성) 기능이 없으므로 장애 상황 시 수동으로 복구한다.
- 장애 상황시 스프링에서 새로운
Redis
서버의 연결 정보를 변경 해주어야 한다.
- 장애 상황시 스프링에서 새로운
2. Sentinel 아키텍처: Sentinel, Master, Replica로 구성, 자동 Failover가 가능한 HA(고가용성) 구성
레디스 Sentinel
은 레디스 서버들(Master, Replica)을 관리한다. Sentinel
은 주기적으로 레디스 서버들을
모니터링한다. 마스터 서버가 서비스할 수 없는 상태가 되면 다른 Replica
를 마스터 서버로 변경한다.
Sentinel
노드가 Redis 마스터와 레플리카 노드를 감시- Master가 비정상 상태일 때 자동으로 Failover(자동 복구)
- 애플리케이션은
Sentinel
과 연결하기 때문에, 장애 상황 발생 시 연결 정보를 변경할 필요가 없다. Sentinel
노드도 장애 상황이 발생할 수 있기 때문에 반드시 3대 이상의 홀수로 존재해야 한다.- 과반수 이상의
Sentinel
이 동의(Quorum based)가 있어야 Failover 진행 - 많은 리소스가 필요하므로
Sentinel
과 마스터 혹은 레플리카를 같은 서버에 올려 사용하기도 한다.
- 과반수 이상의
3. Cluster 아키텍처: 레디스 3.0 버전 이후부터 제공한다. 클러스터에 포함된 노드들이 서로 통신하는 구조이다.
레디스 클러스터는 클러스터에 포함된 노드들이 서로 통신하며 HA를 유지한다. 거기에 샤딩 기능까지 기본 기능으로 사용할 수 있다. Sentinel
과 동일하게 마스터와 레플리카는 짝을 이루어 데이터를 복제한다.
클러스터 내부의 모든 노드는 모두 서로 연결되어 있는 Mesh 구조로 되어 있으며,
가십 프로토콜(gossip protocol)을 사용하여 서로 모니터링 한다.
Sentinel
과 동일하게 Master와 Replica는 짝을 이루어 데이터를 복제.- 클러스터를 구성하기 위해 세 개의 마스터 노드는 반드시 필요하다.
- 레플리카 노드의 개수는 0개 혹은 그 이상으로 설정 가능
- 고가용성을 위해 반드시 한 개 이상의 레플리카를 설정하는 것이 좋다.
- 데이터를 마스터 노드들에 샤딩
- 해시 함수를 사용하여 데이터 분배, 데이터의 키 값을 해시 함수로 넘긴 후 리턴 값을 사용하여
어떤 노드에 저장할지 결정한다. * 리턴 값은 항상 0 ~ 16383 값을 리턴한다. - 클러스터에 노드를 추가하거나 제거할 경우 레디스 명령어를 사용하여 해시 함수 값 범위를 조정할 수 있다.
- 해시 함수를 사용하여 데이터 분배, 데이터의 키 값을 해시 함수로 넘긴 후 리턴 값을 사용하여
- 리밸런싱(Rebalancing) & 리샤딩(Re-shard)
- 조정된 범위에 포함되는 레디스 데이터들은 자동으로 재분배되어 설정된 위치로 이동된다.
- 애플리케이션은 레디스 클러스터 노드 중 하나라도 연결되면 클러스터의 전체 상태 정보를 확인 가능
- 장애 상황 발생 또는 노드 확장(Scale out)시 애플리케이션의 Redis 서버 연결 정보 변경 필요 X

Redis Replication 추가 설명
Redis Replication
는 하나의 Redis 서버(Master)에서 데이터를 다른 하나 이상의 Redis 서버(Slave)로 복제하는 기능이다.
이 기능은 주로 읽기 성능을 향상시키기 위해 사용된다.
Replication 동작 방식
- 마스터-슬레이브 구조: 한 개의 마스터 서버가 있고, 여러 개의 Replica(Slave) 서버가 있을 수 있다.
마스터 서버는 쓰기 작업을 담당하고, 슬레이브 서버는 읽기 작업을 담당하여 분산시킨다. - 데이터 복제: 마스터 서버에서 데이터를 업데이트하면, 그 변경 사항이 슬레이브 서버로 비동기적으로 복제된다.
- 일관성 보장: Redis는 비동기 복제를 사용하므로, 마스터와 슬레이브 간에 데이터 일관성에 약간의 지연이 있을 수 있다.
즉, 슬레이브 서버가 최신 데이터가 아닐 수 있다. 하지만, 이는 대부분의 캐시 사용 사례에서 큰 문제가 되지 않는다.
Redis 자료구조

레디스는 다양한 형태의 자료구조를 제공한다. 기본적으로 키-밸류(Key-Value) 형태의 구조를 띄며,
벨류가 사용하는 자료구조에 따라 여러 기능을 사용할 수 있다.
즉, 어떤 형태의 자료구조를 사용하더라도 키는 반드시 필요하다.
- String
- 문자열 데이터를 저장 및 조회할 수 있는 기본 자료구조
- 단순 증감 연산으로 사용 가능 (INCR / INCRBY / INCRBYFLOAT / HINCRBY / HINCRBYFLOAT / ZINCRBY)
- Bitmap
- 비트 연산을 사용할 수 있는 자료구조
- List
- 리스트 데이터
- 위의 그림과 같이 리스트 아이템은 Linked List 형태로 서로 연결되어 있다.
- Hashes
- 해시 자료구조
- 해시 필드(Field)와 밸류(Value)로 구성된다.
- 해시 데이터는 레디스 키와 매핑되어 있으므로
- 해시 밸류를 생성 및 조회하려면 레디스 키와 필드를 동시에 사용해야 한다.
- Set
- 리스트와 비슷한 집합 데이터
- 중복을 허용하지 않는 자료구조이다.
- Sorted Set(ZSet)
- Set과 비슷한 집합 데이터, 정렬 기능을 제공한다.
- 중복을 허용하지 않으며, 이 때 데이터는 스코어와 함께 저장할 수 있다.
- Sorted Set은 스코어 값을 사용하여 정렬하고, 스코어 값이 중복되면 사전 순으로 정렬한다.
- HyperLogLogs
- 집합의 데이터 개수를 추정할 수 있는 알고리즘 이름이자 이를 사용할 수 있는 레디스 자료구조이다.
- 이 알고리즘은 비트 패턴을 분석하여 비교적 정확한 추정 값(오차 0.81%)을 계산할 수 있다고 한다.
- 예를 들어, 특정 상품의 조회수를 1만 239회라고 정확하게 계산할 때는 시스템 부하가 발생한다.
- 대신 추정 값을 계산하는데 최적화되어 1만 회 같은 근사 값을 조회할 수 있다.
- 중복된 값을 제거 가능하며, 저장 공간이 작아 카운트에 적합하다.
- Stream
- 레디스 5.0 버전부터 제공하는 기능, 이벤트성 로그를 처리할 수 있다.
- 일종의 메시지 서비스 기능이다.
- 스트림 키 이름과 값, 필드를 사용할 수 있는 자료구조 형태를 띈다.
Best Practice - Counting
- String
- 단순 증감 연산
- INCR / INCRBY / INCRBYFLOAT / HINCRBY / HINCRBYFLAOT / ZINCRBY

- Bits 사용
- 데이터 저장공간 절약
- 정수로된 데이터만 카운팅 가능
- 예를 들어, 오늘 서비스 접속 유저 수를 저장하고 싶을 때 비트연산자 사용이 가능하다.
- 오늘 날짜를 키값으로 두고 유저 아이디에 해당하는 비트자리를 1로 설정한다. 즉, 한 명의 유저는 1개의 비트를 의미.
접속한 유저가 천만 명일 경우 천만 자리 수의 비트가 필요 -> 1.2MB 차지 - 주의) 모든 데이터를 정수로 표현할 수 있어야 한다. 유저 ID와 같은 값이 0이상의 정수값일 때만 사용가능하다.
- 시퀀셜한 값이 없으면 사용 불가


- HyperLogLogs 사용
- 대용량 데이터를 카운팅 할 때 적절 (오차 0.81%)
- 모든 String 데이터 값을 유니크하게 구별 가능,
- Set과 비슷하지만, 저장되는 용량은 매우 작음(12KB 고정)
- 저장되는 데이터의 개수와 상관없이 모두 12KB로 고정되므로, 대량의 데이터를 처리할 때 좀 더 적절하다.
- 대신, 한번 저장한 값은 다시 불러올 수 없어 경우에 따라 데이터를 보호하기 위한 목적으로도 사용된다.
- 예를 들어, API 호출 한 유니크 한 IP 개수, 검색엔진에서 사용된 검색어가 몇개인지 확인할 때 사용 가능
- PFADD - 데이터 저장, PFCOUNT - 유니크한 값의 개수 조회, PFMERGE - 여러 데이터를 종합하여 카운트
- PFMERGE는 일별로 데이터를 저장했는데, 월별 데이터를 보고 싶은 경우 사용 가능


Best Practice - Messaging
Lists
- Blocking 기능을 이용해 Event Queue로 사용
- 아래처럼 ClientA가
BRPOP
명령어를 사용해 데이터를 꺼내오려 하는데 현재 리스트에 데이터가 없어 대기를 하는 상황에 - ClientB가
LPUSH
로 myqueue에 hi라는 값을 넣어주면 ClientA에서 바로 해당 값을 확인할 수 있다.

Lists(계속)
- 키가 있을 때만 데이터 저장 가능 - LPUSHX / RPUSHX
- 이미 캐싱되어 있는 피드에만 신규 트윗을 저장
- 인스타그램/페이스북/트위터와 같은 SNS에는 유저별로 타임라인이 존재하며,
- 타임라인에 팔로우 한 사람들의 데이터가 표시된다.
- 트위터에서는 각 유저의 타임라인에 보여줄 트윗을 캐싱하기 위해 Redis의 List를 활용한다.
- 이때, RPUSHX 커맨드를 사용한다.
- 이를 이용해서 트위터를 자주 이용하던 유저의 타임라인에만 새로운 데이터를 캐시해 놓을 수 있다.
- 자주 사용하지 않는 유저는 캐싱 키 자체가 존재하지 않기 때문에
그런 유저들을 위해 데이터를 미리 쌓아놓는 비효율적인 작업을 방지할 수 있다.

Streams
- 로그를 저장하기 가장 적절한 자료구조
- 실제 서버의 로그가 쌓이는 것처럼 모든 데이터는 append-only 방식으로 저장되고 중간에 데이터가 바뀌지 않는다.
- 시간 범위로 검색 / 신규 추가 데이터 수신 / 소비자별 다른 데이터 수신(소비자 그룹)

XADD (전체 key값) *(id 값) <key1> <value1> <key2> <value2>
- *의 경우 직접 id값을 지정할 수도 있지만, *을 사용하면 Redis가 알아서 저장해준다.
- 조회 방식은 다양한데 id값, 시간 범위 등 다양하게 조회할 수 있다.
- 그리고
tail -f
처럼 새롭게 들어온 데이터만 확인할 수도 있어 Kafka처럼 활용가능하다고 한다.
Redis 유효 기간
레디스에 저장되는 모든 데이터는 유효 기간을 설정할 수 있다.
유효 기간이 지난 데이터는 레디스가 해당 데이터를 메모리에서 삭제한다. => 메모리를 효율적으로 사용 가능
레디스는 다양한 방법으로 유효기간 설정이 가능하다.
EXPIRE
명령어를 사용해 이미 생성된 데이터에 유효 기간 설정이 가능하다.- 데이터를 생성할 때
EX
옵션을 사용하여 생성과 동시에 유효 기간 설정이 가능하다.- 이 옵션은 자료구조에 따라 지원하지 않는 경우도 있으니 확인해야 한다.
레디스는 메모리에 데이터를 저장하므로, 저장 공간이 한정적이다. 그래서 레디스에 데이터를 저장할 때는
데이터의 유효기간을 설정하는 것을 권장한다. 만약, 유효기간을 설정하지 않는다면 직접 데이터를 삭제할 때까지
영원히 유지된다.
스프링에서 Redis 자료구조의 사용 방법(Java)
Redis Configuration 설정
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory();
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory());
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new StringRedisSerializer());
return template;
}
}
Redis String 자료구조 사용
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
@Service
public class RedisStringExample {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void setStringValue(String key, String value) {
redisTemplate.opsForValue().set(key, value);
}
public String getStringValue(String key) {
return (String) redisTemplate.opsForValue().get(key);
}
}
Redis Bitmap 자료구조 사용
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
@Service
public class RedisBitmapExample {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void setBit(String key, long offset, boolean value) {
redisTemplate.opsForValue().setBit(key, offset, value);
}
public boolean getBit(String key, long offset) {
return Boolean.TRUE.equals(redisTemplate.opsForValue().getBit(key, offset));
}
}
Redis List 자료구조 사용
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class RedisListExample {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void pushToList(String key, String value) {
redisTemplate.opsForList().rightPush(key, value);
}
public List<Object> getList(String key) {
return redisTemplate.opsForList().range(key, 0, -1);
}
}
Redis Hash 자료구조 사용
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Map;
@Service
public class RedisHashExample {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void putToHash(String key, String hashKey, String value) {
redisTemplate.opsForHash().put(key, hashKey, value);
}
public Map<Object, Object> getHash(String key) {
return redisTemplate.opsForHash().entries(key);
}
}
Redis Set 자료구조 사용
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Set;
@Service
public class RedisSetExample {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void addToSet(String key, String value) {
redisTemplate.opsForSet().add(key, value);
}
public Set<Object> getSet(String key) {
return redisTemplate.opsForSet().members(key);
}
}
Redis Sorted Set 자료구조 사용
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Set;
@Service
public class RedisSortedSetExample {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void addToSortedSet(String key, String value, double score) {
redisTemplate.opsForZSet().add(key, value, score);
}
public Set<Object> getSortedSet(String key) {
return redisTemplate.opsForZSet().range(key, 0, -1);
}
}
Redis HyperLogLog 자료구조 사용
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
@Service
public class RedisHyperLogLogExample {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void addToHyperLogLog(String key, String... values) {
redisTemplate.opsForHyperLogLog().add(key, values);
}
public long getHyperLogLogCount(String key) {
return redisTemplate.opsForHyperLogLog().size(key);
}
}
Redis Stream 자료구조 사용
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.stream.MapRecord;
import org.springframework.data.redis.connection.stream.ObjectRecord;
import org.springframework.data.redis.connection.stream.RecordId;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StreamOperations;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class RedisStreamExample {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void addToStream(String key, Map<String, String> data) {
StreamOperations<String, ?, ?> streamOps = redisTemplate.opsForStream();
streamOps.add(MapRecord.create(key, data));
}
public List<MapRecord<String, Object, Object>> readFromStream(String key) {
return redisTemplate.opsForStream().range(key, Range.unbounded());
}
}
정리
여태까지 Redis
를 사용만 해봤지 Redis
가 무엇인지는 잘 알지 못했다. 레디스의 코어 스레드가 싱글 스레드라는 것을 알게되었다.
또한, Redis
의 구성요소와 특징 그리고 장단점에 관해 여러모로 알 수 있었다.
참고
https://www.youtube.com/watch?v=92NizoBL4uA
저서 '스프링부트로 개발하는 MSA 컴포넌트' Chapter 10 - 김병부 지음.
'DB > Redis' 카테고리의 다른 글
[Redis] Redis 야무지게 사용하기 (0) | 2024.09.20 |
---|