Skip to content

redis memory

정동교 edited this page Dec 4, 2024 · 4 revisions

Redis 메모리 설정 최적화

레디스는 따로 메모리 관련 설정을 해두지 않으면, 메모리가 넘치게 되어 운영체제의 OOM(Out of Memory) Killer 가 동작해 레디스를 종료시키는 경우가 생긴다.

스크린샷 2024-12-04 오후 7 23 41

실제로 이번 개발 과정에서도 위 에러를 만났다.

에러를 해결하면서 레디스 메모리에 대해 학습한 것과 레디스 메모리 최적화 방법을 정리해보았다.


1. Max-memory : 시스템 메모리의 절반으로 제한하는 이유

 레디스를 사용할 때, max-memory-policy 설정은 필수적이다. 그리고 max-memory는 전체 시스템 메모리의 절반 정도로 설정하는 것이 권장된다.

왜 절반으로 제한을 두어야할까? 이유는 크게 두 가지로 정리해볼 수 있다.


RDB 스냅샷 생성 중의 메모리 사용 : fork()

- RDB란?
Redis는 인메모리 데이터 저장소다. 서버 재시작 시 모든 데이터가 유실된다. 그렇기에 적절한 데이터 백업이 필요하다.
Redis는 이를 위해 AOF, RDB의 두 가지 Presistence Option을 제공한다.
아주 간단히 비교하자면, RDB는 key1 : "duck" 이라는 정보의 스냅샷을 보관하는 것이고,
AOF는 set key1 "a", set key1 "duck" 이라는 명령어의 모음을 보관한다.

레디스에는 복구를 위한 RDB 스냅샷 기능을 제공한다. RDB 기능은 어떻게 레디스가 동작하면서 동시에 작업이 진행될 수 있을까?

 이것은 Redis가 RDB 스냅샷을 생성할 때 OS의 fork()를 호출해 새로운 자식 프로세스를 생성하기 때문이다. 백그라운드에서는 자식 프로세스로 RDB를 저장하고, 원래의 프로세스는 일반적인 요청을 처리할 수 있게된다.

스크린샷 2024-12-02 오후 11 37 39

 여기서 fork()는 프로세스를 복제하는데, 이 과정에서 Copy-On-Write 방식을 사용하여 부모 프로세스의 메모리를 자식 프로세스와 공유하게 된다. 이렇게 자식 프로세스는 해당 메모리를 복사하면서 추가적인 메모리 사용을 유발한다.

 따라서 Redis가 이미 시스템 메모리를 거의 다 사용하고 있다면, fork() 호출로 인해 메모리 부족(OOM) 상태가 발생할 가능성이 높아진다.

 이를 방지하기 위해, Redis의 maxmemory는 전체 메모리의 절반 이하로 설정하는 것이 권장된다.


Jemalloc과 메모리 단편화

하나의 이유가 더 있다면, 레디스는 내부적으로 메모리 할당 시, jemalloc를 기본 할당자로 사용하기 때문이다.

 jemalloc는 효율적인 메모리 관리를 위해 설계되었다. 하지만 그렇다 할지라도 여전히 메모리 단편화 문제가 발생할 수 있다.

 단편화로 인해 실제 사용 중인 메모리보다 더 많은 물리적 메모리를 점유하게 되고, 시스템 메모리의 절반으로 maxmemory를 제한하면, 단편화나 추가 메모리 사용으로 인해 Redis가 과도한 메모리를 사용하는 상황을 방지할 수 있게 된다.


2. Max-memory-policy 설정

 maxmemory가 설정되었다면, Redis는 메모리 제한에 도달했을 때의 동작을 정의하기 위해 maxmemory-policy 설정이 필요하다.

메모리가 가득차면, 어떻게 키를 삭제할 것인가에 대한 알고리즘을 설정해주는 것이다.

대표적으로 아래의 정책이 있다.

1. noeviction: 메모리가 가득 차면 새 데이터를 받지 않고 에러를 반환.

2. allkeys-lru: 모든 키에서 가장 적게 사용된 데이터(LRU)를 제거.

