메모리 관리

1. 메모리 관리 아키텍처

  • 레디스 메모리 구조
    • 데이터
    • 데이터 구조 오버헤드
    • 클라이언트 쿼리 버퍼
    • 클라이언트 출력 버퍼
    • AOF 버퍼
    • AOF 재작성 버퍼
    • 레플리케이션 백로그
    • 레디스 함수
    • 이페머럴 스크립트
    • 메모리 단편화
INFO Memory 출력 결과 해석
  • used_memory: 레디스가 jemalloc 같은 메모리 할당자를 사용해 할당한 메모리 크기
    • 해당 메모리는 클라이언트 출력 버퍼 등도 포함되어 데이터만 사용하는 것이 아니다
  • maxmemory: 레디스가 사용할 수 있는 최대 메모리 크기
  • used_memory가 maxmemory 를 넘으면 maxmemory-policy 지시자로 설정한 값을 따르도록 동작
    • mem_not_counted_for_evict 는 AOF 버퍼, RDB 스냅샷을 생성할 때 CoW 에 의해 사용된 메모리양의 합으로
    • used_memory - mem_not_counted_for_evict <= maxmemory
  • used_memory_overhead 는 서버에 할당된 오버헤드를 관리하는 데 사용되는 내부 데이터 구조
    • used_memory_dataset = used_memory - used_memory_overhead (데이터 세트의 메모리 크기)
  • mem_cluster_links 값과 send-buffer-allocated 값
    • 레디스 클러스터의 클러스터 버스에서 캐시 노드가 다른 노드와의 연결에 사용하는 메모리 정보
  • mem_fragmentation_ratio: 메모리 단편화 비율
    • 레디스가 현재 사용중인 것으로 인식하는 메모리 사용량을 RSS 값을 나눈 값
    • mem_fragmentation_ratio = used_memory_rss / used_memory
  • 메모리 단편화 문제 대처 방법
    • 단편화율이 1.5 이상이면 단편화가 심각하고 성능 문제가 발생할 수 있다.
    • 반대로 1.0 보다 낮으면 스왑이 발생하고 있을 가능성이 있다.
    • 단편화 발생 시 대처 방법
      • 레디스 재시작 (RDB, AOF 파일 불러오기를 통해 데이터 복원)
      • 동적 단편화
      • 메모리 스왑 제한
      • 메모리 할당자 변경
      • MEMORY PURGE 명령어 실행
        • 메모리 사용량이 높은 최대 사용량 시간에도 jemalloc 메모리를 즉시 해제하지 않는 상황에서 메모리를 재사용하고자 할 떄 유용
클라이언트 출력 버퍼(COB Client Output Buffer)
  • COB는 서버로부터 데이터를 즉시 읽지 못하는 클라이언트의 연결을 강제로 끊는 용도로 사용
  • 서버는 클라이언트마다 COB를 갖추고 처리한 결과를 클라이언트에 보내지 않고 COB 저장, COB에 저장된 정보가 클라이언트에 한번에 전송
    • 버퍼 크기가 커질 수 있는 상황은 다루는 “데이터 크기가 클 때”, “스토리지가 느릴 때”, “네트워크 환경이 좋지 않을 때”
  • 클라이언트 출력 버퍼 종류
    • pubsub 유형(redis pub/sub 기능)은 버퍼 크기에 제한을 두어 클라이언트의 출력 버퍼가 초과되면 연결을 끊을 수 있다
    • replica 유형은 마스터에서 버퍼링하는 영역
    • normal 유형은 일반적인 클라이언트의 연결에 사용되는 버퍼 영역
    • client-output-buffer-limit 지시자는 레디스에서 클라이언트 출력 버퍼의 크기 제한을 설정할 때 사용

2. 키 만료

  • 최대한 캐시 내의 데이터를 재사용하고 DB 접근 횟수를 줄여 효율적으로 운용할 필요가 있다.
  • 현재 데이터와 크게 다르지 않다면 TTL을 설정해서 적절하게 관리할 필요가 있다.
