최고의 성능을 위한 Redis 메모리 관리 마스터하기

메모리 관리 기술을 숙달하여 최고의 Redis 성능을 잠금 해제하세요. 이 포괄적인 가이드는 Redis의 메모리 사용량을 이해하고, `INFO memory` 및 `MEMORY USAGE`로 모니터링하며, 데이터 구조를 최적화하는 것과 같은 중요한 측면을 다룹니다. 능동적 조각 모음으로 단편화를 방지하고, 효율적인 제거 정책(`maxmemory`, `allkeys-lru`)을 구성하며, 보다 원활한 운영을 위해 지연 해제 기능을 활용하는 방법을 배우십시오. 이러한 실행 가능한 전략을 구현하여 Redis 처리량을 향상시키고, 지연 시간을 줄이며, 안정적이고 고성능의 캐싱 및 데이터 저장을 보장하십시오.

33 조회수

Redis 메모리 관리 마스터링으로 최고 성능 달성하기

Redis는 인메모리 작동 덕분에 놀랍도록 빠른 성능으로 유명합니다. 하지만 최고 성능을 진정으로 발휘하고 유지하려면 Redis 메모리 관리를 마스터하는 것이 단순히 유익한 것을 넘어 필수적입니다. 부적절한 메모리 처리는 지연 시간 증가, 처리량 감소부터 서버 충돌 및 데이터 손실까지 다양한 문제를 야기할 수 있습니다. 이 글에서는 할당 전략, 단편화 이해, 데이터 구조 최적화, 제거 정책 구성 등 Redis 메모리 관리의 중요 측면을 깊이 있게 다루며, 이를 통해 가능한 최고의 안정성과 효율성을 달성하도록 돕겠습니다.

Redis의 효과적인 메모리 관리는 단순히 RAM이 충분한 것 이상입니다. Redis가 데이터를 저장하는 방식, 시스템 리소스를 소비하는 방식, 다양한 구성 설정이 메모리 사용량에 어떤 영향을 미치는지에 대한 깊은 이해가 필요합니다. Redis 인스턴스의 메모리 사용량을 최적화하면 응답성을 크게 향상시키고, 운영 수명을 연장하며, 다양한 부하에서도 애플리케이션에 안정적으로 서비스를 계속 제공하도록 보장할 수 있습니다. Redis 배포를 미세 조정하는 데 도움이 되는 실용적인 기법과 모범 사례를 살펴보겠습니다.

Redis 메모리 사용량 이해

Redis는 모든 데이터를 저장하기 위해 시스템 메모리를 사용합니다. 키-값 쌍을 SET할 때 Redis는 키 문자열과 값 모두에 대해 메모리를 할당하며, 내부 데이터 구조를 위한 약간의 오버헤드도 포함됩니다. 메모리 사용량의 다양한 구성 요소를 이해하는 것이 효과적인 관리의 첫걸음입니다:

  • 데이터 메모리: 실제 데이터(키, 값, 키를 값에 매핑하기 위한 사전과 같은 내부 데이터 구조)로 소비되는 메모리입니다. 크기는 키와 값의 개수 및 크기, 선택한 데이터 구조(문자열, 해시, 리스트, 세트, 정렬된 세트)에 따라 달라집니다.
  • 오버헤드 메모리: Redis는 각 키에 대해 일부 오버헤드(예: 포인터, LRU/LFU 추적을 위한 메타데이터, 만료 정보)를 추가합니다. 작은 데이터 구조는 메모리를 줄이기 위해 특별히 인코딩될 수 있습니다(예: ziplist, intset). 하지만 더 큰 구조는 일반적이고 메모리 집약적인 표현을 사용합니다.
  • 버퍼 메모리: Redis는 클라이언트 출력 버퍼, 복제 백로그 버퍼, AOF 버퍼를 사용합니다. 크거나 느린 클라이언트 또는 바쁜 복제 설정은 상당한 버퍼 메모리를 소비할 수 있습니다.
  • 포크 메모리: Redis가 RDB 스냅샷 저장 또는 AOF 파일 다시 쓰기와 같은 백그라운드 작업을 수행할 때 자식 프로세스를 fork합니다. 이 자식 프로세스는 처음에는 복사 후 쓰기(CoW)를 통해 부모와 메모리를 공유합니다. 그러나 fork 이후 부모 프로세스가 데이터 세트에 쓰는 모든 작업은 페이지가 복제되어 전체 메모리 사용량을 증가시킵니다.

