> 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-3-redis.md).

# 4-2. Redis 활용 및 병목 현상 (Bottleneck) #3 : Redis 병목 현상

Redis 병목 현상에 대하여

### 📌 Redis의 병목 현상 \[[1차 내용](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-3-TDD%EA%B8%B0%EB%B0%98%EC%9D%98-Redis-Patterns-trouble-Shootings-1) + [2차 내용](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-Redis%EC%9D%98-Model%EA%B3%BC-%EC%8B%A4%EB%AC%B4-%EC%A0%81%EC%9A%A9%EC%9D%84-%EC%9C%84%ED%95%9C-Patterns-2)으로 자세하게 기술]

### Cache Penetration (캐시 관통 현상) 및 방어 전략

데이터베이스(원본 datasource)에 존재하지 않는 데이터에 대해 악의적이거나 비정상적인 요청이 지속적으로 대규모로 발생하여, Cache Miss와 DB Call이 최하단까지 지속적으로 반복되고 결국 DB 체계 전체에 극심한 부담과 트래픽 병목을 주는 현상이다.

**Null Object Pattern (널 오브젝트 패턴)**&#x20;

Cache 관통을 방지할 수 있는 가장 1차적이고 직관적인 방안이다. 데이터베이스(datasource)를 조회했음에도 존재하지 않는 Null 데이터를 단순한 빈 값이나 에러로 리턴하는 것이 아니라, 'Null Object'라는 특정한 객체 형태로 표현하여 캐시에 적재하고 이를 반환하도록 설계한다. 이를 통해 단순 Null 처리 시 발생할 수 있는 프로그램 내의 예외 위험성을 감소시키고 연산을 훨씬 유연하게 처리할 수 있도록 돕는다. 다만, 이 패턴을 사용할 경우 사전에 Null Object로 캐싱 생성되었던 key 데이터가 추후 실제로 데이터베이스에 새롭게 생성되었을 때, 기존 캐시의 만료 및 정합성 처리(Cache Eviction) 메커니즘이 매우 복잡해진다는 단점이 존재한다.

**Bloom Filter (블룸 필터)의 도입 목적**&#x20;

Null Object 패턴을 무분별하게 사용할 때 발생하는 불필요한 메모리 낭비와 잦은 cache miss를 근본적으로 방지하기 위한 고도화된 방안이다. 특정 데이터의 존재 유무를 데이터베이스에 직접 접근하지 않고도 Redis Bitmap 아키텍처와 확률론적 알고리즘에 기반하여 극도로 빠르고 가볍게 판단하기 위한 도구이다. 없는 데이터로 판단될 경우에는 아예 데이터베이스 호출 자체를 차단(No Service Call)함으로써, 무분별한 Cache miss와 하위 데이터소스의 병목을 완벽히 방어하는 것을 목적으로 삼는다. 다만, 확률 기반 알고리즘 특성상 실제로는 데이터가 없는데도 있다고 판단할 수 있는 False Positive(오차율)가 존재한다.

**Bloom Filter의 정량적 활용 및 메모리 계산**&#x20;

블룸 필터에서 효율적인 방어벽을 구축하기 위해 필요한 총 비트 수(m)는 다루고자 하는 데이터의 규모인 원소 개수(n)와 허용할 오차율(p)의 수학적 인과 관계에 따라 엄격하게 결정된다. 관련 공식은 다음과 같다. m = - (n \* ln p) / (ln 2)^2 이 식을 바탕으로 정량적으로 계산해 보면, 오차율을 0.01% 기준으로 설정할 때 데이터 개당 필요한 비트 수(m)는 19.2bit가 되며, 100억 개의 데이터를 방어하기 위해서는 약 24GB의 메모리가 요구된다. 만약 오차율 기준을 0.001%로 더욱 엄격하게 낮춘다면 개당 필요한 비트 수(m)는 24bit로 늘어나며, 100억 개 기준 약 30GB의 메모리가 필요하게 된다. 이는 실제 해시 키(Hash Key) 문자열 자체를 가공하여 메모리에 통째로 저장하는 비용을 완전히 제거한 것이므로, 오차율을 무려 1/10 수준으로 대폭 감소시키는데 추가 메모리가 단 6GB밖에 소요되지 않아 대규모 데이터 환경에서 매우 메모리 효율이 좋은 전략임을 증명한다.