만료 방법
  • TTL이 만료되면 대상 키가 메모리에서 삭제되는 것이 아닌 메모리에 남게 되는 데 이 데이터를 삭제하는 방법 의미
  • 수동적 방법
    • 접근할 때 해당 키의 TTL을 확인하여 만약 만료되었다면 해당 키를 메모리에서 삭제
  • 능동적 방법
    • 레디스가 매 초마다 10번, 임의로 20개의 키를 샘플링하여 만료된 키를 확인하고 삭제
    • 샘플링된 키 중 25% 이상이 만료된 경우 삭제 과정 반복
  • 레디스 서버에는 백그라운드로 일정 시간마다 실행되는 타이머 이벤트가 있다.
  • 만료 주기 방식
    • 레디스는 이벤트 루프 기반 작동하지만, 빠른 만료 주기는 이 이벤트 루프의 각 주기마다 실행되며 만료된 키의 비율이 10% 이하이거나 메모리 전체 25% 이하의 크기를 소비하도록 동작
삭제 정책
  • 메모리 사용량이 maxmemory 지시자로 설정된 값에 도달했을 때, 메모리 확보를 위해 키를 삭제하는 과정이 이뤄진다.
  • 리눅스에서는 메모리 사용량이 캐시 노드의 메모리를 압박할 경우 OOM Killer가 활성화되어 프로세스를 강제 종료할 수 있다.
    • 레디스 서버가 중된될 수 있으므로 모니터링해야 한다.
  • LFU 메커니즘
    • 사용 빈도가 가장 낮은 항목을 삭제대상으로 선택하는 알고리즘
    • 밑수를 정하고, 지수 부분만 기억해서 계산을 통해 실제 사용 빈도를 추정하는 방식
    • 지수가 커질수록 더 자주 사용된다고 간주하므로 데이터를 사용할 때마다 지수가 올라간다.
    • 확률을 기반으로 근사치를 계산하는 방식이기 때문에 지수값이 클수록 오차가 커지게 된다.
    • 각 키에 접근할 때마다 카운터 증가, 시간이 지나면 카운터 감소, 카운터 값이 낮은 키 삭제 동작
  • LRU 메커니즘
    • 메모리 할당 시 가장 오랫동안 사용되지 않은 키를 삭제 대상으로 선정하는 방법
    • 레디스의 LRU 방식은 무작위로 선택된 일정 수의 아이템 중 가장 오랫동안 사용되지 않은 아이템을 대상으로 정한다.

3. 메모리를 효율적으로 사용하기 위한 기타 방법

  • 메모리를 효율적으로 사용하기 위한 동적 리해싱(active rehashing), 동적 단편화 제거(active defragmentation) 기법
동적 리해싱
  • 레디스는 하나의 큰 해시 테이블을 사용해 데이터 관리
    • 동적 리해싱 방식은 해당 해시 테이블 크기를 사용하려는 상황에 맞추어 자동 조정
    • CPU 시간의 100ms 중 1ms 만큼 사용하여 리해싱 중인 테이블 관련 작업이 있을 때마다 점진적 진행
  • 해시 테이블의 크기는 2의 거듭 제곱 단위로 충돌 시 체이닝 방식 사용
  • 리해싱 과정에서 크기가 조정된 새로운 테이블 추가, 새로운 아이템이 이 테이블에 저장되도록 한다.
    • 데이터가 모두 이동하면 이전 테이블 삭제
    • 만약 레디스의 응답으로 인해 쿼리가 2ms 만큼 지연되는 것이 문제된다면 비활성화하는 것이 좋다.
  • activerehashing 지시어로 활성화할 수 있다.
동적 단편화 제거
  • 메모리 단편화가 발생할 수 있다.
    • 단편화는 모든 메모리 할당자에서 발생하지만 jemalloc 은 그중에서도 단편화가 덜 발생하도록 설계되어 있어 사용하는 것 권장
  • 동적 단편화 제거 기법을 사용하면 재시작 같은 조치없이도 단편화 문제를 해결할 수 있다.
    • 특정 임계값을 초과하면 jemalloc 기능을 사용하여 연속된 메모리 영역에 새로운 데이터의 레플리카 생성 후 오래된 데이터를 해제하는 방식 진행
    • 모든 키에 대해 점진적으로 반복적으로 수행하여 정상 범위까지 감소