Redis 메모리 모니터링

문제가 확대되기 전에 잠재적인 문제를 식별하려면 Redis 메모리를 정기적으로 모니터링하는 것이 중요합니다. 이를 위한 주요 도구는 MEMORY USAGE와 함께 INFO memory 명령입니다.

INFO memory 명령

redis-cli INFO memory

INFO memory의 주요 메트릭:

  • used_memory: Redis가 할당기(jemalloc, glibc 등)를 사용하여 할당한 총 바이트 수입니다. 이는 데이터, 내부 데이터 구조, 임시 버퍼에서 사용된 메모리의 합계입니다.
  • used_memory_human: 사람이 읽을 수 있는 형식의 used_memory입니다.
  • used_memory_rss: Resident Set Size(RSS)는 운영 체제가 보고한 Redis 프로세스가 소비한 메모리 양입니다. 여기에는 Redis 자체 할당, 운영 체제 메모리 관리에서 사용된 메모리, 공유 라이브러리, 그리고 아직 OS에 반환되지 않은 단편화된 메모리가 포함될 수 있습니다.
  • mem_fragmentation_ratio: 이는 used_memory_rss / used_memory입니다. 이상적인 비율은 1.0보다 약간 높습니다(예: 1.03-1.05). 1.0보다 훨씬 높은 비율(예: 1.5 이상)은 높은 메모리 단편화를 나타냅니다. 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는 작은 집합 데이터 유형을 컴팩트한 방식으로 인코딩하여 메모리를 절약하려고 시도합니다(예: 해시/리스트의 경우 ziplist, 정수 세트의 경우 intset). 이러한 컴팩트한 인코딩은 메모리 효율성이 매우 높지만, 더 큰 구조에 대해서는 덜 효율적이 되며 일반 해시 테이블이나 스킵 리스트로 전환됩니다.
    • : 개별 집합 멤버를 작게 유지하십시오. 해시의 경우, 몇 개의 큰 필드보다는 많은 작은 필드를 선호하십시오.
    • 구성: redis.confhash-max-ziplist-entries, hash-max-ziplist-value, list-max-ziplist-entries, list-max-ziplist-value, set-max-intset-entries, zset-max-ziplist-entries/zset-max-ziplist-value 지시문은 Redis가 컴팩트 인코딩에서 일반 데이터 구조로 전환되는 시점을 제어합니다. 이러한 값을 신중하게 조정하십시오. 너무 큰 값은 액세스 패턴에 대한 성능을 저하시킬 수 있고, 너무 작은 값은 메모리를 증가시킬 수 있습니다.

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를 가진 HASHuser:1을 사용하십시오.

4. 메모리 단편화 관리

메모리 단편화는 메모리 할당기가 필요한 정확한 크기의 연속적인 메모리 블록을 찾지 못하여 사용되지 않는 간격이 발생하는 현상입니다. 이로 인해 used_memory_rssused_memory보다 훨씬 높을 수 있습니다.

  • 원인: 다양한 크기의 키의 빈번한 삽입 및 삭제, 특히 메모리 할당기가 오랫동안 실행된 후 발생합니다.
  • 감지: 1.0보다 훨씬 높은 mem_fragmentation_ratio(예: 1.5-2.0)는 높은 단편화를 나타냅니다.
  • 해결책:
    • Redis 4.0+ 활성 단편화 제거: Redis는 재시작 없이 메모리를 활성적으로 단편화할 수 있습니다. redis.conf에서 activedefrag yes로 활성화하고 active-defrag-max-scan-timeactive-defrag-cycle-min/max를 구성하십시오. 이를 통해 Redis는 데이터를 이동하여 메모리를 압축할 수 있습니다.
    • Redis 재시작: 메모리 단편화를 제거하는 가장 간단하지만 중단이 발생하는 방법은 Redis 서버를 재시작하는 것입니다. 이렇게 하면 모든 메모리가 OS로 다시 해제되고 할당기가 새로 시작됩니다. 영구 인스턴스의 경우, 재시작 전에 RDB 스냅샷 또는 AOF 파일을 저장했는지 확인하십시오.
# 활성 단편화 제거를 위한 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.confmaxmemory 지시문은 이 한계를 설정하고, maxmemory-policy는 제거 전략을 지정합니다.