3. volatile-lru: 만료 시간이 설정된 키에서 LRU 정책으로 제거.

기본 설정은 noeviction이다. (위에서 레디스가 터진 이유..) 서비스에서는 allkeys-lru를 설정했다. 


3. ziplist 적용

ziplist란

ziplist는 Redis에서 사용되는 압축된 연속 메모리 구조로, 작은 데이터를 효율적으로 저장하기 위한 구조이다.


ziplist를 사용하면 메모리가 줄어드는 이유

 그럼 ziplist를 사용하면, 기존의 방식과 어떻게 다르길래 메모리를 줄일 수 있을까?

 이는 ziplist가 연속된 메모리 공간에 데이터를 저장하여 오버헤드를 줄이고, 메모리를 최소화하도록 설계되었기 때문이다.


 일반적으로 Redis의 표준 리스트나 해시는 포인터 기반 데이터 구조로, 각 요소를 저장할 때 포인터 오버헤드가 추가된다. 반면 ziplist는 데이터를 하나의 연속된 메모리 블록에 저장하므로 포인터와 관련된 추가적인 메모리 사용을 없앨 수 있다.

 이게 무슨 말인지 간단히 살펴보자.


일반 리스트

 [포인터1 | 데이터1] -> [포인터2 | 데이터2] -> ...

각 요소가 독립적으로 저장되고 포인터를 사용하여 연결된다.

즉, 여기서는 연속된 데이터로 저장되지 않기 때문에, 포인터 오버헤드가 포함된다.


ziplist 일반 리스트

[헤더 | 데이터1 길이 | 데이터1 | 데이터2 길이 | 데이터2 | ... | 테일]

모든 요소가 연속된 메모리 공간에 저장되기 때문에, 포인터 오버헤드가 필요없다.


추가로 ziplist는 요소의 데이터 타입과 길이, 값을 인코딩해서 저장한다. 예를 들어, 작은 정수는 최소한의 바이트(1바이트 또는 2바이트)로 저장할 수 있다.


물론 단점도 존재한다.

 데이터 크기/수 제한이 있다. 특정 크기 이상(디폴트: 64바이트 또는 512개 요소)으로 커지면 더 이상 ziplist를 사용할 수 없게 되어서, 일반적인 자료 구조로 전환이 된다. 그래서대규모 데이터에서는 비효율적일 수 있다.

그리고 데이터가 연속적으로 저장되기 때문에, 중간 삽입/삭제 시 재배치가 필요하여 업데이트 시간이 더 걸리는 단점이 있다.


참고자료

[우아한테크세미나] 191121 우아한레디스 by 강대명님
https://www.youtube.com/watch?v=mPB2CZiAkKM

[NHN FORWARD 2021] Redis 야무지게 사용하기
https://www.youtube.com/watch?v=92NizoBL4uA

Redis Copy-on-Write 분석
http://redisgate.kr/redis/configuration/copy-on-write.php

🏠 𝐇𝐨𝐦𝐞

🙌 𝐈𝐧𝐭𝐫𝐨𝐝𝐮𝐜𝐭𝐢𝐨𝐧

📄 𝐏𝐫𝐨𝐣𝐞𝐜𝐭 𝐃𝐨𝐜𝐮𝐦𝐞𝐧𝐭𝐚𝐭𝐢𝐨𝐧

🤝 𝐓𝐞𝐚𝐦 𝐑𝐮𝐥𝐞𝐬

🎁 𝐓𝐞𝐜𝐡𝐧𝐢𝐜𝐚𝐥 𝐒𝐡𝐚𝐫𝐢𝐧𝐠

프로젝트 초기 세팅

메인 페이지

로그인/회원가입/게스트 로그인

베팅페이지

BE

FE

🔫 𝐓𝐫𝐨𝐮𝐛𝐥𝐞𝐬𝐡𝐨𝐨𝐭𝐢𝐧𝐠

📎 𝐑𝐞𝐟𝐞𝐫𝐞𝐧𝐜𝐞 𝐋𝐢𝐧𝐤𝐬

Clone this wiki locally