홈시리즈

© 2026 Ki Chang. All rights reserved.

본 블로그의 콘텐츠는 CC BY-NC-SA 4.0 라이선스를 따릅니다.

☕후원하기소개JSON Formatter러닝 대기질개인정보처리방침이용약관

© 2026 Ki Chang. All rights reserved.

콘텐츠: CC BY-NC-SA 4.0

☕후원하기
소개|JSON Formatter|러닝 대기질|개인정보처리방침|이용약관

Redis 내부 원리 총정리 — 싱글 스레드부터 클러스터까지

정기창·2026년 3월 14일

Redis를 '그냥 캐시'로만 쓰던 시절

Redis를 처음 접했을 때는 단순했습니다. SET과 GET으로 캐시를 걸고, TTL을 설정하고, 가끔 DEL로 무효화하는 정도. 그것만으로도 충분히 유용했기에, 내부가 어떻게 동작하는지 궁금하지 않았습니다.

그런데 실무에서 Redis의 역할이 점점 넓어졌습니다. Bull Queue의 메시지 브로커로, RedLock의 분산 락으로, Refresh Token 저장소로, 세션 관리자로. 어느 순간 프로젝트에서 Redis가 관여하지 않는 곳이 없어졌고, "Redis가 죽으면 시스템 전체가 멈춘다"는 사실을 깨닫게 되었습니다.

이 글은 Redis의 내부 원리를 한 곳에 정리한 것입니다. 싱글 스레드 모델부터 자료구조, 영속성, 메모리 관리, 고가용성까지 — 각 주제의 핵심을 짚되, 깊이 있는 내용은 별도의 글로 분리했습니다.

싱글 스레드인데 왜 빠른가

"Redis는 싱글 스레드입니다." 이 말을 처음 들었을 때 직관적으로 이해가 안 됐습니다. 멀티코어 CPU 시대에 스레드를 하나만 쓴다니, 느려야 하는 것 아닌가?

결론부터 말하면, Redis가 빠른 이유는 싱글 스레드 "임에도 불구하고"가 아니라 싱글 스레드 "이기 때문에"입니다.

핵심 메커니즘 3가지

1. 인메모리 연산

Redis의 모든 데이터는 메모리에 존재합니다. 디스크 I/O가 없으므로 하나의 명령이 마이크로초(μs) 단위에 완료됩니다. MySQL이 디스크에서 B-Tree 인덱스를 타는 동안, Redis는 해시 테이블에서 O(1)으로 값을 꺼냅니다.

2. I/O 멀티플렉싱

Redis는 수천 개의 클라이언트 연결을 하나의 스레드로 처리합니다. 비결은 epoll(Linux)이나 kqueue(macOS) 같은 커널 레벨 I/O 멀티플렉싱입니다. 이벤트가 준비된 소켓만 골라서 처리하므로, 연결 수가 늘어나도 성능이 선형적으로 저하되지 않습니다.

3. 컨텍스트 스위칭 제거

멀티스레드 환경에서는 스레드 간 전환(context switch)과 락 경합(lock contention)이 발생합니다. Redis는 스레드가 하나이므로 이 비용이 0입니다. 락이 필요 없다는 것은 모든 명령이 원자적(atomic)으로 실행된다는 뜻이기도 합니다.

Redis 6.0부터 네트워크 I/O에 한해 멀티스레드를 도입했습니다. 하지만 명령 실행 자체는 여전히 싱글 스레드입니다. 네트워크 패킷의 읽기/쓰기만 별도 스레드에서 처리합니다.

핵심 자료구조와 시간복잡도

Redis가 단순한 Key-Value 저장소가 아닌 이유는 다양한 자료구조를 제공하기 때문입니다. 각 자료구조의 시간복잡도를 아는 것은 성능 예측에 필수적입니다.

String

가장 기본적인 타입입니다. 최대 512MB까지 저장 가능하며, 정수값일 경우 내부적으로 int 인코딩을 사용합니다.