**Bloom Filter Split (분할 블룸 필터) 전략**&#x20;

단일 대용량 블룸 필터를 적용할 때의 비효율을 극복하기 위해, 블룸 필터를 N개로 물리적·논리적으로 분할(Split)하여 일괄 초기화 리스크를 방지하고 분할 초기화를 가능케 하는 메모리 효율성 관점의 고도화 전략이다. 각 Split 구역마다 논리적으로 완벽히 분할된 bitmap을 생성하되, 물리적으로는 하나의 거대한 필터처럼 통합 동작하게 구성한다. 이는 Bloom Filter Shard 체계와 유사하게, 각 분할된 블룸 필터 슬롯(Slot)이 자신에게 부여된 고유한 비트 인덱스 및 Split 번호를 독립적으로 할당받아 연산하는 방식이다. 물리적으로 분리된 다수의 블룸 필터를 유기적으로 활용함으로써 특정 영역에 쏠리는 데이터와 트랜잭션을 골고루 분산 저장 및 처리할 수 있어 시스템 전반의 고가용성과 메모리 효율성을 극대화한다.

**Bloom Filter SubFilter (서브 필터) 전략**&#x20;

앞서 언급한 Split 및 Shard 블룸 필터 전략 역시 최초 선언한 단일 블룸 필터의 '고정 비트 수'라는 프레임 안에서 움직이는 전략이므로, 데이터가 임계치 이상으로 영구 지속 유입될 경우 근본적인 한계에 봉착한다. 이를 해결하기 위해 블룸 필터 자체의 크기를 수동으로 키우는(Scale up) 방식을 취하지 않고, 유입된 데이터 규모가 일정 임계치를 초과할 때마다 완전히 새로운 독립된 'Sub Filter'를 동적으로 추가 생성하여 이연 연결하는 전략을 채택한다.&#x20;

완전 조회를 수행할 때의 시간 복잡도는 Sub Filter 개수가 늘어나도 여전히 O(1)의 속도를 유지하므로 매우 강력하지만, Sub Filter가 너무 무분별하게 많아진다면 성능 저하가 올 수 있으므로 시스템 내부에서 최대 허용 개수를 지정하여 적절하게 상한선을 관리해야 한다. 이를 안정적으로 운영하기 위해서는 필터에 기록된 전체 데이터 수의 규모를 주기적으로 모니터링해야 하며, 전체 bit array 영역 중 1로 활성화되어 세팅된 비트의 비중을 샘플링하여 주기적으로 조율해야 한다. 또한 전체 인입 요청 수 대비 발생하는 false positive의 실제 횟수를 철저히 추적 관리해야 하며, 새로운 sub filter가 생성되어 추가될 때마다 전체적인 결합 오차율은 점진적으로 감소하도록 수학적 설계 보정을 적용해야 한다.

### Cache Stampede (캐시 스탬피드 현상) 및 방어 전략

대규모 트래픽 환경에서 원본 데이터베이스(datasource)에 분명히 정상적인 데이터가 엄연히 존재하고 있음에도 불구하고, 캐시에 적재되어 있던 대용량 핵심 데이터의 만료 시간(TTL)이 도래하여 캐시 영역에서 일시에 삭제(만료)되는 순간 발생한다. 이 찰나의 순간에 수많은 분산 애플리케이션 서버에서 대량의 Cache Miss가 동시다발적으로 터지며 동일한 데이터에 대한 대대적인 DB 조회가 데이터베이스로 한꺼번에 몰려, 하부 레벨의 I/O 시스템과 DB 전체를 일시에 마비시키는 중증 장애 현상이다.

**Jitter (지터 알고리즘)**&#x20;

