← 전체 글

[CS] Redis 자료구조

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
HashField-Value 단위 저장필드별 조회, 수정이 필요한 데이터
List순서 있는 데이터Queue
Set중복 없는 집합좋아요, 태그
Sorted SetScore 기반 정렬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/SubStream
실시간 수신OO
메시지 저장XO
과거 메시지 조회XO
Consumer GroupXO
ACK / PendingXO
목적가벼운 실시간 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
간단한 QueueList
중복 제거Set
RankingSorted Set
메시지 기록 및 처리Stream
실시간 메시지 전달Pub/Sub
데이터 복구RDB / AOF
장애 대응Sentinel
데이터 분산Cluster
여러 서버의 동시성 제어Distributed Lock
[CS] 트랜잭션 ACID · 격리수준
[CS] TCP vs UDP · Handshake
© 2026 Evan. All Rights Reserved. / RSS / Sitemap
Powered by Astro