SET, GET, INCR, DECR → O(1)
MSET, MGET → O(N) (N = 키 개수)
APPEND → O(1) (amortized)

List

이중 연결 리스트(Doubly Linked List) 기반이었으나, Redis 7.0부터는 listpack과 quicklist로 대체되었습니다.

LPUSH, RPUSH, LPOP, RPOP → O(1)
LINDEX, LSET → O(N) (N = 인덱스 위치)
LRANGE → O(S+N) (S = 시작 오프셋, N = 반환 요소 수)
LLEN → O(1)

큐(Queue)나 스택(Stack)으로 활용할 때는 양 끝에서의 O(1) 연산만 사용하면 됩니다. 중간 요소 접근은 O(N)이므로 피해야 합니다.

Hash

필드-값 쌍을 저장하는 구조로, 객체를 표현할 때 적합합니다. 내부적으로 필드 수가 적을 때는 listpack(이전에는 ziplist)으로, 많아지면 hashtable로 인코딩합니다.

HSET, HGET, HDEL → O(1)
HMSET, HMGET → O(N) (N = 필드 수)
HGETALL → O(N) (N = 전체 필드 수)
HLEN → O(1)

HGETALL은 필드가 수천 개 이상일 때 성능 이슈를 일으킬 수 있으므로, 대규모 Hash에서는 HSCAN을 사용하는 것이 안전합니다.

Set

중복 없는 문자열 집합입니다. 내부적으로 요소 수가 적고 정수값이면 intset, 그 외에는 hashtable을 사용합니다.

SADD, SREM, SISMEMBER → O(1)
SMEMBERS → O(N)
SINTER, SUNION, SDIFF → O(N*M) (N, M = 집합 크기)

집합 연산(교집합, 합집합, 차집합)은 강력하지만, 큰 집합 간 연산은 성능에 영향을 줍니다.

Sorted Set (ZSet)

Redis 자료구조 중 가장 독특하고 강력합니다. 각 요소에 score(점수)가 붙어 자동 정렬됩니다. 내부적으로 skiplist + hashtable 구조를 사용합니다.

ZADD, ZREM, ZSCORE → O(log N)
ZRANGE, ZREVRANGE → O(log N + M) (M = 반환 요소 수)
ZRANK → O(log N)
ZRANGEBYSCORE → O(log N + M)
ZCARD → O(1)

리더보드, 랭킹, 시간순 정렬 등에 활용됩니다. O(log N) 복잡도 덕분에 수백만 건에서도 빠르게 동작합니다.

내부 인코딩 전환

Redis는 데이터 크기에 따라 메모리 효율적인 인코딩을 자동 선택합니다.

자료구조소규모 인코딩대규모 인코딩전환 기준
Hashlistpackhashtablehash-max-listpack-entries: 128
Listlistpackquicklistlist-max-listpack-size: -2
Setintsethashtableset-max-intset-entries: 512
ZSetlistpackskiplistzset-max-listpack-entries: 128

소규모 데이터에서는 연속 메모리(listpack)를 사용해 캐시 효율을 높이고, 일정 크기를 넘으면 O(1) 또는 O(log N) 접근이 가능한 구조로 전환합니다.

영속성: RDB와 AOF

Redis는 인메모리 데이터베이스이지만, 데이터를 디스크에 저장하는 두 가지 방식을 제공합니다. 자세한 내용은 Redis 영속성 전략 — RDB vs AOF에서 다룹니다. Redis 7.0부터는 AOF + 혼합 방식(RDB preamble)이 기본 활성화되어 있으므로, 별도 설정 없이도 영속성이 동작합니다. 순수 캐시 용도라면 오히려 명시적으로 비활성화해야 합니다.

RDB (Redis Database)

