Redis 메모리 관리 마스터하기: 최고 성능을 위한 핵심 전략
메모리 관리 기술을 마스터하여 Redis의 최고 성능을 끌어내세요. 이 포괄적인 가이드는 Redis 메모리 사용량 이해, `INFO memory` 및 `MEMORY USAGE`를 통한 모니터링, 데이터 구조 최적화 등 중요한 측면을 다룹니다. 능동적 조각 모음으로 단편화를 해결하고, 효율적인 제거 정책(`maxmemory`, `allkeys-lru`)을 구성하며, 지연 해제를 활용하여 더 원활한 작업을 수행하는 방법을 배우세요. 이러한 실행 가능한 전략을 구현하여 Redis 처리량을 향상시키고, 지연 시간을 줄이며, 안정적이고 고성능인 캐싱 및 데이터 저장을 보장하세요.
Redis 메모리 관리 마스터하기: 최고 성능을 위한 핵심 전략
Redis는 데이터를 메모리에 저장하기 때문에 빠르지만, 동시에 메모리 관련 실수가 빠르게 드러납니다. 만료 시간이 없는 캐시는 쓰기 작업이 실패할 때까지 계속 커집니다. 몇 개의 거대한 키는 삭제될 때 지연 시간 스파이크를 유발합니다. 백그라운드 저장은 copy-on-write로 인해 예상보다 더 많은 메모리를 필요로 할 수 있습니다. 느린 클라이언트는 문제의 일부가 될 만큼 큰 출력 버퍼를 생성할 수 있습니다.
좋은 Redis 메모리 관리는 단순히 "RAM을 더 구매하는 것"이 아닙니다. 무엇이 저장되어 있는지, 얼마나 오래 유지되어야 하는지, 메모리 한계에서 어떤 일이 발생하는지, 그리고 키가 클 때 어떤 작업이 서버를 차단할 수 있는지 아는 것입니다.
Redis 메모리 사용량 이해
Redis는 시스템 메모리를 사용하여 모든 데이터를 저장합니다. SET 명령으로 키-값 쌍을 설정하면 Redis는 키 문자열과 값 모두에 대해 메모리를 할당하고, 내부 데이터 구조에 대한 약간의 오버헤드도 추가합니다. 메모리 사용량의 다양한 구성 요소를 이해하는 것이 효과적인 관리의 첫 단계입니다:
- 데이터 메모리: 실제 데이터(키, 값 및 키를 값에 매핑하는 딕셔너리와 같은 내부 데이터 구조)가 소비하는 메모리입니다. 크기는 키와 값의 수와 크기, 그리고 선택한 데이터 구조(문자열, 해시, 리스트, 셋, 정렬된 셋)에 따라 달라집니다.
- 오버헤드 메모리: Redis는 각 키에 대해 포인터, 메타데이터, 만료 정보, 제거 관련 데이터 등의 오버헤드를 추가합니다. 작은 집계 구조는 Redis 버전과 데이터 유형에 따라 listpack 또는 intset과 같은 압축 인코딩을 사용할 수 있는 반면, 더 큰 구조는 더 일반적인 표현을 사용합니다.
- 버퍼 메모리: Redis는 클라이언트 출력 버퍼, 복제 백로그 버퍼, AOF 버퍼를 사용합니다. 크거나 느린 클라이언트, 또는 바쁜 복제 설정은 상당한 버퍼 메모리를 소비할 수 있습니다.
- 포크 메모리: Redis가 RDB 스냅샷 저장 또는 AOF 파일 재작성과 같은 백그라운드 작업을 수행할 때, 자식 프로세스를
fork합니다. 이 자식 프로세스는 처음에 copy-on-write(CoW)를 통해 부모와 메모리를 공유합니다. 그러나fork이후 부모 프로세스가 데이터셋에 쓰기를 수행하면 페이지가 복제되어 총 메모리 사용량이 증가합니다.
Redis 메모리 모니터링
Redis 메모리를 정기적으로 모니터링하면 문제가 커지기 전에 식별하는 데 중요합니다. 이를 위한 주요 도구는 INFO memory 명령과 MEMORY USAGE입니다.
INFO memory 명령
redis-cli INFO memory
INFO memory의 주요 지표:
used_memory: Redis가 할당자(jemalloc, glibc 등)를 사용하여 할당한 총 바이트 수입니다. 데이터, 내부 데이터 구조 및 임시 버퍼에서 사용하는 메모리의 합계입니다.used_memory_human: 사람이 읽기 쉬운 형식의used_memory입니다.used_memory_rss: 상주 세트 크기(RSS)로, 운영 체제가 보고하는 Redis 프로세스가 소비하는 메모리 양입니다. 여기에는 Redis 자체 할당, 운영 체제 메모리 관리에서 사용하는 메모리, 공유 라이브러리, 그리고 아직 OS로 반환되지 않은 잠재적으로 단편화된 메모리가 포함됩니다.mem_fragmentation_ratio: 대략used_memory_rss / used_memory입니다. 1.0 이상의 값은 정상입니다. 훨씬 더 높은 값은 단편화, 할당자 동작 또는 OS로 반환되지 않은 RSS를 의미할 수 있습니다. 1.0 미만의 값은 메모리가 스왑 아웃되거나 측정 타이밍 효과를 나타낼 수 있으므로 조사할 가치가 있는 경고 신호입니다.allocator_frag_bytes: 메모리 할당자가 보고하는 단편화 바이트 수입니다.lazyfree_pending_objects: 비동기적으로 해제되기를 기다리는 객체 수입니다.
MEMORY USAGE 명령
개별 키의 메모리 사용량을 검사하려면:
redis-cli MEMORY USAGE mykey
redis-cli MEMORY USAGE myhashkey SAMPLES 0 # 집계에 대한 추정
이 명령은 주어진 키에 대한 예상 메모리 사용량을 제공하여 크거나 비효율적으로 저장된 데이터 포인트를 정확히 찾아내는 데 도움을 줍니다.
주요 메모리 최적화 전략
Redis에서 메모리를 최적화하려면 올바른 데이터 유형 선택부터 단편화 관리까지 여러 사전 예방적 단계가 필요합니다.
1. 데이터 구조 최적화
Redis는 각각 고유한 메모리 동작을 가진 여러 데이터 구조를 제공합니다. 올바른 구조는 애플리케이션이 데이터를 읽고 쓰는 방식에 따라 달라집니다.
- 문자열: 가장 간단하지만 큰 문자열에 주의하세요. 매우 큰 문자열(MB 단위)에
SET또는GET을 사용하면 네트워크 및 메모리 전송 오버헤드로 인해 성능에 영향을 줄 수 있습니다. - 해시, 리스트, 셋, 정렬된 셋(집계): Redis는 작은 집계 데이터 유형을 컴팩트하게 인코딩할 수 있습니다. 정확한 인코딩 이름과 임계값은 Redis 버전에 따라 다르므로,
redis.conf및OBJECT ENCODING출력을 확인하여 이전ziplist용어가 모든 곳에 적용된다고 가정하지 마세요.- 팁: 개별 집계 멤버를 작게 유지하세요. 해시의 경우, 몇 개의 큰 필드보다 많은 작은 필드를 선호하세요.
- 구성: Redis 버전마다 다릅니다. 이전 버전은
*-ziplist-*설정을 사용했지만, 최신 버전은 일부 구조에 대해 일반적으로*-listpack-*설정을 사용합니다. 실제 데이터로 신중하게 튜닝하고 테스트하세요. 압축 인코딩은 메모리를 절약하지만 특정 액세스 패턴에서는 CPU 비용이 발생할 수 있습니다.
2. 키 디자인 모범 사례
값이 일반적으로 더 많은 메모리를 소비하지만, 키 이름을 최적화하는 것도 중요합니다:
- 짧고 설명적인 키: 더 짧은 키는 특히 수백만 개의 키가 있을 때 메모리를 절약합니다. 그러나 극단적인 간결성을 위해 명확성을 희생하지 마세요. 설명적이면서도 간결한 키 이름을 목표로 하세요.
- 나쁨:
user:1000:profile:details:email - 좋음:
user:1000:email(이메일만 저장하는 경우)
- 나쁨:
- 접두사 사용: 조직 목적으로 일관된 접두사(예:
user:,product:)를 사용하세요. 이는 메모리 영향이 최소화되지만 관리를 용이하게 합니다.
3. 오버헤드 최소화
모든 키와 값에는 약간의 내부 오버헤드가 있습니다. 특히 작은 키의 수를 줄이는 것이 효과적일 수 있습니다.
- 여러 문자열 대신 해시: 엔터티에 대해 많은 관련 필드가 있는 경우 여러
STRING키 대신 단일HASH에 저장하세요. 이렇게 하면 최상위 키의 수와 관련 오버헤드가 줄어듭니다.- 예:
user:1:name,user:1:email,user:1:age대신 필드name,email,age를 가진HASH키user:1을 사용하세요.
- 예:
4. 메모리 단편화 관리
메모리 단편화는 메모리 할당자가 필요한 정확한 크기의 연속 메모리 블록을 찾을 수 없어 사용되지 않는 간격이 발생할 때 발생합니다. 이로 인해 used_memory_rss가 used_memory보다 훨씬 높아질 수 있습니다.
- 원인: 특히 메모리 할당자가 오랫동안 실행된 후 다양한 크기의 키를 자주 삽입하고 삭제하는 경우.
- 감지:
mem_fragmentation_ratio가 1.0(예: 1.5-2.0)보다 훨씬 높으면 높은 단편화를 나타냅니다. - 해결책:
- Redis 4.0+ 능동적 조각 모음: Redis는 재시작 없이 메모리를 능동적으로 조각 모음할 수 있습니다.
redis.conf에서activedefrag yes를 활성화하고active-defrag-max-scan-time및active-defrag-cycle-min/max를 구성하세요. 이를 통해 Redis가 데이터를 이동시켜 메모리를 압축할 수 있습니다. - Redis 재시작: 메모리를 조각 모음하는 가장 간단하면서도 중단적인 방법은 Redis 서버를 재시작하는 것입니다. 이렇게 하면 모든 메모리가 OS로 반환되고 할당자가 새로 시작됩니다. 영구 인스턴스의 경우 재시작하기 전에 RDB 스냅샷 또는 AOF 파일이 저장되었는지 확인하세요.
- Redis 4.0+ 능동적 조각 모음: Redis는 재시작 없이 메모리를 능동적으로 조각 모음할 수 있습니다.
# 능동적 조각 모음을 위한 redis.conf 설정
activedefrag yes
active-defrag-ignore-bytes 100mb # 단편화가 100MB 미만이면 조각 모음하지 않음
active-defrag-threshold-lower 10 # 단편화 비율이 10%를 초과하면 조각 모음 시작
active-defrag-threshold-upper 100 # 단편화 비율이 100%를 초과하면 조각 모음 중지
active-defrag-cycle-min 1 # 조각 모음을 위한 최소 CPU 노력 (1-100%)
active-defrag-cycle-max 20 # 조각 모음을 위한 최대 CPU 노력 (1-100%)
제거 정책: maxmemory 관리
Redis를 캐시로 사용할 때 메모리가 미리 정의된 한계에 도달하면 어떻게 되는지 정의하는 것이 중요합니다. redis.conf의 maxmemory 지시문이 이 한계를 설정하고, maxmemory-policy가 제거 전략을 결정합니다.
maxmemory 2gb # 최대 메모리를 2GB로 설정
maxmemory-policy allkeys-lru # 모든 키 중에서 가장 최근에 사용되지 않은 키 제거
일반적인 maxmemory-policy 옵션:
noeviction: (기본값)maxmemory에 도달하면 새 쓰기가 차단됩니다. 읽기는 계속 작동합니다. 디버깅에는 좋지만 일반적으로 프로덕션 캐시에는 적합하지 않습니다.allkeys-lru: 모든 키스페이스(만료 여부와 관계없는 키)에서 가장 최근에 사용되지 않은(LRU) 키를 제거합니다.volatile-lru: 만료가 설정된 키만 LRU 키를 제거합니다.allkeys-lfu: 모든 키스페이스에서 가장 자주 사용되지 않은(LFU) 키를 제거합니다.volatile-lfu: 만료가 설정된 키만 LFU 키를 제거합니다.allkeys-random: 모든 키스페이스에서 무작위로 키를 제거합니다.volatile-random: 만료가 설정된 키만 무작위로 키를 제거합니다.volatile-ttl: 만료가 설정된 키만 TTL(Time To Live)이 가장 짧은 키를 제거합니다.
올바른 정책 선택:
- 일반 캐싱의 경우, 데이터에 최신성 또는 빈도 중 무엇이 더 유용한 지표인지에 따라
allkeys-lru또는allkeys-lfu가 좋은 선택인 경우가 많습니다. - 주로 세션 관리 또는 명시적 만료가 있는 객체에 Redis를 사용하는 경우
volatile-lru또는volatile-ttl이 더 적합할 수 있습니다.
경고: maxmemory-policy가 noeviction으로 설정되고 maxmemory에 도달하면 쓰기 작업이 실패하여 애플리케이션 오류가 발생합니다.
maxmemory 값 선택
maxmemory를 서버의 총 RAM과 동일하게 설정하지 마세요. 운영 체제, Redis 프로세스 오버헤드, 클라이언트 버퍼, 복제 백로그, 지속성 copy-on-write, 모니터링 에이전트 및 긴급 SSH 액세스를 위한 공간을 남겨두세요.
캐시 전용 Redis 인스턴스의 경우, 간단한 시작점은 물리적 RAM보다 여유 있는 마진으로 maxmemory를 설정한 다음, 피크 부하 및 백그라운드 지속성 동안 실제 지표를 관찰하는 것입니다. 지속성이 많은 인스턴스의 경우 RDB 스냅샷 및 AOF 재작성이 일시적으로 메모리 압력을 증가시킬 수 있으므로 더 많은 헤드룸을 남겨두세요.
공유 호스트에서 제한이 전혀 없는 구성은 위험합니다. Redis는 OS 및 다른 서비스와 경쟁하여 커널 OOM 킬러가 무엇을 죽일지 결정할 때까지 경쟁할 수 있습니다. 명확한 maxmemory와 신중한 제거 정책이 추론하기 더 쉽습니다.
큰 키는 지연 시간 문제이기도 합니다
메모리 관리는 총 바이트만이 아닙니다. 하나의 거대한 키는 무해해 보이는 작업에 해를 끼칠 수 있습니다.
예:
redis-cli MEMORY USAGE huge:hash
redis-cli HLEN huge:hash
redis-cli LLEN queue:events
DEL로 매우 큰 키를 삭제하면 Redis가 메모리를 해제하는 동안 이벤트 루프가 차단될 수 있습니다. Redis 버전이 지원하는 경우 큰 키에는 UNLINK를 선호하세요:
redis-cli UNLINK huge:hash
UNLINK는 키를 분리하고 메모리를 비동기적으로 해제합니다. 키는 키스페이스에서 빠르게 사라지고, 비용이 많이 드는 해제 작업은 백그라운드에서 발생합니다.
큰 컬렉션의 경우, 크기를 제한하여 설계하세요. 스트림과 리스트를 자릅니다. 액세스 패턴과 일치하는 경우 테넌트, 시간 버킷 또는 객체 유형별로 큰 해시를 분할하세요. 백만 개의 필드를 가진 해시는 일부 최상위 키 오버헤드를 절약할 수 있지만, 마이그레이션, 만료, 검사 또는 삭제가 어려워질 수 있습니다.
지속성과 메모리 오버헤드
Redis 지속성 메커니즘(RDB 및 AOF)도 메모리와 상호 작용합니다:
- RDB 스냅샷: Redis가 RDB 파일을 저장할 때 자식 프로세스를
fork합니다. 스냅샷 프로세스 중에 부모가 Redis 데이터셋에 쓰기를 수행하면 copy-on-write(CoW)로 인해 메모리 페이지가 복제됩니다. 이는 특히 빈번한 RDB 저장이 있는 바쁜 인스턴스에서 일시적으로 메모리 사용량을 두 배로 늘릴 수 있습니다. - AOF 재작성: 유사하게, AOF 파일이 재작성될 때(예:
BGREWRITEAOF)fork가 발생하여 일시적인 메모리 중복이 발생합니다. AOF 버퍼 자체도 메모리를 소비합니다.
팁: 가능하면 RDB 저장 및 AOF 재작성을 비피크 시간에 예약하거나, 서버에 CoW 오버헤드를 처리할 수 있는 충분한 여유 RAM이 있는지 확인하세요.
지연 해제
Redis 4.0은 큰 키를 삭제하거나 데이터베이스를 플러시할 때 서버가 차단되는 것을 방지하기 위해 지연 해제(비차단 삭제)를 도입했습니다. Redis는 메모리를 동기적으로 회수하는 대신 메모리 해제 작업을 백그라운드 스레드에 넣을 수 있습니다.
lazyfree-lazy-eviction yes: 제거 중에 메모리를 비동기적으로 해제합니다.lazyfree-lazy-expire yes: 키가 만료될 때 메모리를 비동기적으로 해제합니다.lazyfree-lazy-server-del yes: 지원되는 경로에서 서버 측 삭제를 위해 메모리를 비동기적으로 해제합니다.lazyfree-lazy-user-del yes: 사용자가 발행한DEL이 지원되는 Redis 버전에서UNLINK처럼 동작하도록 합니다.
바쁜 인스턴스에서 지연 해제를 신중하게 활성화하여 동기적 메모리 회수로 인한 지연 시간 스파이크를 줄이세요. 그런 다음 lazyfree_pending_objects를 관찰하세요. 높게 유지되면 백그라운드 해제 작업이 따라잡지 못하고 있는 것입니다.
클라이언트 버퍼와 파이프라이닝
파이프라이닝은 주로 네트워크 최적화 기술이지만, 명령 처리를 더 효율적으로 만들어 간접적으로 메모리 성능에 영향을 줄 수 있습니다. 단일 왕복으로 여러 명령을 Redis에 보내면 클라이언트와 서버 측 모두에서 네트워크 지연 시간과 명령당 CPU 오버헤드가 줄어듭니다. 이를 통해 Redis는 큰 명령 큐를 축적하지 않고도 초당 더 많은 작업을 처리할 수 있으며, 그렇지 않으면 클라이언트 버퍼에서 더 높은 메모리 사용량을 초래하거나 시간이 지남에 따라 메모리 할당자에 부담을 주는 느린 처리가 발생할 수 있습니다.
파이프라이닝은 처리량을 향상시킬 수 있지만, 무제한 파이프라인은 클라이언트 출력 버퍼를 증가시킬 수 있습니다. 거대한 파이프라인을 보내고 응답을 느리게 읽는 클라이언트는 Redis가 많은 양의 보류 중인 출력을 보유하게 만들 수 있습니다.
INFO clients에서 클라이언트 버퍼 지표를 관찰하고 일반, 복제본 및 pub/sub 클라이언트에 대해 적절한 제한을 구성하세요:
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit replica 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
이는 보편적인 권장 사항이 아닌 예시 스타일의 기본값입니다. 핵심은 뒤처질 수 있는 클라이언트에 대해 무제한 성장을 피하는 것입니다.
유용한 메모리 검토 루틴
Redis 인스턴스가 예상보다 더 많은 메모리를 사용하기 시작하면, 광범위한 것부터 구체적인 것까지 작업하세요:
INFO memory에서used_memory, RSS, 단편화, 할당자 통계 및 보류 중인 지연 해제를 확인하세요.INFO keyspace에서 하나의 데이터베이스 또는 키 패밀리가 증가하고 있는지 확인하세요.MEMORY USAGE,SCAN및 유형별 길이 명령으로 큰 키를 샘플링하세요.- 캐시와 같은 키에 TTL이 있는지 확인하세요.
- 최근 배포에서 새 키 이름, 더 긴 값 또는 누락된 만료를 검토하세요.
- 지속성 타이밍 및 포크 관련 메모리 압력을 확인하세요.
- 트래픽 스파이크 중 메모리가 급증하면 클라이언트 버퍼를 검토하세요.
예를 들어, 메모리 증가가 새 기능 릴리스를 따른다면 만료되지 않은 새 키를 찾으세요:
redis-cli --scan --pattern 'feature-x:*' | head
redis-cli TTL feature-x:example
TTL이 -1이면 키에 만료가 없는 것입니다. 이는 영구 데이터에 대해 올바를 수 있습니다. 캐시 데이터에 대해서는 일반적으로 잘못된 것입니다.
Redis 메모리 문제는 인스턴스가 가득 차기 전에 수정하는 것이 가장 쉽습니다. 의도적인 maxmemory를 설정하고, 인스턴스의 역할과 일치하는 제거 정책을 선택하고, 캐시 키를 TTL로 유지하고, 과도하게 큰 키를 피하고, 포크 및 버퍼를 위한 헤드룸을 남겨두세요. 그런 다음 주요 데이터 형태 변경 후 실제 메모리 지표를 검토하세요. Redis의 메모리 동작이 지루할 때 Redis는 빠르게 유지됩니다.