클라우드에서 사용하는 레디스

  • AWS ElastiCach는 레디스와 멤키시를 사용하는 예시로 설명

1. OSS와 레디스 차이

  • OSS 버전의 레디스를 운영할 경우 사용자가 직접 장애 대응이나 메트릭 모니터링 등을 관리해야 하지만, ElastiCache는 관리형 서비스이기 때문에 사용자 수고를 덜 수 있다.
  • ElastiCache 제공
    • 완전 관리
      • 하드웨어 프로비저닝, 소프트웨어 패치 적용, 셋업설정 작업, 모니터링, 장애 시 자동 복구
    • 쉬운 스케일링, 백업/복원 기능, 이벤트 알림 기능, 셀프서비스 업데이트 기능
    • 자동 페일 오버, 스왑 메모리 관리, 온라인 리샤딩, 자동 쓰기 슬롯 스로틀링, 안전성 및 규정 준수
    • 지역 간 레플리케이션 기능, 데이터 계층화, 자동 스케일링
고유 기능
  • 이벤트 알림 기능
    • 알림을 통해 관리 콘솔뿐 아니라 AWS CLI나 AWS SDK 에서도 이벤트를 처리할 수 있다.
    • SNS와 연동함으로써 이벤트가 발생했을 때 이메일 또는 SMS를 보내거나 람다, SQS가 자동으로 실행되도록 할 수 있다.
  • 셀프서비스 업데이트 기능
    • 셀프서비스 업데이트 기능을 사용하면 업데이트의 적용 시기와 내용을 더욱 유연하게 제어할 수 있다.
    • 유지보수 창보다 더 유연하게 유지보수 실행 시간을 제어할 수 있는 시스템이 셀프서비스 업데이트 기능
  • 자동 페일오버
    • ElastiCache는 자동 페일오버 기능을 갖추고 있어 높은 내구성 제공하여 페일 오버는 내부 모니터링 메커니즘을 바탕으로 동작
    • 페일 오버 시에는 레플리케이션의 지연 시간이 가장 짧은 레플리카가 선택된다
    • OSS 버전에서는 자동으로 페일오버가 되지 않아 수동으로 조치가 필요하다
  • 멀티AZ
    • AWS AZ를 여러개 사용하여 동시 장애를 피하는 시스템
    • 멀티 AZ가 활성화되면 자동 페일오버 기능도 활성화
    • 멀티 AZ간 지연으로 인한 성능 이슈가 있을 수 있지만 가용성 향상이라는 이점이 더 큰 경우가 많다
    • reserved-memory-percent 매개변수
      • 스냅샷 생성 등에 사용되는 메모리(데이터 세트를 제외한 레디스 서버 내의 메모리) 예약
      • 메모리가 충분하지 않은 상황에서 프로세스를 포크하지 않고 동일 프로세스에서 처리해서 메모리 사용량을 제한하도록 동작
  • 온라인 리샤딩
    • 온라인 리샤딩은 현재 사용중인 클러스터의 샤드 수를 변경할 수 있는 기능으로 온라인으로 요청 처리를 병행하면서 변경 작업을 수행할 수 있다.
    • 샤드 간의 슬롯 배치가 불균형할 때 샤드 추가/삭제를 통해 재분배할 수 있다.
    • 온라인 샤딩을 해야 할 땐 사용가능한 메모리가 현재 사용중인 메모리의 1.5배 이상인지 확인하고 실행해야 한다.
  • 안전성과 보안 관리
    • 디스크 내의 데이터 보관 시 암호화와 TLS 통신 제공
    • ElastiCache는 VPC 내에 생성할 수 있으며 AUTH 명령어를 이용해 인증 기능을 조작하여 로그인을 시도하는 방법이 있다
  • I/O 처리 멀티스레드화
    • vCPU를 사용할 수 있는 캐시 노드에서 I/O를 멀티 스레드로 처리하여 성능 향상시킬 수 있다.
  • 리전 간 레플리케이션 기능
    • 데이터 스토어 기능을 통해 리전 간 레플리케이션 기능을 사용할 수 있음
    • 하나의 리전에서 쓰기 작업을 수행하고 최대 두개의 다른 리전에서 이 데이터를 레플리케이션하여 읽어올 수 있다
  • 데이터 계층화
    • 데이터를 메모리뿐 아니라 로컬의 SSD 스토리지에도 보관하며 계층적으로 관리할 수 있다.
    • LRU 알고리즘에 따라 자주 접근하지 않는 데이터를 메모리 SSD로 이동한 후 데이터에 접근하면 비동기적으로 메모리로 데이터를 읽어온다
  • 자동 스케일링
    • 클러스터 모드 활성화된 레디스 클러스터의 자동 스케일링 기능을 사용할 수 있다
    • 미리 설정된 규칙에 따라 자동으로 샤드 및 레플리카의 수를 조절
