Redis 개요 및 핵심 특징
Redis는 높은 성능의 분산 키-값 저장 시스템으로, 캐시와 분산 데이터 구조 서버로 작동합니다. 이를 통해 고컨커런시 환경에서 데이터 접근 속도를 향상시키고 다양한 비즈니스 요구 사항을 처리할 수 있습니다.
1. Redis의 주요 기능
- 캐시: 데이터베이스 병목 현상을 해결하고 읽기 성능을 향상시킵니다.
- 분산 데이터 구조 서버: String, Hash, List, Set, Sorted Set 등 다양한 데이터 구조를 지원하여 카운터, 순위표, 분산 세션 등을 구현합니다.
- 경량 메시지 큐: Pub/Sub 또는 Streams를 사용해 비동기 통신을 제공합니다.
2. Redis의 주요 특성
| 특성 | 설명 |
|---|---|
| 싱글 스레드 모델 | 모든 요청을 하나의 스레드에서 처리하며 epoll 다중 플렉싱을 통해 10만 QPS 이상을 지원합니다. |
| 키-값 저장소 | 키는 문자열이며 값은 다양한 데이터 구조를 지원합니다. |
| 만료 메커니즘 | TTL(Time To Live) 기반으로 키가 자동으로 만료됩니다. |
| 지속화 | RDB(스냅샷)와 AOF(로그)를 통해 데이터 안전성을 보장합니다. |
| 고가용성 | 마스터-슬레이브 복제, 센티널(Sentinel), Redis 클러스터를 통해 단일 포인트 오류를 해결합니다. |
Redis의 성능 요인
1. 싱글 스레드 모델의 원리
Redis의 싱글 스레드는 실제로 모든 요청을 하나의 스레드에서 처리하지만, IO 작업이나 지속화 작업은 별도의 스레드에서 실행됩니다. 이 덕분에:
- 문맥 전환이 필요 없어 효율적입니다.
- epoll 다중 플렉싱을 통해 여러 클라이언트 연결을 동시에 처리합니다.
- C 언어로 구현된 최적화된 데이터 구조를 사용합니다.
2. 데이터 구조의 구현
다음 표는 각 데이터 구조의 구현 방식과 주요 특징을 보여줍니다.
| 데이터 구조 | 구현 방법 | 핵심 특징 | 적용 예시 |
|---|---|---|---|
| 스트링(String) | SDS(Simple Dynamic String) | 가변 길이 문자열, 예비 할당을 지원합니다. | 캐시, 카운터, 분산 잠금 |
| 해시(Hash) | 압축 리스트(ziplist) → 해시 테이블 | 작은 데이터에는 압축 리스트를 사용해 메모리를 절약합니다. | 객체 속성 저장, 장바구니 관리 |
| 리스트(List) | 빠른 리스트(quicklist) | 양방향 연결 리스트와 압축 리스트를 결합하여 메모리 사용을 줄입니다. | 메시지 큐, 최근 활동 목록 |
| 셋(Set) | 정수 집합(intset) → 해시 테이블 | 작은 정수 집합에서는 메모리 절약 효과를 제공합니다. | 중복 제거, 공통 친구 찾기 |
| 정렬된 셋(Sorted Set) | 스킵 리스트(skiplist) + 해시 테이블 | O(logN) 범위 쿼리를 지원합니다. | 랭킹 시스템, 시간대별 정렬 |
3. 지속화 메커니즘
Redis의 지속화는 RDB와 AOF를 조합하여 데이터 손실 문제를 해결합니다.
(1) RDB(Redis Database Snapshot)
- 주기적으로 메모리 데이터를 디스크에 스냅샷으로 저장합니다.
- `save`, `bgsave` 명령어로 수동 또는 자동 트리거됩니다.
- 파일 크기가 작고 복구 속도가 빠릅니다.
(2) AOF(Append Only File)
- 모든 쓰기 작업을 텍스트 형태로 파일에 추가합니다.
- `appendfsync always`, `everysec`, `no` 옵션으로 동기화 정책을 설정할 수 있습니다.
- AOF 재작성(AOF Rewrite)을 통해 로그를 최적화합니다.
(3) 혼합 지속화(Mixed Persistence)
- RDB 스냅샷과 AOF 로그를 결합한 방식으로, 성능과 안전성을 모두 확보합니다.
고급 특징 및 활용
1. 분산 잠금
Redis를 사용한 분산 잠금은 여러 프로세스 간의 상호 배제를 보장합니다.
-- Lua 스크립트로 잠금 해제
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
2. 트랜잭션 및 낙관적 잠금
Redis의 트랜잭션은 MULTI/EXEC 명령어를 통해 수행되며, WATCH 메커니즘을 통해 낙관적 잠금을 구현할 수 있습니다.
3. 스트림(Streams)
Redis 5.0부터 제공되는 스트림은 메시지를 영구적으로 저장하고 소비자 그룹을 관리할 수 있는 기능을 제공합니다.
실제 적용 사례
1. 캐시
데이터베이스로부터 가져온 데이터를 Redis에 저장하고 일정 시간 이후 자동 삭제합니다.
2. 카운터
# Python 코드로 인크먼트 구현
redis.incr('like_count:article_123')
3. 순위표
# Python 코드로 순위표 생성
redis.zadd('rank', {'user_1': 1000, 'user_2': 900})
top_users = redis.zrevrange('rank', 0, 9, withscores=True)
4. 분산 세션
Spring Boot 구성 예시:
spring:
session:
store-type: redis
timeout: 1800 # 초 단위
5. 리밋팅
public boolean limitRequest(String ip) {
long now = System.currentTimeMillis();
long window = 1000; // 1초
long maxRequests = 100;
redis.zremrangeByScore(ip, "0", String.valueOf(now - window));
long count = redis.zcard(ip);
if (count < maxRequests) {
redis.zadd(ip, now, String.valueOf(now));
redis.expire(ip, (int)(window / 1000));
return true;
}
return false;
}
인터뷰 대비 질문
- Redis의 스레드 모델은 무엇이며 왜 싱글 스레드인지?
- RDB와 AOF의 차이는 무엇이며 어떻게 사용해야 하나?
- 캐시 페너스의 해결 방법은?
- 분산 잠금의 신뢰성은 어떻게 보장하나?