특정 시점의 메모리 스냅샷을 바이너리 파일로 저장합니다. fork() 시스템 콜로 자식 프로세스를 만들고, Copy-on-Write(COW) 메커니즘을 활용합니다.

  • 장점: 컴팩트한 파일, 빠른 복구, 성능 영향 적음
  • 단점: 마지막 스냅샷 이후 데이터 유실 가능 (최대 수 분)
  • 적합한 경우: 백업, 재해 복구, 데이터 유실을 일부 감수할 수 있는 경우

AOF (Append Only File)

모든 쓰기 명령을 로그 파일에 순차적으로 기록합니다. fsync 정책에 따라 내구성 수준이 달라집니다.

  • always: 매 명령마다 fsync → 데이터 유실 0, 성능 최저
  • everysec: 1초마다 fsync → 최대 1초 유실, 성능과 내구성의 균형 (권장)
  • no: OS에 맡김 → 성능 최고, 유실 범위 예측 불가

혼합 방식 (Redis 4.0+)

AOF rewrite 시 RDB 스냅샷 + 이후 AOF 명령을 결합합니다. RDB의 빠른 복구 속도와 AOF의 낮은 데이터 유실을 모두 얻을 수 있습니다.

aof-use-rdb-preamble yes  # Redis 7.0부터 기본값

메모리 관리와 Eviction 정책

Redis는 메모리에 모든 데이터를 올리므로, 메모리가 가득 차면 어떤 데이터를 버릴지 결정해야 합니다.

maxmemory 설정

maxmemory 4gb
maxmemory-policy allkeys-lru

maxmemory를 설정하지 않으면 Redis는 메모리를 무한정 사용하다가 OOM Killer에 의해 죽을 수 있습니다. 프로덕션에서는 반드시 설정해야 합니다.

Eviction 정책

정책동작
noeviction메모리 초과 시 쓰기 거부 (에러 반환)
allkeys-lru모든 키에서 LRU(최근 미사용) 제거
allkeys-lfu모든 키에서 LFU(최소 빈도) 제거 (Redis 4.0+)
allkeys-random모든 키에서 랜덤 제거
volatile-lruTTL이 설정된 키에서 LRU 제거
volatile-lfuTTL이 설정된 키에서 LFU 제거
volatile-randomTTL이 설정된 키에서 랜덤 제거
volatile-ttlTTL이 가장 짧은 키 우선 제거

대부분의 캐시 용도에서는 allkeys-lru가 무난합니다. 접근 빈도까지 고려하고 싶다면 allkeys-lfu가 더 정확합니다. 만약 캐시와 영속 데이터가 함께 있다면, TTL이 설정된 키만 제거하는 volatile-lru를 고려할 수 있습니다.

Redis의 LRU는 정확한 LRU가 아닙니다. 전체 키를 스캔하는 대신, maxmemory-samples(기본 5) 개의 키를 랜덤 샘플링하여 그 중 가장 오래된 것을 제거합니다. 이 근사 LRU 방식은 O(1) 복잡도를 유지하면서 실제 LRU에 근접한 결과를 냅니다.

고가용성: Replication, Sentinel, Cluster

프로덕션에서 Redis를 단일 노드로 운영하면, 그 노드가 죽는 순간 서비스 전체가 멈춥니다. Redis는 세 가지 레벨의 고가용성을 제공합니다. 자세한 내용은 Redis Sentinel vs Cluster에서 다룹니다.

Replication (복제)

가장 기본적인 구성입니다. 마스터의 데이터를 하나 이상의 레플리카에 복제합니다.

  • 비동기 복제가 기본 — 마스터에 쓰기 후 레플리카에 전파되기까지 지연이 있습니다
  • 읽기 부하 분산에 유용하지만, 자동 장애 복구(failover)가 없습니다
  • 마스터가 죽으면 수동으로 레플리카를 승격해야 합니다

Sentinel (센티널)