maxmemory 2gb # 최대 메모리를 2기가바이트로 설정
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-policynoeviction으로 설정되어 있고 maxmemory에 도달하면 쓰기 작업이 실패하여 애플리케이션 오류가 발생합니다.

지속성과 메모리 오버헤드

Redis 지속성 메커니즘(RDB 및 AOF)도 메모리와 상호 작용합니다:

  • RDB 스냅샷: Redis가 RDB 파일을 저장할 때 자식 프로세스를 fork합니다. 스냅샷 프로세스 중에 부모 프로세스가 Redis 데이터 세트에 쓰는 모든 작업은 복사 후 쓰기(CoW)로 인해 메모리 페이지가 복제될 수 있습니다. 이는 특히 빈번한 RDB 저장으로 인해 바쁜 인스턴스에서 일시적으로 메모리 사용량을 두 배로 늘릴 수 있습니다.
  • AOF 다시 쓰기: 마찬가지로, AOF 파일이 다시 쓰여질 때(예: BGREWRITEAOF) fork가 발생하여 일시적인 메모리 복제가 발생합니다. AOF 버퍼 자체도 메모리를 소비합니다.

: 가능하다면 오프피크 시간에 RDB 저장 및 AOF 다시 쓰기를 예약하거나, CoW 오버헤드를 처리할 충분한 여유 RAM을 서버에 확보하십시오.

지연 해제 (Lazy Freeing)

Redis 4.0은 큰 키를 삭제하거나 데이터베이스를 플러시할 때 서버를 차단하는 것을 방지하기 위해 지연 해제(비차단 삭제)를 도입했습니다. Redis는 동기적으로 메모리를 회수하는 대신, 백그라운드 스레드에 메모리 해제 작업을 맡길 수 있습니다.

  • lazyfree-lazy-eviction yes: 제거 중에 메모리를 비동기적으로 해제합니다.
  • lazyfree-lazy-expire yes: 키 만료 시 메모리를 비동기적으로 해제합니다.
  • lazyfree-lazy-server-del yes: 큰 키/데이터베이스에서 DEL, RENAME, FLUSHALL, FLUSHDB가 호출될 때 메모리를 비동기적으로 해제합니다.

권장 사항: 동기적 메모리 회수로 인한 잠재적인 지연 시간 급증을 줄이기 위해 바쁜 인스턴스에 대해 지연 해제를 활성화하십시오.

파이프라이닝 및 메모리

파이프라이닝은 주로 네트워크 최적화 기법이지만, 명령 처리를 더 효율적으로 만들어 간접적으로 메모리 성능에 영향을 줄 수 있습니다. 여러 명령을 단일 왕복으로 Redis에 전송함으로써 네트워크 지연 시간과 클라이언트 및 서버 측의 명령당 CPU 오버헤드를 줄입니다. 이를 통해 Redis는 더 많은 작업을 초당 처리할 수 있으며, 이는 결국 클라이언트 버퍼에서 더 높은 메모리 사용량을 축적하거나 시간이 지남에 따라 메모리 할당기에 부담을 주는 느린 처리로 이어질 수 있습니다.

파이프라이닝은 메모리 할당을 직접 관리하지는 않지만, 효율성 향상을 통해 Redis는 명령 오버헤드로 인한 리소스 낭비를 줄이면서 더 높은 처리량을 처리할 수 있게 하며, 메모리 할당기가 부하 상태에서 더 원활하게 작동하도록 합니다.

결론

Redis 메모리 관리를 마스터하는 것은 지속적인 과정이며, 애플리케이션의 성능과 안정성에 상당한 영향을 미칩니다. Redis가 메모리를 사용하는 방식, 메모리 사용량의 신중한 모니터링, 데이터 구조 최적화, 단편화의 효과적인 관리, 제거 정책의 현명한 구성 등을 이해함으로써 Redis 인스턴스가 최고 효율로 실행되도록 보장할 수 있습니다.

항상 명확한 모니터링으로 시작한 다음, 데이터 모델링 모범 사례, 적절한 구성 설정, 지속성 및 제거 전략에 대한 사려 깊은 고려 사항을 조합하여 적용하십시오. 애플리케이션과 데이터가 발전함에 따라 메모리 사용 패턴을 정기적으로 검토하여 견고하고 고성능의 Redis 환경을 유지하십시오.