1. Redis는 왜 사용할까?
일반적인 애플리케이션에서는 데이터를 데이터베이스에 저장하고 필요할 때 조회한다.
예를 들어 사용자 정보를 조회한다고 생각해보자.
Client
↓
Spring Server
↓
Database
↓
사용자 조회
↓
응답
그런데 동일한 데이터를 매우 자주 조회한다면 매 요청마다 DB에 접근하는 것은 비효율적일 수 있다.
이때 자주 사용하는 것이 Cache이다.
캐시는 자주 사용하는 데이터를 더 빠른 저장소에 임시로 저장해 두었다가 다시 사용하는 방식이다.
캐시를 사용하면 3가지의 장점을 발생할 수 있다.
1. 자주 접근하는 데이터의 빠른 검색
→ 사용자가 자주 요청하는 데이터를 미리 저장해두어, 재요청시 빠르게 제공
2. 원본 데이터 소스의 부하 감소
→ 캐시가 유효한 동안 반복적인 DB 조회를 줄여 DB의 부하 감소
3. 네트워크 지연 시간 최소화
→ 물리적으로 가까운 곳에 데이터를 꺼내기 때문에 응답 속도가 비약적으로 향상
대표적으로 Redis를 캐시로 사용하면 다음과 같은 구조를 만들 수 있다.
Client
↓
Server
↓
Redis
│
├─ 데이터 있음 → 바로 반환
│
└─ 데이터 없음
↓
DB 조회
↓
Redis에 저장
↓
반환
캐시에 데이터가 있는 경우를 Cache Hit, 없는 경우를 Cache Miss라고 한다.
Cache Miss가 발생하면 DB에서 데이터를 가져온 뒤 Redis에 저장하는 방식이 대표적인 Cache-Aside 패턴이다.
2. Redis란?
**Redis(Remote Dictionary Server)**는 데이터를 주로 메모리에 저장하는 In-Memory Data Structure Store이다.
기본적으로 Key-Value 형태로 데이터를 저장하지만 Value에 단순 문자열뿐 아니라 다양한 자료구조를 사용할 수 있다.
String
Hash
List
Set
Sorted Set
Stream
그래서 Redis는 단순한 캐시뿐 아니라 세션, Counter, Ranking, 메시징 등에도 활용할 수 있다.
Redis는 왜 빠를까?
가장 큰 이유는 데이터를 주로 **Memory(RAM)**에서 처리하기 때문이다.
또한 많은 Redis 명령은 하나의 메인 실행 흐름에서 순차적으로 처리되기 때문에 복잡한 Lock 경쟁을 줄일 수 있다.
흔히 Redis를 싱글 스레드라고 표현하지만 모든 작업이 하나의 스레드에서 실행된다는 의미는 아니다. Redis 6 이후에는 네트워크 데이터를 읽고 쓰는 I/O 작업에 여러 스레드를 사용할 수 있다. 하지만 GET, SET, INCR처럼 실제 Redis 데이터를 읽거나 수정하는 명령 실행은 주로 메인 스레드에서 순차적으로 처리된다. 따라서 하나의 Redis 명령은 실행 도중 다른 명령이 끼어들지 않아 원자적으로 처리할 수 있다.
3. Redis의 주요 자료구조
Redis에서는 데이터의 특성에 맞는 자료구조를 선택하는 것이 중요하다.
| 자료구조 | 특징 | 대표 사용 |
|---|---|---|
| String | 하나의 값을 통째로 저장 | 캐시, Counter |
| Hash | Field-Value 단위 저장 | 필드별 조회, 수정이 필요한 데이터 |
| List | 순서 있는 데이터 | Queue |
| Set | 중복 없는 집합 | 좋아요, 태그 |
| Sorted Set | Score 기반 정렬 | Ranking |
| Stream | 메시지 기록 | 이벤트 처리 |
3.1 String
가장 기본적인 자료구조이다.
SET user:1:name "Evan"
GET user:1:name
→ "Evan"
문자열뿐 아니라 직렬화된 객체나 숫자 등도 저장할 수 있다.
특히 숫자에는 INCR, DECR 같은 연산을 사용할 수 있다.
SET views 100
INCR views
→ 101
따라서 캐시, 조회수, 요청 횟수, Counter 등에 사용할 수 있다.
3.2 Hash
하나의 Key 안에 여러 Field-Value를 저장하는 자료구조이다.
user:1
│
├─ name → Evan
├─ age → 30
└─ job → Backend
HSET user:1 name Evan age 30
HGET user:1 name
→ Evan
객체 전체가 아니라 특정 Field만 조회하거나 수정할 수 있기 때문에 사용자 정보나 세션처럼 여러 속성을 가진 데이터를 표현하기 좋다.
3.3 List
List는 순서가 유지되는 자료구조이다.
양쪽 끝에서 값을 추가하거나 제거할 수 있다.
LPUSH / RPUSH
LPOP / RPOP
이를 이용하면 FIFO 방식의 Queue 등을 만들 수 있다.
task1 → task2 → task3
Redis는 데이터가 들어올 때까지 기다리는 BLPOP, BRPOP 같은 Blocking 연산도 지원하기 때문에 간단한 작업 Queue에 활용할 수 있다.
3.4 Set
Set은 중복을 허용하지 않는 집합이다.
post:1:likes
user1
user2
user3
같은 user1을 다시 추가해도 중복 저장되지 않는다.
또한 교집합, 합집합, 차집합 등의 연산을 지원한다.
A = {Redis, Kafka, Spring}
B = {Redis, Docker, Spring}
교집합
= {Redis, Spring}
따라서 좋아요 사용자, 태그, 친구 목록 등 중복되지 않는 데이터를 다룰 때 유용하다.
3.5 Sorted Set
Sorted Set은 Set처럼 Member의 중복을 허용하지 않으면서 각 Member에 Score를 부여한다.
userA → 1200
userB → 1700
userC → 1500
Score를 이용하면:
1. userB → 1700
2. userC → 1500
3. userA → 1200
처럼 순위를 만들 수 있다.
따라서 게임 Ranking, 인기 검색어, 점수 기반 순위 등에 적합하다.
Set
→ 중복 X
→ 정렬 기준 X
Sorted Set
→ 중복 X
→ Score 기준 정렬 O
3.6 Stream
Stream은 메시지나 이벤트를 순서대로 저장하는 로그 형태의 자료구조이다.
1 → 주문 생성
2 → 결제 완료
3 → 배송 시작
List와 달리 단순히 메시지를 꺼내는 것뿐 아니라 메시지를 기록하고 처리 상태를 관리할 수 있다.
여러 Consumer가 메시지를 처리할 때는 Consumer Group을 사용할 수 있다.
Stream
↓
Consumer Group
/ \
Consumer A Consumer B
처리가 완료되면 XACK를 보내고, 아직 ACK되지 않은 메시지는 Pending 상태로 관리할 수 있다.
XACK는 메시지 자체를 삭제하는 것이 아니라 해당 Consumer Group에서 처리가 완료됐음을 기록하는 것이다.
4. TTL은 왜 필요할까?
캐시에는 DB와 다른 오래된 데이터가 남을 수 있다.
예를 들어 DB의 사용자 이름은 변경됐는데 Redis에는 이전 이름이 계속 저장되어 있을 수 있다.
이런 데이터를 Stale Data라고 한다.
Redis에서는 Key에 **TTL(Time To Live)**을 설정해 일정 시간이 지나면 자동으로 만료시킬 수 있다.
SET user:1 "data" EX 300
캐시 저장
↓
300초
↓
자동 만료
따라서 실제 캐시에서는 TTL 설정이나 캐시 삭제·갱신 등을 통해 DB와 캐시 사이의 데이터를 관리해야 한다.
Eviction
TTL이 시간이 지나면서 데이터를 만료시키는 기능이라면,
Eviction은 Redis의 메모리가 부족할 때 어떤 데이터를 제거할지 결정하는 정책이다.
대표적으로:
allkeys-lru
→ 가장 오래 사용되지 않은 Key 제거
allkeys-lfu
→ 사용 빈도가 낮은 Key 제거
noeviction
→ Key를 제거하지 않고 새로운 쓰기 요청에 오류 반환
따라서 Redis를 캐시로 사용할 때는 TTL뿐 아니라
메모리 제한과 Eviction 정책도 함께 고려해야 한다.
5. Pub/Sub과 Stream의 차이
Redis에서는 Pub/Sub(Publish/Subscribe) 방식의 메시징도 사용할 수 있다.
Publisher
↓
Channel
/ \
↓ ↓
A B
Subscriber
Publisher가 메시지를 발행하면 현재 해당 Channel을 구독 중인 Subscriber에게 바로 전달된다.
하지만 Pub/Sub은 메시지를 저장하지 않는다.
따라서 Subscriber가 연결되어 있지 않았다면 해당 메시지를 나중에 다시 받을 수 없다.
Stream과 비교하면:
| 구분 | Pub/Sub | Stream |
|---|---|---|
| 실시간 수신 | O | O |
| 메시지 저장 | X | O |
| 과거 메시지 조회 | X | O |
| Consumer Group | X | O |
| ACK / Pending | X | O |
| 목적 | 가벼운 실시간 Broadcast | 기록, 재처리가 필요한 이벤트 처리 |
즉:
저장이나 재처리가 필요 없는 실시간 메시지 전파
→ Pub/Sub
메시지를 기록하고 처리 상태까지 관리
→ Stream
이라고 이해하면 된다.
6. Redis의 원자성과 Transaction
Redis의 INCR처럼 하나의 명령은 원자적으로 처리할 수 있다.
하지만:
GET stock
↓
값 계산
↓
SET stock
처럼 여러 명령을 조합하면 그 사이에 다른 Client의 명령이 실행될 수 있다.
즉:
Redis가 명령을 순차적으로 처리한다
= 여러 명령으로 만든 로직 전체가 자동으로 원자적이다
는 아니다.
이런 경우 Redis의 **Transaction(MULTI/EXEC)**이나 Lua Script 등을 사용할 수 있다.
또 Pipeline도 자주 등장하는데 Transaction과 목적이 다르다.
Pipeline
→ 여러 명령을 묶어서 전송
→ 네트워크 RTT 감소
→ 성능 최적화
Transaction
→ 여러 명령을 Queue에 저장한 뒤 실행
→ 실행 중 다른 Client의 명령이 끼어들지 않음
→ 단, Redis Transaction은 일반적인 RDB Transaction과 달리 실행 중 오류가 발생했다고 해서 이미 실행된 명령을 Rollback하지 않는다.
따라서 Pipeline을 사용했다고 해서 자동으로 Transaction이 되는 것은 아니다.
7. RDB와 AOF
Redis는 In-Memory 기반이지만 데이터를 디스크에 저장하는 Persistence(영속성) 기능도 제공한다.
대표적인 방법이 RDB와 AOF이다.
RDB
특정 시점의 Redis 데이터를 Snapshot으로 저장한다.
Redis 상태
↓
Snapshot
파일이 비교적 작고 복구가 빠른 편이지만 마지막 Snapshot 이후 변경된 데이터는 장애 발생 시 잃을 수 있다.
AOF
Redis에서 수행된 쓰기 명령을 Log 형태로 기록한다.
SET A 1
INCR A
SET B 10
...
RDB보다 많은 변경 내용을 보존할 수 있지만 파일 크기가 커질 수 있고 쓰기 및 복구 비용이 증가할 수 있다.
RDB
→ Snapshot
AOF
→ 쓰기 명령 Log
두 방식을 함께 사용하는 것도 가능하다.
8. Sentinel과 Cluster
Redis 서버 하나에 장애가 발생하면 서비스를 계속 사용할 수 없을 수 있다.
Sentinel
Sentinel은 Redis 서버를 감시하고 Primary 장애 시 Replica를 새로운 Primary로 승격하는 Failover를 지원한다.
Primary 장애
↓
Sentinel 감지
↓
Replica 승격
즉 **고가용성(High Availability)**이 주요 목적이다.
Cluster
Cluster는 데이터를 여러 Redis Node에 나누어 저장하는 Sharding을 지원한다.
Node A → 데이터 일부
Node B → 데이터 일부
Node C → 데이터 일부
따라서:
Sentinel
→ 장애 대응 / 고가용성
Cluster
→ 데이터 분산 / 수평 확장 + 고가용성
이라고 구분할 수 있다.
9. Redis를 이용한 분산 락
여러 서버가 동일한 자원을 동시에 수정해야 한다면 한 서버 내부의 Lock만으로는 충분하지 않다.
Server A ─┐
├→ 동일한 자원
Server B ─┘
이때 모든 서버가 접근할 수 있는 Redis를 이용해 분산 락을 구현할 수 있다.
Redis의 SET NX 등을 이용해 Lock이 없는 경우에만 획득하도록 만들 수 있다.
다만 단순히 SETNX만 사용해서는 충분하지 않다.
안전하게 사용하려면:
Lock 소유자 식별
TTL 설정
해제 시 소유권 확인
등을 함께 고려해야 한다.
원본 자료에서도 Redis 분산 락에서는 소유자를 식별하는 값과 만료 시간을 사용하고, 해제 시 소유권을 원자적으로 확인해야 한다고 설명한다.
10. 핵심 정리
Redis 기능을 요구사항 기준으로 정리하면 다음과 같다.
| 요구사항 | 사용할 수 있는 기능 |
|---|---|
| 단순 캐시 | String + TTL |
| 객체의 여러 속성 | Hash |
| 간단한 Queue | List |
| 중복 제거 | Set |
| Ranking | Sorted Set |
| 메시지 기록 및 처리 | Stream |
| 실시간 메시지 전달 | Pub/Sub |
| 데이터 복구 | RDB / AOF |
| 장애 대응 | Sentinel |
| 데이터 분산 | Cluster |
| 여러 서버의 동시성 제어 | Distributed Lock |