제한
  • 일부 기능이나 명령어 사용 제한
    • ex) 관리 콘솔에서는 AOF를 사용할 수 없고 스냅샷 생성 방식 사용 가능
  • 모든 요구사항에 대응하기 어려울 수 있어 충족 여부 확인 필요

2. 클라우드에서 사용하는 방법

  • 가능한 한 ElasiCache의 사용 가능한 최신 버전을 사용하는 것이 좋다.
  • 왭 브라우저 내에서 관리 콘솔 화면을 따라 다음과 같은 항목을 입력해 생성할 수 있다.
    • 레디스 클러스터 이름
    • 버전 지정
    • 레디스 클러스터 사용 여부(ElastiCache 에서는 클러스터 모드 활성/비활성이란 용어 사용)
  • 엔드포인트
    • redis-cli를 사용하여 원격 호스트에서 운영 중인 레디스 서버 접근 (-h)
      • -c 옵션을 사용하여 클러스터 기능을 활성화할 수 있다.
    • 노드별로 엔드포인트가 발급되며 이를 통해 노드 연결 가능
    • 매개변수는 ElastiCache에서 관리하기 때문에 일부는 편집할 수 없거나 OSS 버전과 다른 기본값을 가지고 있는 경우, 캐시 노드 타입별로 값이 정해져 있는 경우 존재
      • 동적, 정적 타입이 있으며 동적 타입은 변경 후 즉시 반영되지만 정적 타입은 설정을 반영하려면 재시작 필요

3. 클라우드를 활용한 트러블슈팅

