Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | |||||
| 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 10 | 11 | 12 | 13 | 14 | 15 | 16 |
| 17 | 18 | 19 | 20 | 21 | 22 | 23 |
| 24 | 25 | 26 | 27 | 28 | 29 | 30 |
| 31 |
Tags
- 응원가
- 앱 개발
- flutter
- Firebase
- 뮤직플레이어
- linear classifier
- 해커톤
- loss function
- 고려대학교
- 국방오픈소스아카데미
- optimization
- KUsic
- ModalBottomSheet
- 다익스트라
- 백준
- 더보기창
- 앱 출시
- text recognition
- 이미지 분류
- 알고리즘
- 고려대학교 응원가
- image_picker
- google_ml_kit
- OSAM
- image classification
- 앱
- 고연전
- Dijkstra
- CS231n
- K-nearest neighbor
Archives
- Today
- Total
영주머니의 개발주머니
Redis를 효과적으로 사용하는 6가지 활용 사례 본문
Redis
- 인메모리 저장소
- 모든 데이터를 메모리에 저장하여 매우 빠른 읽기/쓰기 성능 제공한다.
- 싱글 스레드
- 다양한 자료구조 지원
- String, List, Set, Hash, Sorted Set, HyperLogLog 등
Redis 주요 활용 사례
1. Cache

- 일반적인 캐시 구조는 3개의 layer로 이뤄진다.
- Application Cache
- 어플리케이션 메모리 내부에 존재한다.
- 주로 유저 프로필 같이 자주 접근되는 데이터를 저장하는 해시맵이다.
- 캐시 크기가 작으며 앱이 재시작되면 데이터가 사라진다.
- Second Level Cache
- 서버마다 로컬로 존재하는 캐시이다.
- LRU, LFU, TTL과 같은 정책을 설정하여 캐시에서 자동으로 데이터를 제거한다.
- Distributed Cache
- 애플리케이션 서버와 분리된 별도의 서버에 존재한다.
- 여러 서버에 분산(샤딩)할 수 있어 확장성이 높다.
- Redis가 여기에 해당한다.
- 파레토 원칙에 따르면 20%의 데이터가 전체 데이터 접근의 80%를 차지한다. 자주 접근되는 20%의 데이터를 Redis에 캐싱하면 대부분의 요청에 대한 성능 향상이 가능하다.
- Redis를 사용하면 성능이 향상되지만, 캐시 데이터의 일관성(coherence) 관리가 복잡해진다. 즉, 데이터베이스와 캐시에 저장된 데이터가 서로 불일치할 가능성이 존재한다.
- 이를 관리하기 위해 Lazy Loading, Write-through, Cache aside, Read aside 같은 캐싱 전략을 사용할 수 있다.
2. Session Store
- 각 서버에 로컬로 세션을 저장하는 대신 Redis에 세션 정보를 저장하고 모든 서버가 같은 Redis Session 저장소를 바라보도록 한다.
- Redis를 사용하면 로드 밸런싱을 하는 경우에 sticky session을 사용하지 않아도 된다.
- Sticky session: 로드 밸런서의 설정을 통해 사용자의 요청이 처음 세션을 저장한 서버로만 가도록 설정하는 방법


- Session 저장소의 경우 거의 모든 인가 요청 시에 사용되기 때문에 저장소 접근이 잦다. 따라서 디스크 I/O가 필요한 RDB보다 인메모리 기반의 Redis를 사용하는 것이 유리하다.
- Session 정보는 Redis에 JSON 혹은 비슷한 형식으로 직렬화되어 저장된다.

3. Leaderboard
- Redis의 Sorted Set (ZSet) 자료구조를 사용하면 빠르고 확장 가능한 리더보드를 만들 수 있다.
- ZSet은 점수를 기준으로 자동 정렬되는 집합(set)이다.
- ZSet 연산은 내부적으로 Skip List + Hash Table로 구현되어 있어서 O(log(N))에 연산이 가능하다.
- ZSet의 특징
- member는 유일하다
- 각 member는 관련된 score를 가진다.
- member는 점수를 기준으로 정렬된다.
- add, remove, query range, rank 등과 같은 연산을 지원한다.
- Redis ZSet을 이용한 Leaderboard 구현 예시
- ZADD stepcounts 1000 Bob → Bob을 점수 1000으로 stepcounts ZSet에 추가한다.
- ZINCRBY stepcounts 15 Bob → Bob의 점수를 15만큼 증가시킨다.
- ZREVRANGE stepcounts 0 2 → stepcounts ZSet에서 점수 기준 내림차순으로 상위 3명을 조회한다.

4. Message Queue
- Redis는 가벼운 pub-sub 및 queuing 기능을 제공한다.
- 일반적으로 사용되는 Kafka 같은 추가적인 미들웨어 없이 비동기 메시징을 구현할 수 있다는 장점이 있다.
- Redis로 message queue를 구현하는 방식은 크게 List, Stream, Pub/Sub이 있다.
4.1. List
- Redis List는 FIFO 방식의 큐로 사용된다.
- LPUSH: 데이터를 왼쪽 끝에 추가
- RPOP: 데이터를 오른쪽 끝에서 꺼내옴

