> For the complete documentation index, see [llms.txt](https://leehyokyun.gitbook.io/openbook/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://leehyokyun.gitbook.io/openbook/4-2.-redis-bottleneck-1-redis.md).

# 4-2. Redis 활용 및 병목 현상 (Bottleneck) #1 : Redis에 대한 고찰

Redis 설계 사상 및 내부 구조에 대하여

### 📌 Redis의 설계 사상 [\[세부적인 내용은 벨로그에 자세하게 기술\]](https://velog.io/@gyrbs22/%EB%B0%B1%EC%97%94%EB%93%9C-Redis%EA%B0%80-%EB%B9%A0%EB%A5%B8-%EC%9D%B4%EC%9C%A0-%EC%9D%B8%EB%A9%94%EB%AA%A8%EB%A6%AC%EB%8A%94-%ED%95%84%EC%9A%94%EC%A1%B0%EA%B1%B4%EC%9D%B4-%EC%95%84%EB%8B%8C-%EC%B6%A9%EB%B6%84%EC%A1%B0%EA%B1%B4%EC%9D%B4%EB%8B%A4)

### 1. Redis 인메모리는 필요조건이 아닌 충분조건이다.

* **역 그리디(Reverse Greedy)적 접근**: 동적 계획법(DP)이 이전 단계에서 만족한 최적 상태를 현재 단계에 누적하듯, Redis의 속도 또한 단순 정방향 접근이 아닌 역발상적 아키텍처 사상에서 비롯된다.
* **통념의 오류**: 흔히 Redis는 단순히 "인메모리(RAM)여서 빠르다"고 생각하지만, 본질은 **인메모리라는 환경을 극한으로 활용하기 위해 설계된 특별한 구조와 전략** 때문이다.
* 자주 조회되는 데이터를 RAM 캐시(Buffer)에 두는 일반 RDB(PostgreSQL 등)와 비교했을 때, Redis가 독보적으로 빠른 이유는 '데이터의 위치'가 아니라 '인메모리를 활용하는 차별화된 전략'에 있다.

***

### 2. Redis가 선택한 6가지 핵심 제거 전략

Redis는 RAM을 메인 저장소로 채택한 뒤, 메모리 단에서 발생할 수 있는 하드웨어·OS 수준의 병목 장벽을 구조적으로 제거하였다.

#### 2-1. 싱글스레드 (Single-Thread)

* **도입 목적**: 동시성 제어용 대안이 아닌, **멀티스레드가 유발하는 성능 저하 장벽을 제거하기 위한 전략**이다.
* **제거 대상 (멀티스레드의 단점)**:
  * **Lock/Mutex 오버헤드**: 자원 동기화 처리로 인한 대기 체증 및 성능 저하 발생
  * **Cache Line Bouncing**: 멀티코어가 동일한 64바이트 캐시 라인을 서로 점유하려 경쟁하면서 발생하는 유령 캐시 미스 및 메모리 트래픽 병목 현상
  * **False Sharing (가짜 공유)**: 스레드 A와 B가 서로 다른 멤버 변수(`countA`, `countB`)를 바라보더라도 두 변수가 물리적으로 같은 캐시 라인에 존재해, 한쪽의 갱신이 다른 쪽 코어의 캐시 라인을 무효화(Invalidate)시키는 현상
* **Redis의 해결책**: `1 Thread ── 1 Event Loop ── 1 Command at a time` 구조를 채택하여 CPU 참조 국소성(Locality)을 극한으로 올리고 자원 경쟁 및 락 오버헤드를 원천 차단한다.

#### 2-2. 논블로킹 I/O (Non-Blocking)

* **I/O 멀티플렉싱**: OS 커널 수준의 `epoll`(Linux), `kqueue`(BSD), `select`(Fallback)를 활용하여, 싱글스레드 하나가 수천\~수만 개의 커넥션을 차단(Blocking) 없이 관리하며 준비된 I/O를 즉시 처리한다.
* **컨텍스트 스위칭 제거**: 외부 통신 반환을 대기하며 블로킹되지 않고 즉시 제어권을 리턴(`Non-blocking`)하므로, OS 스케줄러 개입과 무거운 컨텍스트 스위칭 비용이 발생하지 않는다. 오직 메모리-메모리 간 연산(Stack, Heap 참조) 중심의 초고속 처리가 가능하다.

#### 2-3. 예측 가능한 명령 체계

* **싱글스레드 환경 보호**: 이벤트 루프가 특정 명령에 의해 독점되는 것을 막기 위해 모든 명령의 실행 시간을 O(1) 또는 O(log N) 수준으로 예측 가능하게 유지한다.
* **분할 탐색 지향**: 모든 키를 한 번에 가져와 루프를 블로킹하는 `keys` 방식 대신, 일정량만 조회하고 제어권을 넘겨주는 `SCAN`(`ScanOptions`) 방식을 권장하여 타일 레이턴시(Tail Latency)를 최소화하고 다른 명령 처리를 동시에 지원한다.

#### 2-4. CPU 친화적인 내장 자료구조 (Cache-Friendly)

* **포인터 추적(Pointer Chasing) 배제**: 메모리 사방에 파편화되어 분산되는 다중 포인터 구조(LinkedList, Tree, HashMap)로 인한 비효율성과 캐시 미스를 회피한다.
* **오프셋 기반 연산**: 데이터 저장소 자체가 물리적으로 뭉쳐져 있는 응집도 높은 구조(`[header][entry][entry][end]`)를 사용한다. 인접 노드로 이동할 때 포인터가 아닌 오프셋(Offset)으로 순차 스캔하여 CPU 캐시 히트율을 극대화한다.

#### 2-5. 통신 프로토콜 단순화 (RESP)

* **파싱 비용 최소화**: JSON, HTTP, gRPC와 같이 파싱 오버헤드가 큰 포맷과 복잡한 상태 머신을 배제하고, 독자적인 텍스트 기반 프로토콜인 RESP(REdis Serialization Protocol)를 사용한다.
* **기계 친화적 저급 구조**: 라인 단위의 극도로 단순한 파싱 상태 머신을 유지함으로써, 불필요한 직렬화/역직렬화 비용과 네트워크 대역폭 낭비를 걷어내고 효율적이고 빠른 통신을 지원한다.

#### 2-6. 선택적 디스크 I/O 격리 (AOF/RDB)

* **핫 패스(Hot-Path) 최적화**: 메인 연산 경로는 철저하게 `Client ➡ RAM ➡ 응답`으로만 설계되어 디스크 접근이 전무하며 충분한 성능과 예측 가능성을 확보한다.
* **백그라운드 격리**: 지속성 보장을 위한 AOF 로그 기록 및 체크포인트 저장은 메인 스레드와 격리된 백그라운드 영역에서 안정성 보장용으로만 비동기 수행되어 메인 연산 속도를 보장한다.

***

### 3. 요약 및 아키텍처 결론

Redis는 레이턴시를 유발하는 하드웨어 및 OS 수준의 모든 병목을 제거하고 **오직 순수한 네트워크 비용만 남겼기 때문에**  단위의 경이로운 속도를 낼 수 있다.

| 제거 대상              | 제거함으로써 얻은 아키텍처 효과                    |
| ------------------ | ------------------------------------ |
| **Lock (동기화)**     | 자원 점유 경쟁 지연 및 데드락 제거                 |
| **Context Switch** | OS 스케줄러 개입 최소화, 예측 가능성 증가            |
| **Blocking I/O**   | 스레드 유휴 상태 방지, 꼬리 지연(Tail Latency) 감소 |
| **Disk I/O**       | 메인 요청 처리 경로의 핫 패스 단순화                |
| **복잡한 프로토콜**       | 파싱 오버헤드 억제 및 CPU 자원 소모 감소            |

> 💡 **개발자를 위한 인사이트** 개발자가 Redis를 다룰 때 단순히 `redisTemplate`을 기계적으로 사용하는 것을 넘어, Redis의 싱글스레드 사상과 CPU 친화적 구조를 이해해야 한다. 포인터 추적을 최소화하는 자료구조를 선택하고, 이벤트 루프를 독점하지 않는 명령(`SCAN` 등)으로 접근 방향을 설정할 때 Redis의 성능적 강점을 100% 이끌어낼 수 있다.

### 📌 Redis의 내부구조 [\[세부적인 내용은 벨로그에 자세하게 기술\]](https://velog.io/@gyrbs22/%EB%B0%B1%EC%97%94%EB%93%9C-Redis%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B3%A0%EC%B0%B0Redis%EC%97%90%EC%84%9C-%EB%B0%9C%EC%83%9D%EA%B0%80%EB%8A%A5%ED%95%9C-%EB%8B%A4%EC%96%91%ED%95%9C-%EC%8B%A4%EB%AC%B4%EC%A0%81-%EC%83%81%ED%99%A9%EA%B3%BC-%EC%9D%B4%EC%97%90-%EB%8C%80%ED%95%9C-Trouble-Shootings-1#1-%EA%B0%9C%EC%9A%94)

### 1. 개요 및 핵심 사상

* **설계 사상의 중요성**: Redis가 압도적인 성능을 내는 본질은 단순히 RAM을 사용하기 때문이 아니다. 싱글 스레드 기반의 이벤트 루프, 컨텍스트 스위칭 비용 절감, Non-blocking 네트워크 I/O, 그리고 파싱이 간단한 RESP 프로토콜이 맞물린 결과다.
* **실무적 접근**: Redis는 단순 캐시를 넘어 분산 락, 실시간 핫 데이터(Hot Data) 처리 등 다양한 용도로 사용된다. 구조적 장벽을 제거한 설계 사상을 명확히 인지하고 실무 아키텍처에 적용해야 한다.

***

### 2. 대안 기술과의 비교 및 도입 고려사항

물리적 비용과 운영 복잡도가 높은 Redis를 채택하기 전, 아래의 기술군과 명확한 비교 검토를 거쳐야 한다.

#### 2-1. 관계형 데이터베이스 (RDB)

* **고려사항**: RDB의 디스크 I/O로 인한 Latency 편차(100ms\~1s)를 극복해야 하는가?
* **차별점**: Redis는 복잡한 스키마와 참조 관계를 배제하고 String, List, SortedSet 등 단순명료한 Key-Value 구조와 가벼운 RESP 프로토콜을 사용하여 RDB의 트래픽 병목을 완화한다.

#### 2-2. MongoDB (NoSQL)

* **고려사항**: 자유로운 데이터 모델 관리가 목적인가, 실시간 원자성 처리가 목적인가?
* **차별점**: MongoDB는 Document(JSON) 기반의 동적 속성 추가에 특화된 영속성 중심의 NoSQL이다. 멀티 트랜잭션 원자성을 보장하지 않고 최종 일관성(Eventually Consistency)을 따르므로, 실시간 Hot Data 연산 및 무결성 보장에는 Redis가 더 적합하다.

#### 2-3. Memcached

* **고려사항**: 오직 순수 캐싱만을 원하며 예산이 제한적인가?
* **차별점**: Memcached는 오직 String 타입만 지원하며 영속성이 전혀 없다. 재시작 시 데이터가 즉시 휘발된다. 또한 멀티 스레드 블로킹 방식으로 동작하여 Scale-out 확장에 불리하다. 반면 Redis는 다양한 자료구조, TTL, 고가용성 확장 기능을 제공한다.

***

### 3. 고가용성(HA) 확보를 위한 클러스터 환경 구성 전략

처음부터 과도한 환경을 구축하기보다 트래픽 규모와 요구사항에 맞춰 수평 확장(Scale-out)해야 한다.

#### 3-1. 단일 Master 서버

* 모든 요청을 인스턴스 한 대가 처리한다.
* 단일 장애점(SPOF)이 존재하여 서버 다운 시 서비스가 중단되며, 단일 스펙(CPU/메모리)에 의존하므로 소규모 환경에만 적합하다.

#### 3-2. Master - Replica 구조

* 쓰기(Write)는 Master, 읽기(Read)는 Replica가 담당하여 부하를 분산한다.
* Application 단이나 Client 설정(ReadFrom)을 통해 명령을 분기한다.
* 비동기 명령 스트리밍(Command Streaming) 방식으로 복제가 이루어지므로 시점에 따라 데이터 불일치가 발생할 수 있다. 최초 동기화 시에는 Master의 RDB 스냅샷을 통째로 전송한다.

#### 3-3. Not Clustered - Sentinel 구조

* Master와 Replica의 상태를 감시하는 Sentinel 노드를 추가한 구조다.
* **장애 감지 프로세스**: 한 노드가 장애를 감지하면 주관적 장애(SD, Subjectively Down) 상태가 되며, 이것이 지정된 정족수(Quorum)를 넘어서 전파되면 객관적 장애(OD, Objective Down)로 판단한다.
* **자동 페일오버**: 객관적 장애 판단 시 Sentinel이 자동으로 새로운 Master를 선출하고 Replica를 재구성하여 서비스 중단 없이 가용성을 유지한다.

#### 3-4. Clustered (Redis Cluster)

* 데이터를 여러 Master 노드에 나누어 저장하는 샤딩(Sharding) 구조다.
* 총 16384개의 Hash Slot을 나누어 가지며, 노드 간 상태 확인은 가십 프로토콜(Gossip Protocol)을 통해 무작위로 소문을 퍼뜨리듯 전파되어 최종 수렴된다.
* **CROSSSLOT 에러**: 여러 노드에 흩어진 Key들을 하나의 명령으로 처리하는 aggregation 파이프라인이 없기 때문에 발생한다. 2PC 구현이 불가능하여 분산 트랜잭션의 원자성 보장이 어렵다.
* **해시 태그(Hash Tag) 해결책**: key에 중괄호를 사용하면(예: {user:1}:a, {user:1}:b) 괄호 안의 값만 해싱하므로 동일한 Hash Slot(동일 노드)에 데이터를 강제 위치시켜 원자적 연산을 가능하게 한다.

***

### 4. 내장 자료구조의 내부 구현 및 최적화 원리

Redis는 C언어 기반이지만 성능 극대화를 위해 독자적인 적응형 자료구조를 사용한다.

#### 4-1. String과 SDS (Simple Dynamic String)

Redis의 String은 단순 C 구조체인 sdshdr를 사용한다. 문자열 길이(len), 남은 공간(free), 실제 버퍼(buf)를 필드로 가진다.

* **O(1) 길이 조회**: len 필드가 존재하여 문자열 길이를 즉시 파악한다.
* **Binary Safe**: 문자열 끝을 나타내는 Null Byte(\0)에 의존하지 않고 len 기준으로 파싱하므로 이미지, 직렬화 페이로드 등 모든 이진 데이터를 안전하게 저장한다. 단, 단일 Key당 최대 크기는 512MB로 제한된다.
* **버퍼 오버플로우 방지 및 Pre-allocation**: 문자열 확장 시 매번 메모리 재할당(realloc)을 하지 않고, free 공간을 여유 있게 확보하는 전략으로 공간/시간 효율성을 높인다.

**String의 3가지 인코딩 방식**

1. **INT**: 64비트 내의 정수 값은 문자열이 아닌 정수 형태로 다루어 비트 수준에서 메모리를 절약한다.
2. **EMBSTR (짧은 문자열, 44Byte 이하)**: 데이터 파편화를 막기 위해 redisObject, sdshdr, buf를 단 1번의 malloc으로 연속된 메모리 공간에 할당한다. CPU 캐시 라인(64Byte) 내에 안착하므로 Cache Hit 율이 극대화되며 이 구조는 Immutable하게 관리된다.
3. **RAW (긴 문자열)**: 데이터 수정 및 추가에 유연하게 대응하도록 redisObject와 sdshdr를 분리하여 총 2번의 malloc을 수행한다. 포인터로 연결되므로 유연하지만 캐시 효율은 EMBSTR보다 낮다. (EMBSTR에 append 연산이 발생하면 포인터를 버리고 RAW 구조로 새로 할당된다.)

#### 4-2. List (Quicklist)

* Redis 7.0 이상부터는 Ziplist와 LinkedList를 혼합한 **Quicklist**를 사용한다.
* 전체 구조는 LinkedList 형태를 취하여 Head와 Tail에서의 삽입/삭제가 매우 빠르다(O(1)). 내부의 각 노드는 연속된 배열인 Ziplist로 이루어져 메모리 효율을 높인다.
* 특정 인덱스 조회나 중간 삽입/삭제 시에는 여전히 O(N)의 탐색 비용이 발생하므로 대규모 데이터 처리 시 주의해야 한다.

#### 4-3. Set (Intset & Hashtable)

* **Intset**: 정수형 데이터만 존재하고 개수가 적을 때 사용된다. 구조체 내 정수 배열 형태이며 조회 시 이진 탐색(intsetFind)을 사용하여 O(log N)의 성능을 낸다. 데이터가 커지거나 문자열이 들어오면 자동으로 Hashtable로 전환된다.
* **Hashtable**: 모든 형태의 데이터를 수용하며 O(1)의 탁월한 탐색 성능을 보장한다. 다만 포인터 기반 연결 구조체이므로 메타데이터 오버헤드로 인한 메모리 소모가 크다.

#### 4-4. Hash (Listpack & Hashtable)

* Redis 7.0 이상부터는 기존 Ziplist의 단점(연쇄 업데이트 발생 등)을 보완한 **Listpack**을 소규모 데이터 인코딩에 사용한다. 크기가 커지면 **Hashtable**로 전환된다.
* **Listpack**: Field와 Value를 연속된 공간에 콤팩트하게 나열하여 메모리 단편화를 줄이고 연쇄 업데이트(Cascade Update) 비용을 제거했다. 중간 조회는 O(N)이다.
* **Hashtable**: 해시 함수를 통해 Field 단위에 O(1)로 즉시 접근할 수 있으나 포인터 낭비 및 공간 비용이 크다. 단일 String Key를 여러 개 생성하는 것보다 하나의 Hash 구조에 Field로 묶어 관리하는 것이 메모리 제어 측면에서 유리할 수 있다.

#### 4-5. Sorted Set (Skiplist + Hashtable)

* Score 기반 정렬과 빠른 순위 연산을 동시에 처리하는 정교한 자료구조다. 데이터가 커지면 **Hashtable과 Skiplist를 혼용**한다.
* **Hashtable의 역할**: Member를 Key로 삼아 Score를 O(1) 만에 빠르게 조회한다.
* **Skiplist의 역할**: Score를 기준으로 정렬을 유지하며, 순위 조회(ZRANK)는 O(log N), 범위 조회(ZRANGE)는 O(log N + M)의 뛰어난 효율을 제공한다.
* 멤버 변경이나 삭제 시 두 자료구조에 동시에 반영하여 데이터 일관성을 유지한다.

***

### 5. 데이터 영속화(Persistence) 전략

Redis의 휘발성을 보완하고 재시작 시 데이터 유실 및 서비스 중단을 방지하기 위한 필수 장치다.

#### 5-1. RDB Snapshot

* 특정 시점의 메모리 상태를 통째로 덤프하여 dump.rdb 바이너리 파일로 저장한다.
* **Copy On Write (COW) 방식**: 싱글 스레드 오버헤드를 피하기 위해 자식 프로세스를 fork()한다. 부모와 자식 프로세스는 읽기 시 동일한 메모리 페이지를 공유하지만, 부모 프로세스에 쓰기(수정)가 발생하면 해당 페이지의 복사본을 만들어 최신 데이터를 반영한다.
* **주의점**: fork 및 스냅샷 도중 쓰기 연산이 많아지면 메모리 사용량이 급증하여 OOM(Out Of Memory) 위험이 발생하므로 적절한 저장 주기 튜닝이 필요하다.
* **Global Dict 참조**: RDB 스냅샷은 OS 레벨의 4KB 페이지 목록을 그대로 덤프하는 것이 아니라, Redis 내부의 핵심 저장 공간인 Global Dict를 순회하며 실제 Key-Value 자료구조의 원본 데이터를 참조하여 바이너리로 저장한다.

#### 5-2. AOF (Append Only File)

* 데이터 변경을 유발하는 모든 쓰기 명령을 RESP 프로토콜 텍스트 형태로 로그 파일에 순차 기록한다.
* fsync 옵션(always, everysec, no)에 따라 디스크 flush 주기를 제어하며, 주천 규칙인 everysec 사용 시 1초 단위로 안전하게 저장되므로 데이터 유실이 거의 없다. 대신 텍스트 기반이라 파일 용량이 크고 디스크 I/O 비용이 지속 발생한다.

#### 5-3. AOF 재작성 (AOF Rewrite)

* 동일 Key에 대한 무수한 수정 명령을 최종 상태 하나로 압축하는 과정이다. (예: SET a 1 \~ 4의 연속 연산을 최종 상태인 SET a 4 하나로 통합)
* 이 역시 COW 방식으로 자식 프로세스를 생성하여 현재 메모리 스냅샷 기반의 새 AOF 파일을 만들고, 그동안 부모에게 발생한 추가 쓰기는 덮어쓰기 전 버퍼에 모았다가 최종 병합(rename)하는 방식으로 유연하게 처리된다.

#### 5-4. 하이브리드(Hybrid) 영속성 전략 (Redis 4.0 이상)

* RDB의 빠른 복구속도와 AOF의 데이터 무결성 장점을 결합한 최적의 전략이다.
* 영속성 관리는 단일 AOF 파일로 수행하되, **AOF 재작성(Rewrite) 시점의 데이터는 RDB 바이너리 스냅샷 형태로 파일 앞부분에 기록**하고, 재작성 중에 발생한 추가 명령들만 텍스트 로그 형태로 파일 뒷부분에 추가한다.
* 이로 인해 복구 시 앞부분의 바이너리는 초고속으로 메모리에 로드하고, 뒷부분의 최신 변경점만 Replay하여 손실 없이 가장 최적화된 속도로 데이터를 복구할 수 있다.

***

### Redis를 아키텍칭에 활용하기 위한 설계적 접근 방법

Redis는 하드웨어와 OS, 그리고 소프트웨어 아키텍처가 철저하게 최적화된 시스템이다. 성능을 극한으로 끌어올린 만큼, 개발자가 무분별하게 무거운 명령(예: 대규모 keys 연산)을 수행하거나, 단일 Key에 거대한 파편화 데이터를 밀어 넣으면 Redis 고유의 설계 사상이 무너져 심각한 레이턴시 배가 현상이 발생한다.

실무 트래픽 규모에 맞춰 적절한 고가용성 구조(Sentinel / Cluster)를 판단하고, 비즈니스 특성에 맞는 자료구조(Embedded String 활용 유도, Hash 분할 맵핑 등) 및 하이브리드 영속성 설정을 조화롭게 튜닝하는 아키텍처적 안목이 핵심이다.

기본적으로 매우 빠른 연산이 설계 사상이라는 점에 유의하여, 하나의 요청에 대해 대규모 데이터를 한번에 받는 것이 아니라, Pipeline을 사용한다던지 혹은 MGET과 같이 대량 조회를 하되, 이를 끊어서 chunk처럼 조회하는 등의 효율적인 조회 방식을 채택하는 설계 방향을 적용해야 한다.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://leehyokyun.gitbook.io/openbook/4-2.-redis-bottleneck-1-redis.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