최소한으로 모니터링을 해야 하는 메트릭
  • 최소한 모니터링해야 하는 메트릭
    • CPU: CPUUtilization 메트릭, EngineCPUUtilization 메트릭
    • 메모리: SwapUsage 메트릭, Evictions 메트릭
    • 연결: CurrConnections 메트릭
  • CPUUtilization 메트릭, EngineCPUUtilization 메트릭
    • CPU 사용률이 높다고 항상 문제가 있는 것은 아니며 자원을 효율적으로 사용하고 있다는 것을 의미할 수 있다
    • CPUUtilization은 호스트 전체의 CPU 사용률, 캐시 노드의 vCPU 개수로 나눈 값으로 계산
    • EngineCPUUtilization는 레디스 엔진이 사용하는 코어의 사용량
      • 레디스는 싱글스레드로 요청을 처리하므로 대상 코어의 CPU를 확인하는 것이 좋다.
    • 워크로드가 원인일 경우 확인 사항
      • 복잡도가 큰 명령어, 최적이 아닌 데이터 모델, 스케일 업 및 스케일 아웃, 스냅샷 생성에 의한 영향
  • SwapUsage 메트릭
    • 호스트에서 사용되는 스왑의 양을 나타낸다.
    • 물리적인 메모리 내에서 회수할 수 없는 페이지를 디스크로 이동시켜 메모리를 더 효율적으로 사용하며 캐시 히트율이 향상될 수 있다
    • 스왑이 커지는 원인
      • 프로세스가 물리 메모리 이상의 메모리를 요구하는 경우
      • 콜드 키로 인한 영향
      • 파일 캐시 시스템으로 인한 커널의 압박
    • 단편화 정도가 심할 경우 해결하기 위한 방법
      • 엔진 재시작, 노드 교체, 동적 단편화 제거 활성화, 메모리 할당자 변경
  • Evictions 메트릭
    • 아이템이 메모리에서 삭제된 경우 확인할 수 있는 지표로 maxmemory에 도달했을 때 maxmemory-policy 정책에 따라 아이템 삭제(LRU, LFU)
    • Evictions 메트릭이 지속적으로 발생한다면 메모리 부족상태일 수 있다.
      • 사용 가능한 메모리양이 더 많은 캐시 노드 타입으로 스케일 업을 한다
      • TTL 설정
      • 샤드 추가로 쓰기 부하를 샤드 간에 분산
  • CurrConnections 메트릭
    • 현재 클라이언트의 연결 수로 문제가 발생했을 때 CLIENT LIST 명령어를 사용해 관련 클라이언트의 세부 정보를 확인할 수 있다
주의해야 하는 메트릭
  • 메모리
    • 메모리 사용량은 BytesUsedForCache, DatabaseMemoryUsage Percentage 메트릭
    • BytesUsedForCache 메트릭은 레디스 엔진이 데이터 세트 및 기타 용도로 사용하는 메모리를 포함한 전체 메모리 사용량
    • DatabaseMemoryUsage 메트릭은 레디스가 사용할 수 있는 최대 메모리 중 실제로 사용중인 메모리 비율
  • 연결
    • NewConnections 는 기간 내에 레디스 서버가 허용한 총 연결수를 나타낸다
      • 극도로 커지게 되면 캐시 노드의 처리 능력을 초과하여 타임아웃 발생 가능
    • 연결에 문제가 생긴다면 레디스 서버의 캐시 노드뿐 아니라 클라이언트의 캐시 노드와 클라인터간 네트워크도 조사해야 한다
  • SaveInProgress 메트릭
    • RDB 파일 저장일 때 확인할 수 있는 메트릭으로 아래같은 경우에 메트릭 변경
      • 레플리케이션 연결이 끊어진 후 복구되어 전체 동기화가 시작되는 시점
      • RDB 파일 스냅샷이 생성되는 시점
    • 해당 값이 1로 오래 유지되면 성능에 영향을 미칠 수 있다.
      • 원인을 파악하기 위해서는 백업 창에 설정된 시간과 메트릭이 증가하는 시점 확인 필요
      • 값이 높으면 레플리카 또는 RDB에 이슈가 있을 수 있다
  • 캐시 유효성
    • CacheHits, CacheMisses 메트릭은 레디스의 효율성 평가하는 데 사용할 수 있다
  • 네트워크
    • 네트워크의 한계에 닿기 전에 다른 메트릭이 먼저 병복현상을 일으키는 경우가 많다.
  • 레플리케이션
    • ReplicationBytes, ReplicationLog: 레플리카에 적용되는 데 걸리는 시간 지연
    • IsPrimary: 마스터인 경우 1, 레플리카인 경우 0
    • PrimaryLinkHealthStatus: 마스터와 레플리카 간의 연결 유지 상태를 1과 0을 나타낸다. 0에 경우 문제
    • GlobalDatastoreReplicationLog
      • 프라이머리 리전과 세컨더리 리전간의 최대 지연시간 또는 클러스터 모드가 활성화된 클러스터의 샤드간에 최대 지연 시간
  • 지연 시간
    • 여러 데이터 유형 및 기능별로 지연 시간 메트릭 제공