- 한계
- Consumer가 새로운 메시지를 확인하기 위해 Redis를 주기적으로 Polling 해야 함 → CPU 낭비 발생
- BRPOP (Blocking Right Pop)을 사용하면 새 메시지가 올 때까지 효율적으로 대기 가능
- 메시지를 소비하면 리스트에서 제거됨
- Kafka처럼 백업하려면 BRPOPPUSH 명령어를 사용해 소비한 메시지를 다른 리스트로 옮겨야 함
- List는 병렬 처리를 위한 Consumer Group을 지원하지 않아 Consumer가 느릴 경우 메시지가 메모리에 쌓임
- Consumer가 새로운 메시지를 확인하기 위해 Redis를 주기적으로 Polling 해야 함 → CPU 낭비 발생
4.2. Stream
- 메시지의 영속성, 순서 보장, Consumer 그룹 지원
- XADD mystream * field value 형태로 새 메시지를 추가
- *을 사용하면 Redis가 자동으로 고유 ID를 생성함 → 메시지 순서 추적 및 중복 방지 가능
- XGROUP으로 Consumer 그룹 생성
- XREADGROUP으로 여러 Consumer가 하나의 스트림을 병렬로 읽을 수 있음
- 소비된 메시지는 즉시 제거되지 않고 Pending Entries List에 임시 저장됨
- Consumer가 XACK 명령어로 확인 응답(ACK)를 보내야 해당 메시지가 완전히 제거됨

4.3. Pub/Sub
- SUBSCRIBE: 특정 채널을 구독하여, 해당 채널에 메시지가 발행되면 수신할 수 있게 함
- UNSUBSCRIBE: 더 이상 채널을 구독하지 않도록 해제함
- PUBLISH: 지정한 채널에 메시지를 발행하여, 해당 채널을 구독 중인 모든 Consumer에게 메시지를 전달함

- Redis Pub/Sub은 기본적인 fire-and-forget 메시징을 지원하지만 메시지의 영속성, 전달 보장, 순서 보장과 같은 신뢰성 매커니즘은 부족함
- Fire and forget: 메시지를 보내고 나서 도달 여부나 응답을 확인하지 않는 방식
5. Website Analytics
- 웹에서 PV(페이지뷰), UV(고유 방문자 수)를 계산할 때 단순히 HashSet을 사용해서 각 유저의 ID를 저장하면 메모리 사용이 사용자 수에 비례해서 선형적으로 증가한다.
- Redis의 HyperLogLog는 고정된 적은 메모리로 대량의 고유값 개수(Cardinality)를 근사 추정할 수 있다.
- 정확도는 약간 떨어지지만, 웹 스케일에서도 고정된 메모리를 소모하여 효율적으로 PV/UV 분석이 가능하다.
- Redis를 사용한 website analytics 예시
- PFADD: 사용자가 페이지 방문 시 HyperLogLog에 user ID 추가
- PFCOUNT: 해당 키로 고유 방문자 수 추정

6. Flash Sale
- Flash Sale: 짧은 시간 동안 특정 상품을 한정 수량, 특별 할인 가격에 판매하는 이벤트
- Flash Sale에서는 재고 업데이트를 위한 동시 요청이 많아진다.
- Redis의 키-값 저장소를 이용해 분산 락(distritubted lock)을 구현하면 락을 획득한 인스턴스만 재고를 수정할 수 있고, 작업 후 락을 해제해야 한다.

- 인스턴스 A는 inventorylock 키가 0일 때만 1로 설정하며 락을 획득한다.
- Redis는 단일 스레드로 요청을 순차 처리하므로 먼저 온 A가 락을 선점한다.
- 이후 B는 락이 이미 1로 설정돼 있어서 락 획득에 실패하고 요청도 실패한다.
- SETNX 명령을 사용하여 원자적으로 잠금을 획득할 수 있다.
SETNX inventorylock 1
// Update the inventory
DEL inventorylock
- 다만, 이러한 방식에는 다음과 같은 잠재적 문제가 존재한다.
- A가 inventorylock을 delete하기 전에 죽으면 락이 영원히 해제되지 않는다.
- A가 가진 락을 B가 실수로 지울 수 있다.
- 해결 방법
- 락에 만료 시간을 설정해 자동 삭제되도록 한다.
- 고유 식별자를 락 값으로 설정해 락 소유자만 삭제 가능하게 한다.
SET inventorylock instanceA NX PX 10000 //Update the inventory- instanceA: 락의 소유자 (고유 식별자)
- NX: 키가 없을 때만 설정 (이미 있으면 실패)
- PX 10000: 10초 (=10,000ms) 후 자동 만료 설정 (TTL)
- Redis 분산 락은 대부분의 경우 잘 동작하지만 high throughput 환경에서는 한계가 있다.
- 초당 수백만 건의 락 요청이 몰리면 락 획득/해제 비용이 병목이 될 수 있다.
- 공정성 보장이 없어서 어떤 클라이언트는 락을 계속 못 잡고 starving 할 수 있다.
- 초대형 서비스에서는 Redis보다 ZooKeeper와 같은 전용 락 서비스가 더 적합할 수 있다. 다만, 구현이 더 복잡하다.
참고글
https://blog.bytebytego.com/p/the-6-most-impactful-ways-redis-is
Comments