각 핵심 데이터별 캐싱 만료 시간(TTL)을 일괄적으로 고정하지 않고, 비즈니스 로직 단에서 만료 시간에 무작위 미세 편차(Jitter)를 동적으로 가산하여 부여하는 전략이다. 이를 통해 수많은 캐시 키들이 동일한 시점에 동시·일괄적으로 대량 만료되는 현상을 원천 차단하고 분산 만료를 유도함으로써, 데이터베이스로 향하는 순간적인 트래픽 병목을 방지하고 부하를 부드럽게 분산시킨다. 통계성 데이터나 대고객 정보성 데이터와 같이 만료 시간의 변동성이 어느 정도 유연하게 허용되는 데이터 군에 적용할 때 가장 탁월한 효과를 발휘한다.

**PER (Probabilistic Early Recomputation, 확률적 조기 갱신)**&#x20;

캐싱 데이터의 만료 시간이 실제로 완전히 다 가버리기 전에, 인입되는 특정 요청 흐름 속에서 캐시의 만료 잔여 시간을 계산하고 수학적 확률식을 대입하여 캐싱을 사전에 조기 갱신(Recomputation)하는 전략이다. 특정 확률 공식을 적용하여 캐싱 만료 확률 수치가 시스템이 정한 특정 임계 기준 이상으로 커지게 되면, 유입된 수많은 트래픽 중 단 하나의 특정 요청 스레드에 캐싱 갱신 책임을 위임하여 백그라운드 형태로 조기에 캐시를 최신화한다. 이 방식을 통해 캐시가 완전히 깨진 상태에서 요청을 받는 일을 완전히 없앨 수 있으나, 극심한 대규모 데이터 상황에서는 조기 갱신을 판단하고 연산하는 시점 자체에도 동시다발적인 연산 병목이 유발될 여지가 있으므로 세밀한 튜닝이 동반되어야 한다.

**Request Collapsing (요청 붕괴/단일화)와 분산락 활용**&#x20;

동일한(중복) 타겟 데이터에 대해 다발적인 Cache Miss가 터지며 데이터베이스 조회가 몰릴 때, 모든 서버의 요청을 데이터베이스로 그대로 통과시키지 않고 최초 인입된 단 1개의 요청에 대해서만 원본 데이터베이스 접근 권한을 부여하는 전략이다. 이를 위해 Redis 기반의 분산락(Distributed Lock) 아키텍처를 도입하여, 최초로 락을 획득한 1개의 서비스 요청 스레드만 원본 데이터베이스(datasource)에 접근하여 데이터를 조회하고 캐시에 다시 적재하도록 통제하며, 나머지 후행 요청 스레드들은 락을 대기하거나 캐싱이 완료될 때까지 기다리도록 요청을 단일화한다. 이때 시스템 내부적으로 Logical TTL(논리적 만료 시간)과 Physical TTL(물리적 만료 시간)을 철저히 이중 결합하여 활용하는 것이 핵심이다.&#x20;

분산락 획득에 실패한 후행 스레드들은 무작정 Blocking 되어 대기하며 시스템 자원을 소모할 필요 없이, 물리적으로는 아직 삭제되지 않고 남아있는 이전의 캐싱 값(Logical TTL은 만료되었으나 Physical TTL은 남아있는 데이터)을 그대로 즉시 반환받아 서비스하도록 처리한다. 이를 통해 데이터베이스의 동시 다발적 조회 갱신 실패 현상을 완벽히 방어하며, 후행 요청들은 주기적인 폴링(Polling)을 통해 완벽히 갱신이 끝났는지 여부를 부드럽게 확인 및 동기화할 수 있다.&#x20;