유지보수 창 주의사항
  • ElastiCache에서 생성한 클러스터는 주간 유지보수 창이 설정하며 설정한 시간에 유지관리 실행 및 보류 상태가 변경
  • 레디스 노드 장애로 인해 다운된 상태가 되면 서비스 전체가 유지될 수 있도록 시스템을 준비해야 한다
이벤트 확인
  • AWS Health Dashboard 나 이벤트등도 확인해두는 것이 좋다
  • 캐시 노드에서 AWS 기반의 장애 등으로 문제가 발생하면 문제를 감지하고 자동으로 복구 작업을 수행한다.
    Recovering cache nodes 0001 Finished recovery for cache nodes 0001
    
  • 엔진 로그로 문제를 감지하고 자동으로 복구 작업을 수행한다

레디스 구조

  • RESP
  • 레디스를 위한 프로토콜로 클라이언트-서버 모델 기반으로 TCP 보다 상위 계층인 애플리케이션 계층
  • redis-cli 같은 많은 라이브러리도 RESP에서 정의된 메시지를 통해 통신
  • RESP3
    • 부동소수점 수나 불리언을 문자열/정수처리 동작을 개선하고 프로토콜의 표현력을 늘리고 암묵적 변환을 없애는 것을 목표로 한다
  • SDS
    • 가변 길이 문자열에 관한 C 언어 라이브러리로 레디스의 문자열에 SDS가 사용되고 있다
  • ae (Asynchronous Event)
    • 이벤트 기반 라이브러리로 싱글 스레드를 통한 요청의 이벤트 기반 처리 기능을 구현한 것이 ae 이다
    • epoll, kqueue, select 등의 wrapper 사용
      • 파일 디스크립터 -> I/O 중복성 모듈 -> 이벤트 루프 -> 파일 이벤트 디스패처 -> 수신,읽기,쓰기 핸들러
    • File, Time 이벤트 두가지 유형 처리
      • File Event: 네트워크 소켓 등 파일 디스크립터에서 발생하는 읽기/쓰기 요청(I/O 이벤트).
      • Time Event: 만료된 키 삭제, 정기 작업 등 시간에 기반한 이벤트.
    • Redis 이벤트 루프 구조
      • Redis는 자체적으로 구현한 이벤트 루프(ae.c)를 돌리며, epoll/kqueue/select 등 다양한 I/O multiplexing 사용
      • File Event에는 AE_READABLE, AE_WRITABLE 플래그 지정, 읽기/쓰기 작업이 발생하면 등록된 handler(콜백)가 호출
      • Time Event는 기간이 지난 키의 삭제, 주기적 통계 작업 등에 사용됩니다.
    • 동작 과정 예시
      • 서버가 소켓에 연결을 받는다(file event 등록)
      • 클라이언트의 명령이 들어오면 AE_READABLE 이벤트와 함께 handler 실행
      • 응답 데이터를 보낼 시 AE_WRITABLE 이벤트가 발생하고 handler 실행
      • 시간 기반 작업은 time event로 추가 주기적으로 처리
  • RAX
    • 메모리 관리용 데이터 구조로 안티레즈가 레디스의 성능 문제를 해결하기 위해 만든 기수 트리(Radix Tree)
    • rax는 key(문자열)를 트리 구조의 경로로 분해하여 저장하는 방법 제공
    • 공통 접두어(prefix)가 많은 문자열 집합을 메모리 효율적으로 저장하고 빠르게 검색 가능
    • Redis의 set, sorted set(특히 zset), 클러스터 슬롯 맵 등에 핵심적으로 사용
    • 동작 방식
      • 트리에 문자열을 삽입(예: “test”, “toaster”)하면, 공통 부분(“t”, “o”)은 하나의 경로로 합쳐집니다.
      • key들이 겹치는 부분(접두어)이 많을수록 노드 개수가 줄어드는 구조
        • 특히 대량의 유사 문자열을 저장할 때 메모리 효율성이 좋고 시간 성능이 좋다.
      • 노드 내부에는 자식 노드 테이블이 있어 빠른 탐색이 가능

5. Copy On Write

6. Raft

7. HyperLogLog