레디스 서버는 메모리를 다음과 같은 형태로 사용합니다.
레디스는 malloc
함수로 메모리를 할당하기 때문에 운영체제의 메모리 페이지 할당 매핑을 제어할 수 없도록 구현되어 있습니다. 따라서 레디스에서 데이터 삭제 명령어를 실행해도 확보된 메모리가 운영체제로 반환된다는 보장이 없으며, 실제 메모리 사용량인 RSS도 변하지 않습니다.
메모리 할당자(mem_allocator
)는 새로운 키를 추가할 때 비어 잇는 메모리 청크를 재사용하도록 동작하기 때문에 새롭게 사용하는 메모리가 max값을 초과하지 않는 한 max값으로 유지됩니다.
요청된 쿼리는 '클라이언트 쿼리 버퍼'라는 공간에 일단 저장 후 소켓을 통해 데이터를 점진적으로 읽습니다. 데이터가 크거나 복잡한 명령어인 경우 버퍼에서 모든 처리 내용을 모아서 버퍼링합니다. 이 과정은 클라이언트 S/W의 버그 등 버퍼를 과도하게 소비하는 것을 방지하고, 레디스 서버가 크래시하는 사태를 예방합니다.
used_memory
는 레디스 jemalloc
같은 메모리 할당자를 사용하여 할당한 메모리의 크기를 의미합니다. 즉, 실제로 데이터가 사용하는 크기를 나타냅니다. 클라이언트 출력 버퍼 등도 포함되어 있으므로 데이터만 사용하는 것이 아니라는 점에 유의해야 합니다.
레플리카 출력 버퍼와 AOF 버퍼 크기는 used_memory
에 포함되지만, maxmemory
를 계산할 때는 포함되지 않는다는 점도 유의해야 합니다. INFO 명령어의 mem_not_counted_for_evict
는 AOF 버퍼, AOF 재작성 버퍼, 레플리카용 클라이언트 출력 버퍼 그리고 RDB의 스냅숏을 생성할 때 CoW에 의해 사용된 메모리의양의 합계를 나타냅니다.
실제 사용되는 메모리양
used_memory
-mem_not_counted_for_evict
<=maxmemory
레디스 7.0 이후부터는 maxmemory_clients
를 통해 모든 클라이언트가 사용하는 메모리 소비량을 제한할 수 있습니다. 제한값을 초과하면, 소비량이 가장 많은 클라이언트의 연결이 해제됩니다. 해제 대상에서 제외하고 싶다면 CLIENT NO-EVICT on
명령어를 상ㅇ하면 됩니다.
used_memory_overhead
는 할당된 오버헤드를 관리하는 데 사용되는 내부 데이터 구조입니다.
used_memory_overhead
= 서버 초기 메모리 사용량
+ 레플리케이션 백로그
+ 클라이언트 출력 버퍼(replica, normal, pussub, master)
+ 레디스 클러스터의 클러스터 버스에서 다른 노드와의 연결에 사용되는 메모리
+ AOF 버퍼
+ AOF 재작성 버퍼
+ 이페머럴 스크립트의 캐시
+ 레디스 함수
+ 레디스 서버 내 메인 해시 테이블의 데이터 구조에 대한 오버헤드
+ 레디스 서버 내 만료용 해시 테이블의 데이터 구조에 대한 오버헤드
MONITOR
명령어 관련 클라이언튼 출력 버퍼는 used_memory
와 used_memory_overhead
양쪽에 모두 포함됩니다.
used_memory_dataset
=used_memory
-used_memory_overhead
used_memory_dataset_perc
=used_memory_dataset
/ (used_memory
-used_memory_startup
)
메모리 단편화율은 레디스가 현재 사용 중인 것으로 인식하는 메모리 사용량을 RSS 값으로 나눈 값 or RSS 값 대비 레디스가 현재 사용 중이라고 인식한 메모리 사용량을 의미합니다.
mem_fragmentation_ratio
=used_memory_rss
/used_memory
단편화율이 1.0이거나 그보다 약간 더 높으면 양호한 편이고, 1.5 이상이면 단편화가 심각하고 성능 문제가 발생할 수 있으므로 주의해야 합니다. 반대로 1보다 낮다면 스왑이 발생하고 있을 가능성이 있습니다.
단편화가 발생했을 때의 대처 방법
MEMORY PURGE
명령어 실행jemalloc
이 메모리를 즉시 해제하지 않는 상황에서 메모리를 재사용하고자 할 때 유용레디스 서버는 각 클라이언트, 레플리카, Pub/Sub 기능용 COB(Client Output Buffer)를 가지고 있습니다. COB는 특정 이유로 서버로부터 데이터를 즉시 읽지 못하는 클라이언트의 연결을 강제로 끊는 용도로 사용됩니다.
레디스 서버는 클라이언트마다 COB를 갖추고, 서버에서 처리한 결과를 직접 클라이언트에게 보내지 않고 일단 COB에 저장합니다. 그 후 COB에 저장된 정보가 클라이언트에 한번에 전송됩니다.
COB 버퍼가 커질 수 있는 상황
만약 COB를 많이 사용하는 클라이언트가 있고, 크기에 제한을 두지 않으면 다른 처리 작업에 영향을 줄 수 있습니다. 제한이 있고 초과를 했을 때 해당 클라이언트 연결을 끊을 수 있습니다.
client-output-buffer-limit
는 레디스에서 클라이언트 출력 버퍼의 크기 제한을 설정하는 데 사용됩니다. normal, replica, pubsub의 세 가지 유형의 COB에 각각 하드 리미트와 소프트 리미트를 설정할 수 있습니다. 하드 리미트는 설정된 값에 도달하면 즉시 연결이 끊기는 방식이며, 소프트는 설정된 값에 도달 후 지정한 시간 동안 지속되면 연결이 끊깁니다.
레디스에서 키의 만료를 처리하는 방법은 크게 수동적 방법과 능동적 방법으로 나뉘며, 기본적으로 둘을 조합해서 사용합니다. TTL이 만료되면 대상 키가 메모리에서 삭제되는 것이 아니라 메모리에 남게 되는데 이 데이터를 삭제하는 방법을 의미합니다.
수동적 방법은 클라이언트가 키에 접근할 때 해당 키의 TTL이 만료되었는지 확인합니다. 만약 TTL이 만료되었다면, 해당 키를 메모리에서 삭제합니다.
능동적 방법은 레디스가 매 초마다 10번, 임의로 20개의 키를 샘플링하여 만료된 키를 확인하고 삭제합니다. 이때 샘플링된 키 중 25% 이상이 만료된 경우, 다시 샘플링과 삭제 과정을 반복합니다. 이 과정은 레디스가 싱글 스레드로 처리하기 때문에 다른 작업을 일시적으로 차단하여 지연 시간에 영향을 줄 수 있습니다.
레디스에서는 메모리 사용량이 maxmemory
로 설정된 값에 도달했을 때, 메모리 확보를 위해 키를 삭제하는 과정이 이루어집니다. 이때 maxmemory-policy
로 설정된 정책을 따릅니다.
정책 | 상세 |
---|---|
noeviction | 오류를 반환하고 어떤 키도 삭제하지 않는다 |
volatile-random | TTL이 설정된 키 중에서 임의로 키를 선택해 삭제한다 |
allkeys-random | 모든 키 중에서 임의로 키를 선택해 삭제한다. |
volatile-lru | TTL이 설정된 키 중에서 가장 최근에 사용되지 않는 키를 선택해 삭제한다. |
allkeys-lru | 모든 키 중에서 가장 최근에 사용되지 않는 키를 선택해 삭제한다. |
volatile-ttl | TTL이 설정된 키 중에서 남은 TTL이 가장 짧은 키를 선택해 삭제한다. |
volatile-lfu | TTL이 설정된 키 중에서 가장 적게 사용된 키를 선택해 삭제한다. |
allkeys-lfu | 모든 키 중에서 가장 적게 사용된 키를 선택해 삭제한다. |
오픈소스 버전의 레디스에서 noeviction
이 기본값으로 설정되어 있으며, 관리형 서비스를 사용하는 경우에는 다를 수 있습니다. 메모리 사용 문제에 대처하기 위해서는 키의 TTL 설정을 확인하거나, 다른 maxmemory-policy
를 시도하는 것이 효과적입니다. 임시 해결책으로는 DEL
명령어를 사용하여 불필요한 키를 삭제하거나 메모리 용량을 증가시키는 방법이 있습니다.
리눅스에서는 메모리를 압박할 경우 OOMKiller가 활성화하므로 지속적으로 모니터링해야 합니다.
레디스는 하나의 큰 해시 테이블을 하용하여 데이터를 관리합니다. 동적 리해싱 방식은 이 해시 테이블의 크기를 사용하려는 상황에 맞추어 자동으로 조정합니다.
해시 테이블의 크기는 2의 거듭제곱 단위로 설정되며, 해시 충돌이 발생할 경우 충돌을 처리 하기 위해 체이닝 방식을 사용합니다.
리해싱 과정에서는 크기가 변경된 새로운 테이블을 추가로 준비하여, 오래된 테이블에서 새 테이블로 데이터가 옮겨지고 이전 테이블은 삭제됩니다.
동적 단편화 제거는 특정 임계값을 초과했을 때 jemalloc
의 기능을 사용하여 연속된 메모리 영역에 새로운 데이터의 레플리카를 생성한 후 오래도니 데이터를 해제하는 방식으로 진행됩니다. 모든 키에 대해 점진적으로 반복적으로 수행되는 방식을 통해 단편화를 정상 범위까지 감소시킵니다.
임계값은 대표적으로 단편화로 인해 사용할 수 없는 메모리의 최소 크기, 메모리 단편화의 최대 최소 비율, CPU 최대 최소 사용률 등으로 설정할 수 있습니다.