Replication에 자동 failover를 더한 것입니다. Sentinel 프로세스들이 마스터를 모니터링하다가, 마스터가 죽으면 레플리카를 새 마스터로 자동 승격합니다.

  • 최소 3대의 Sentinel 프로세스 권장 (과반수 합의)
  • 데이터 샤딩 없음 — 단일 마스터에 모든 데이터 저장
  • 메모리가 한 노드 한계를 넘지 않는 규모에 적합

Cluster (클러스터)

데이터를 16,384개의 해시 슬롯으로 나누어 여러 노드에 분산합니다. 샤딩 + 고가용성을 모두 제공합니다.

  • 각 노드가 일부 슬롯만 담당 → 수평 확장(scale-out) 가능
  • 각 마스터 노드마다 레플리카를 둬서 failover 지원
  • 멀티키 명령 제한: 같은 슬롯에 있는 키끼리만 트랜잭션/파이프라인 가능
  • 대규모 데이터(수십 GB 이상)에 적합

선택 기준

데이터가 한 노드 메모리에 들어가는가?
├─ YES → Sentinel (자동 failover + 단순한 운영)
└─ NO  → Cluster (샤딩 + failover)

자동 failover가 필요 없는가?
├─ YES → Replication만 (가장 단순)
└─ NO  → Sentinel 또는 Cluster

정리

Redis를 깊이 이해하면, 단순한 캐시 이상의 가치를 끌어낼 수 있습니다. 어떤 자료구조가 O(1)이고 어떤 것이 O(N)인지 알면 성능 병목을 예측할 수 있고, 영속성 전략을 이해하면 데이터 유실 범위를 통제할 수 있고, 고가용성 옵션을 알면 장애에 대비할 수 있습니다.

이 글에서 다룬 각 주제에 대해 더 깊이 있는 내용은 별도의 글에서 다루었습니다.

  • 싱글 스레드와 I/O 멀티플렉싱 — 이벤트 루프의 동작 원리와 Redis 6.0+ I/O 스레드
  • RDB vs AOF 트레이드오프 — fork()의 비용, AOF rewrite, 혼합 방식의 실전 적용
  • Sentinel vs Cluster — 장애 복구 시나리오, 해시 슬롯, 운영 경험
  • Bull Queue + RedLock 실전기 — Redis를 메시지 브로커와 분산 락으로 쓴 경험

Redis는 단순해 보이지만, 그 단순함 뒤에 정교한 설계가 있습니다. 내부를 알수록 더 안전하고 효율적으로 쓸 수 있다는 것을 실감하게 됩니다.

RedisValkey백엔드데이터베이스캐시인프라면접 준비

관련 글

Redis가 싱글 스레드인데 왜 빠른가 — I/O 멀티플렉싱부터 6.0 스레드 모델까지

Redis가 싱글 스레드라는 말은 반만 맞습니다. 정확히 무엇이 싱글 스레드이고, I/O 멀티플렉싱은 어떻게 동작하며, Redis 6.0의 I/O 스레드는 무엇을 바꿨는지. 면접에서 "왜 빠른가?"에 확실히 답할 수 있도록 정리했습니다.

관련도 94%

Redis 영속성 전략 — RDB vs AOF, 그리고 혼합 방식의 트레이드오프

Redis는 인메모리 데이터베이스이지만, 데이터를 디스크에 저장하는 두 가지 방식이 있습니다. RDB 스냅샷의 fork()와 COW 메커니즘, AOF의 fsync 정책, 그리고 Redis 4.0의 혼합 방식까지. 실무 선택 기준을 정리했습니다.

관련도 93%

Redis Sentinel vs Cluster — 고가용성 아키텍처의 선택 기준

Redis 단일 노드가 죽으면 서비스 전체가 멈춥니다. Sentinel은 자동 failover를, Cluster는 샤딩과 failover를 제공합니다. 각각의 동작 원리, 장애 복구 시나리오, 그리고 어떤 상황에서 무엇을 선택해야 하는지 정리했습니다.

관련도 91%