-
Notifications
You must be signed in to change notification settings - Fork 0
redis memory
레디스는 따로 메모리 관련 설정을 해두지 않으면, 메모리가 넘치게 되어 운영체제의 OOM(Out of Memory) Killer 가 동작해 레디스를 종료시키는 경우가 생긴다.
실제로 이번 개발 과정에서도 위 에러를 만났다.
에러를 해결하면서 레디스 메모리에 대해 학습한 것과 레디스 메모리 최적화 방법을 정리해보았다.
레디스를 사용할 때, max-memory-policy 설정은 필수적이다. 그리고 max-memory는 전체 시스템 메모리의 절반 정도로 설정하는 것이 권장된다.
왜 절반으로 제한을 두어야할까? 이유는 크게 두 가지로 정리해볼 수 있다.
- RDB란?
Redis는 인메모리 데이터 저장소다. 서버 재시작 시 모든 데이터가 유실된다. 그렇기에 적절한 데이터 백업이 필요하다.
Redis는 이를 위해 AOF, RDB의 두 가지 Presistence Option을 제공한다.
아주 간단히 비교하자면, RDB는 key1 : "duck" 이라는 정보의 스냅샷을 보관하는 것이고,
AOF는 set key1 "a", set key1 "duck" 이라는 명령어의 모음을 보관한다.
레디스에는 복구를 위한 RDB 스냅샷 기능을 제공한다. RDB 기능은 어떻게 레디스가 동작하면서 동시에 작업이 진행될 수 있을까?
이것은 Redis가 RDB 스냅샷을 생성할 때 OS의 fork()를 호출해 새로운 자식 프로세스를 생성하기 때문이다. 백그라운드에서는 자식 프로세스로 RDB를 저장하고, 원래의 프로세스는 일반적인 요청을 처리할 수 있게된다.
여기서 fork()는 프로세스를 복제하는데, 이 과정에서 Copy-On-Write 방식을 사용하여 부모 프로세스의 메모리를 자식 프로세스와 공유하게 된다. 이렇게 자식 프로세스는 해당 메모리를 복사하면서 추가적인 메모리 사용을 유발한다.
따라서 Redis가 이미 시스템 메모리를 거의 다 사용하고 있다면, fork() 호출로 인해 메모리 부족(OOM) 상태가 발생할 가능성이 높아진다.
이를 방지하기 위해, Redis의 maxmemory는 전체 메모리의 절반 이하로 설정하는 것이 권장된다.
하나의 이유가 더 있다면, 레디스는 내부적으로 메모리 할당 시, jemalloc를 기본 할당자로 사용하기 때문이다.
jemalloc는 효율적인 메모리 관리를 위해 설계되었다. 하지만 그렇다 할지라도 여전히 메모리 단편화 문제가 발생할 수 있다.
단편화로 인해 실제 사용 중인 메모리보다 더 많은 물리적 메모리를 점유하게 되고, 시스템 메모리의 절반으로 maxmemory를 제한하면, 단편화나 추가 메모리 사용으로 인해 Redis가 과도한 메모리를 사용하는 상황을 방지할 수 있게 된다.
maxmemory가 설정되었다면, Redis는 메모리 제한에 도달했을 때의 동작을 정의하기 위해 maxmemory-policy 설정이 필요하다.
메모리가 가득차면, 어떻게 키를 삭제할 것인가에 대한 알고리즘을 설정해주는 것이다.
대표적으로 아래의 정책이 있다.
1. noeviction: 메모리가 가득 차면 새 데이터를 받지 않고 에러를 반환.
2. allkeys-lru: 모든 키에서 가장 적게 사용된 데이터(LRU)를 제거.
3. volatile-lru: 만료 시간이 설정된 키에서 LRU 정책으로 제거.
기본 설정은 noeviction이다. (위에서 레디스가 터진 이유..) 서비스에서는 allkeys-lru를 설정했다.
ziplist는 Redis에서 사용되는 압축된 연속 메모리 구조로, 작은 데이터를 효율적으로 저장하기 위한 구조이다.
그럼 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=92NizoBL4uARedis Copy-on-Write 분석
http://redisgate.kr/redis/configuration/copy-on-write.php
Betting duck
- [지호] 베팅 생성 페이지에서 아이콘이 함께 리렌더링 되는 문제 해결
- [정민] 채팅 시스템을 위한 IRC 프로토콜
- [정민, 지호] 소켓 이벤트가 무한리필 되는 문제 해결하기
- [정민, 지호] 소켓 관리 실패로 인한 서버 다운 문제 개선하기
- [석호] 베팅 종료 API 개선하기
- [동교] 베팅 프로세스 흐름도
- [동교] 실시간 베팅에서 레디스 원자성 테스트
- [석호] Redis에서의 트랜잭션
- [석호] Redis를 이용한 메시지 큐 구현(1)
- [석호] Redis를 이용한 메시지 큐 구현(2)
- [동교] Redis에서 O(N) 관련 명령어는 주의하기
- [동교] 베팅덕에 적용한 다양한 캐시 전략
- [동교] 레디스의 메모리 설정 최적화
- [정민] 브라우저에서 렌더가 일어나는 방식
- [정민] Layered frontend application
- [정민] sessionStorage를 이용하여 전역 상태를 관리해도 될까?
- [정민] 왜 css는 내 마음대로 적용이 안될까?
- [공통] 브랜치에서 push 했는데 dev로 바로 병합된다?
- [공통] Cross-Origin WebSocket에서 쿠키 전송 문제: 삽질 기록과 해결
- [FE] Cannot find package 'prettier-plugin-tailwindcss'
- [BE] NestJS에서의 @Injectable() - WebSocketGateway 싱글톤 이슈
- 유저 스토리는 무엇인가?
- GitFlow vs Trunk-based 협업 방식
- The Front End Developer/Engineer Handbook 2024
- 코드 리뷰 in 뱅크샐러드 개발 문화
- Optimize Largest Contentful Paint
- The Looper Mini Web Machine
- Best practices for fonts
- React Folder Structure in 5 Steps [2024]
- Speeding up the JavaScript ecosystem - The barrel file debacle