Redis는 메모리 기반 데이터베이스로, 빠른 응답속도와 높은 처리량 덕분에 캐싱, 세션 관리, 실시간 분석 등 다양한 분야에서 널리 사용됩니다. 하지만 Redis는 메모리 상에서 데이터를 관리하기 때문에, 메모리 사용량이 급증하면 성능 저하나 OOM(Out Of Memory) 문제가 발생할 수 있습니다. 따라서 Redis를 효율적으로 운영하기 위해서는 메모리 최적화와 관리 전략이 필수적입니다.
1. Redis 메모리 구조 이해
Redis 메모리 최적화의 첫 단계는 메모리 구조를 이해하는 것입니다. Redis는 모든 데이터를 메모리에 올리고 관리하므로, 데이터를 어떻게 저장하는지가 곧 메모리 사용량에 직결됩니다.
1.1 데이터 구조별 메모리 사용
Redis는 다양한 자료구조를 지원합니다. 각 자료구조는 내부적으로 메모리를 다르게 사용합니다.
자료구조 내부 구조 메모리 특징
String | Simple dynamic string (SDS) | 가장 기본, 값이 작을수록 메모리 효율적 |
List | Linked list 또는 Ziplist | 길이가 짧으면 Ziplist 사용, 길면 Linked list 사용 |
Set | Hash table 또는 Intset | 숫자만 있을 때 Intset 사용, 메모리 효율적 |
Hash | Hash table 또는 Ziplist | 필드 수가 적으면 Ziplist 사용, 메모리 절약 가능 |
Sorted Set | Skiplist + Hash table | 정렬 유지 필요, 메모리 소모가 큼 |
핵심 포인트: 작은 데이터를 저장할 때는 Ziplist/Intset 구조를 활용하면 메모리를 아낄 수 있습니다.
1.2 Redis 메모리 내부 구조
Redis는 데이터 자체뿐만 아니라, 내부 관리용 메모리도 사용합니다. 주요 구성은 다음과 같습니다.
- 데이터 객체(Object)
Redis는 모든 데이터를 객체로 감싸서 관리합니다. - struct redisObject { void *ptr; // 실제 데이터 포인터 int type; // 자료형 (String, List, Set 등) int encoding; // 내부 인코딩 (raw, int, ziplist 등) int refcount; // 참조 카운트 int lru; // LRU 사용 시 최근 접근 시간 }
- Hash Table 및 슬롯 관리
Key-Value는 해시 테이블을 사용합니다. 해시 테이블 크기가 커지면 rehash가 발생하며, 이 과정에서 일시적으로 메모리 사용량이 증가할 수 있습니다. - RDB/AOF 백업 메모리
snapshot(RDB)나 Append Only File(AOF) 적용 시, 백업 과정에서 메모리가 추가로 필요합니다. - 결론: 데이터 구조 선택과 백업 전략은 메모리 사용량에 직접적인 영향을 줍니다.
2. 메모리 최적화 전략
Redis 메모리 최적화는 크게 데이터 구조 최적화, 메모리 정책 설정, 데이터 관리 전략으로 나눌 수 있습니다.
2.1 데이터 구조 최적화
- String
- 값이 작은 경우에는 raw string 그대로 사용.
- 큰 문자열은 **압축(zlib 등)**을 고려.
// Java Redis 예시 (Jedis) jedis.set("user:1:name", "Alice"); jedis.set("user:2:bio", compress("Long bio text ..."));
- Hash
- 많은 필드가 있는 Hash라면 ziplist(단, 필드 수 512 이하, 값 64바이트 이하) 사용 가능.
hash-max-ziplist-entries 512 hash-max-ziplist-value 64
- List
- 길이가 짧은 리스트는 Ziplist 사용
list-max-ziplist-entries 512 list-max-ziplist-value 64
Tip: 작은 데이터일수록 ziplist/Intset을 활용하면 메모리를 크게 절약할 수 있습니다.
2.2 메모리 정책 설정
Redis는 maxmemory 설정을 통해 메모리 상한을 지정하고, eviction 정책으로 메모리 부족 시 데이터를 제거합니다.
# 최대 메모리 2GB 제한
maxmemory 2gb
# 정책 예시
# noeviction: 메모리 초과 시 쓰기 실패
# allkeys-lru: 가장 오래 사용하지 않은 키 제거
# volatile-lru: TTL이 있는 키 중 오래된 키 제거
maxmemory-policy allkeys-lru
주요 Eviction 정책
정책 설명
noeviction | 메모리 초과 시 쓰기 실패 |
allkeys-lru | 모든 키 대상으로 LRU 제거 |
volatile-lru | TTL이 있는 키 대상으로 LRU 제거 |
allkeys-random | 모든 키 중 랜덤 제거 |
volatile-ttl | TTL이 가장 짧은 키 제거 |
LRU 정책을 사용하면 자주 사용되는 데이터는 유지, 사용하지 않는 데이터는 자동 제거 가능.
2.3 데이터 만료 전략
Redis는 TTL(Time-To-Live)을 설정하여 자동으로 데이터를 제거할 수 있습니다.
// Java Jedis 예시
jedis.setex("session:123", 3600, "userData"); // 1시간 후 만료
- 장점: 불필요한 데이터 자동 제거, 메모리 유지 관리 용이
- 주의: TTL이 없는 키는 Eviction 정책에 의해서만 제거됨
2.4 압축과 직렬화
Redis에 큰 데이터를 저장할 때는 압축과 직렬화를 통해 메모리를 절약할 수 있습니다.
// Jackson + Snappy 예시
ObjectMapper mapper = new ObjectMapper();
byte[] compressed = Snappy.compress(mapper.writeValueAsBytes(obj));
jedis.set("key:compressed".getBytes(), compressed);
주의: 압축/직렬화는 CPU 사용량을 증가시킬 수 있으므로, 메모리 절약과 성능 간 균형 필요.
3. 실시간 메모리 모니터링
Redis는 운영 중 메모리를 모니터링하고 문제를 조기 발견할 수 있는 다양한 도구를 제공합니다.
3.1 INFO 명령
redis-cli info memory
- used_memory: 실제 사용 중인 메모리
- used_memory_rss: 운영체제에서 할당한 메모리
- maxmemory: 설정한 최대 메모리
- mem_fragmentation_ratio: 메모리 단편화 비율
Fragmentation Ratio가 1.5 이상이면 메모리 단편화 주의
3.2 keyspace 확인
redis-cli info keyspace
- 각 DB별 키 수, 만료 키 수 확인 가능
3.3 실시간 모니터링 도구
- Redis Monitor: 모든 명령 추적
- Redis Slowlog: 느린 명령 확인
- Grafana + Prometheus: 시각화 및 알람 설정
4. 운영 환경 최적화 전략
4.1 클러스터링 및 샤딩
Redis 단일 인스턴스는 메모리 한계가 있습니다.
Redis Cluster를 통해 데이터를 샤딩하면 메모리 확장성이 향상됩니다.
Shard 1 -> keys a-m
Shard 2 -> keys n-z
4.2 데이터 TTL 정책 통합
- 세션 데이터: 1~2시간
- 캐시 데이터: 서비스 특성에 따라 5~30분
- 로그/이벤트 데이터: 1~7일
TTL 정책을 일관성 있게 관리하면 메모리 폭주를 방지할 수 있습니다.
4.3 메모리 정리 주기
- MEMORY PURGE 명령으로 필요시 단편화 제거
- Redis 재시작 후 RDB/AOF 로딩 시 메모리 최적화
5. 실무 적용 예시
5.1 사용자 세션 캐싱
// 사용자 세션 저장
String sessionKey = "session:" + userId;
jedis.setex(sessionKey, 3600, serialize(session));
- TTL 1시간으로 만료 관리
- 자주 사용되는 세션만 Redis에 유지, 오래된 세션 자동 제거
5.2 인기 상품 캐시
String productKey = "product:123";
if (!jedis.exists(productKey)) {
Product product = productRepository.findById(123);
jedis.setex(productKey, 300, serialize(product));
}
- TTL 5분 적용
- allkeys-lru 정책으로 메모리 초과 시 오래된 캐시 제거
6. 마무리
Redis 메모리 최적화는 단순히 설정 몇 개 바꾸는 수준이 아니에요. 데이터 구조 선택 → 메모리 정책 → 모니터링 → 운영 전략이 서로 맞물린 하나의 생태계를 관리하는 과정이라고 보는 게 맞습니다.
실제 운영하다 보면 자연스럽게 “이 데이터를 정말 Redis에 올려야 할까?”라는 고민이 생기고, 구조와 정책을 조합해서 메모리를 아끼는 과정은 퍼즐을 맞추는 재미처럼 느껴집니다.
처음에는 maxmemory와 LRU 정책 정도만 설정했는데, 구조를 조금씩 바꾸고 TTL 전략을 적용하며, 필요하면 클러스터링까지 고려하다 보면 어느 순간 Redis 운영이 훨씬 편해지고 자신감도 붙는 느낌을 받을 수 있습니다.
물론 지나치게 최적화하면 코드가 복잡해지고 유지보수가 어려워지기 때문에, 적절한 균형을 찾는 것이 가장 중요합니다.
Redis를 제대로 쓰려면 ‘속도’뿐만 아니라 ‘메모리 관리’까지 같이 고민해야 한다는 사실을 다시 한 번 느낄 수 있는 경험이었습니다.
'Redis' 카테고리의 다른 글
[Redis] Redis 분산락(Distributed Lock)을 이용한 재고관리 구현 - 2 (0) | 2025.01.17 |
---|---|
[Redis] Redis 분산락(Distributed Lock)을 이용한 재고관리 구현 - 1 (0) | 2025.01.15 |
[Redis] Redis 클러스터(Redis Cluster) 구성하기 (0) | 2025.01.09 |