다만 대규모 트래픽 하에서는 결국 성극(성능의 병목 및 편차)이 발생할 가능성이 여전히 존재하므로 최후의 방어선으로 아예 데이터베이스에 도달하는 트래픽의 절대적인 최대 한계 규모를 고정하는 Rate Limit(처리율 제한) 아키텍처를 도입해야 한다. 무작정 획일적인 limit을 단일 진입점에 적용하는 구조가 아니라, 웹 서버, 애플리케이션, 캐시, DB 등 각 계층별로 수용 가능한 트래픽 수용력을 정밀 고려한 Multi Layer Rate Limiting(다계층 처리율 제한) 전략이 필수적이다. 비록 최종 병목 구간은 하부 DB가 되겠지만, Caching 레이어의 고성능 확장성을 온전히 유지하고 보호하기 위해 병목 구간 앞단에서 멀티 토큰(Multi Token) 전략 체계를 적용하여 의도적인 요청 거부 및 Fail 처리를 수행함으로써 전체 아키텍처의 완전한 붕괴를 영리하게 방어해 내야 한다.

**Write Through (라이트스루 캐싱)** 데이터의 대대적인 조회 시점에 Cache Miss를 맞이하고 나서야 데이터베이스에서 값을 퍼 올리는 일반적인 Look Aside 캐싱 방식의 한계를 탈피하는 캐시 극대화 전략이다.&#x20;

데이터가 생성, 수정, 삭제되는 모든 쓰기(Write) 시점에 애플리케이션이 원본 데이터베이스와 캐시 저장소에 데이터를 동시에, 혹은 순차적으로 즉시 적재하는 아키텍처를 취한다. 데이터를 쓸 때마다 캐시를 매번 갱신하므로 전체적인 쓰기 성능 오버헤드와 디스크 및 인메모리 쓰기 부하가 다소 증가할 수는 있으나, 이후 발생하는 모든 읽기(Read) 요청 시점에서는 완벽한 100% Cache Hit를 보장할 수 있다.&#x20;

특히 Redis의 Sorted Set 자료구조와 같이 데이터의 노출 빈도와 실시간 가중치 스코어에 따라 실시간 정렬 및 핫 데이터 추출이 지속적으로 일어나는 비즈니스 레이어에서는 Write Through 방식이 트래픽 병목을 사전에 파괴하는 데 가장 효과적인 솔루션이 된다. 노출 빈도가 극도로 높은 핵심 트래픽 경로상에서 이 방식을 채택하면 완벽한 cache hit율을 기반으로 하부 bottleneck을 전방위 방어할 수 있으며, 무조건적으로 시스템의 모든 무분별한 데이터를 다 캐싱하는 구조가 아니라 캐싱 영역의 데이터가 부족하거나 만료될 때에만 원본에서 적절하게 보충 추출해오는 유기적인 결합 제어가 핵심 메커니즘이 된다.

### Hot Key (핫키 현상) 및 아키텍처 분산 전략

전체 분산 아키텍처에서 발생하는 데이터베이스 및 캐시 시스템의 성능 병목과 지연 현상은 모든 데이터에 골고루 유입되는 트래픽 때문이 아니다. 실제로는 전체 데이터 중 단 10\~15%의 극히 일부 시스템에 분포된 인기 있는 핵심 '인기 데이터(Hot Key)'에 전 세계적인 대규모 트래픽이 집중적으로 몰리기 때문에 발생한다.

**Application Sharding & Replication (애플리케이션 샤딩 및 레플리케이션)**&#x20;

애플리케이션(Application) 비즈니스 레이어 단에서 핫키로 분류된 특정 핵심 자원의 샤드 키(Shard Key) 사본들을 물리적으로 분산되어 있는 다수의 독립된 모든 샤드 및 레플리카 노드 전체에 강제적으로 복제 및 분산 저장(Replication)해두는 분산 아키텍처 전략을 실행한다.&#x20;

애플리케이션이 동일한 핫키 데이터를 읽어올 때 단 한 곳의 물리 노드를 찌르지 않고 분산된 N개의 샤드 노드 전체로 요청을 고르게 분산하여 던질 수 있게 되므로, 트래픽 부하가 정확히 구성된 샤드 및 레플리카 개수(N)만큼 완벽하게 분할 감소되어 분산 처리 성능이 비약적으로 향상된다.


---

# 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-3-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.
