devcken.io

Thoughts, stories and ideas.

Redis RAM Ramifications - Part 1

원문: https://redislabs.com/blog/redis-ram-ramifications-part-i/

... 우리가 알고 있듯, 알고 있다고 아는 것이 있다. 우리가 안다고 아는 것이 있다는 것이다. 우리는 또한 모른다고 알고 있는 것이 있다. 즉, 우리가 모르는 무언가가 있다고 알고 있다고 하는 것이다. 그러나 모르는 것을 모르는 것도 있다. 우리가 알지 못하는 것을 알지 못하고 있음을 말한다"

미국 국방 장관 도널드 럼스펠드, 2002년

Redis는 얼마나 많은 RAM을 필요로 할까?

Redis에 관해 가장 자주 반복되는 질문이 있다면, 이것일 겁니다. 이에 대한 대답은 너무도 많은 요인들 때문에 정확히 대답하기 가장 힘든 대답 중 하나입니다. 이 포스트에서 (그리고 내가 대충 얘기하고 중얼거리면서 장황하게 이야기하는 경향이 있기에 앞으로 쓸 글들에서) Redis의 RAM 소비에 대해 알아볼 것입니다. 모든 것에 대한 굉장한 답변은 보장하지는 못하지만, 다음과 같은 것들에 도움이 되길 바랍니다:

  • 알려진 것에 대해 알고 있다는 것(KKs)을 확인하기 위해 반복합니다
  • 가능하면 알고 있지 못하는 것을 아는 것으로 바꾸거나, 적어도 대충 이해하는 정도(KRUU)로 바꾸고자 합니다
  • 우리가 모르고 있는 것을 모르는 것에 대해 희망을 가지고 어떻게든 알아보면서, 모르고 있다는 사실을 아는 것, 대충이나마 이해하는 것, 혹은 알고 있는 것으로 바꿉니다

사이드 노트: RSS 피드를 구독해 이 시리즈에 대한 진행 과정을 추적할 수 있습니다. 아니면, Redis Watch에 가입해 메일함에 주간 업데이트가 오도록 할 수 있습니다. 그러나 그것만 보고 있다면, 실시간 정보를 위해 트위터를 팔로우하세요.

아무튼, Redis는 소프트웨어 중 하나이고 동작하는데 RAM을 요구합니다. 그러나, Redis는 그냥 보통의 소프트웨어가 아니라, in-memory 데이터베이스이며, 이는 Redis가 관리하는 모든 데이터 조각들 역시 RAM 안에 유지된다는 것을 뜻합니다. Redis가 운영에 필요로 하는 RAM을 Opernational RAM이라고 하고, 데이터 스토리지에 사용되는 RAM을 User Data RAM이라고 해보죠.

짐을 싣지 않은 제비의 비행 속도는 얼마인가?

Redis의 Operational RAM에 대해 얼른 알아보죠.

그것은 Redis가 수행하는 많은 목적과 작업을 위해 사용되며, RAM의 청크를 사용자의 데이터가 아닌 모든 것을 위해 Redis가 사용하는 전체 메모리와 같다고 생각하는 방법 중 하나입니다(나는 아마도 이후에 이것 때문에 천천히 진행할 것 같다). Redis의 RAM 사용 공간은 다음을 포함하는, 무수히 많은 배치 요인들에 영향을 받습니다:

  • 서버 프로세서의 아키텍처
  • 운영 시스템
  • Redis의 버전과 구성 정보
  • 수많은 아는 것, 무엇인지는 알지만 잘은 모르는 것, 아예 모르는 것

하지만, 전형적인 서버의 유휴 상태에서 Redis를 테스트하여 Redis의 operational RAM 요구 사항에 대한 기준을 쉽게 정할 수 있습니다. 예를 들어, 가상화된 우분투 14 64비트 서버의 upladen African swallow V3.0.0 인스턴스의 메모리 사용 공간은 7995392 바이트(또는 약 7.6MB)다. Redis 인스턴스가 얼마나 많은 RAM을 할당하는지 ps의 RSS 컬럼 혹은 Redis의 INFO 명령어를 사용해 빠르게 알아낼 수 있습니다:

foo@bar:~$ uname -a  
Linux bar 3.13.0-49-generic #81-Ubuntu SMP Tue Mar 24 19:29:48 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux  
foo@bar:~$ ps aux | grep redis-server  
foo 20139 0.0 0.1 42304 7808 pts/1 Sl+ 19:18 0:00 ./redis-server *:6379  
foo 20143 0.0 0.0 15940 944 pts/9 S+ 19:18 0:00 grep --color=auto redis-server  
foo@bar:~$ redis-cli INFO memory | grep used_memory_rss  
used_memory_rss:7995392  

사이드 노트: 위 두 메서드와 다른 것들의 결과가 항상 동일한 것은 아닙니다. 또한, 앞으로 보게 되겠지만 usedmemoryrss가 Redis의 used_memory와 상당히 차이가 난다는 점을 알아두시기 바랍니다.

이것이 최근에 초기화된 Redis 인스턴스라는 점 덕분에, 이 수치가 운영 상의 공정한 기준이라는 것을 추정할 수 있습니다. Redis의 operational RAM은 증가할 수 있으며, 심지어는 엄청 늘수도 있지만, 이제부터는 그 점에 대해 걱정할 필요가 없습니다.

뭐라고? 제비가 코코넛을 나른다고?

Redis는 아름답지만, 그저 아름다운 면 때문에 사용하고 있지는 않겠죠? 아닐 겁니다. 오늘 날 지구 상에서 가장 빠른 NoSQL를 필요로 하기 때문에 Redis를 관리하고 있을 겁니다. 당신은 Redis가 당신의 코코넛을 나르도록 하며 (거의) 매 초당 43번의 날개 짓을 하도록 합니다. 그래서 사용자 데이터가 차지하는 RAM은 얼마나 될까요? 그것은 코코넛에 달려있습니다 🙂

Redis의 schemaless schema*는 Key-Value 모델에 기반합니다. Redis가 관리하는 모든 사용자 데이터는 기본적으로 KV 쌍입니다. 키와 값이 더 길거나 클수록, Redis가 더 많은 RAM을 필요로 한다는 것을 이해하는 것은 대단한 일이 아닙니다. 그러나 Redis는 데이터를 간결하고 조직적으로 유지하기 위해 설계된 몇 가지 독창적인 트릭을 가지고 있습니다.

** 스키마가 없는 데이터베이스 같은 것은 없습니다. 대부분은 암시적인 데이터베이스만 가질 수 있죠.

가장 간단한 Redis 데이터 타입과 관련된 예제로 그것을 알아보죠:

127.0.0.1:6379> SET swallow coconut  
OK  

위에서 얼마만큼의 RAM을 사용했을까요? 모든 데이터가 키에 의해 구성되므로, 키의 이름이 우리가 알아내야 할 첫번째 요소입니다. Redis 키 이름은 바이너리에 안전한 문자열로 512MB까지 가능합니다. 좋아요. Redis에서 문자열 값 또한 바이너리에 안전하며 0.5GB까지 가능합니다. 그러므로 위 예제에서 "swallow"에 대해 7 바이트, "coconut"에 대해서 또 7바이트 라고 추정할 수 있는데... 틀렸습니다. 적어도 부분적으로는 말이죠. 다음을 보겠습니다:

127.0.0.1:6379> STRLEN swallow  
(integer) 7
127.0.0.1:6379> DEBUG SDSLEN swallow  
key_sds_len:7, key_sds_avail:0, val_sds_len:7, val_sds_avail:0  

STRLEN을 이용해 본대로, Redis는 "swallow"라는 키에 저장한 값("coconut")의 길이가 7바이트라고 합니다. 더욱이, "비밀의" DEBUG SDSLEN 명령어 역시 동일한 주장을 하고 있지만, 둘 다 데이터의 오버헤드에 대해서는 언급하고 있지 않은데, 모든 Redis 데이터 구조는 자신의 수화물을 가지고 있습니다. 즉, 실제 문자열 ("swallow"와 "coconut") 외에도 Redis는 그것을 관리하기 위한 약간의 RAM을 더 필요로 합니다.

Redis의 모든 key-value 튜플은 내부적인 장부를 위해 추가적인 RAM을 사용하며, 해당 RAM의 양은 데이터 구조와 데이터 자체에 달려 있으며, 나는 메타데이터가 아닌 이 오버헤드가 사용자 데이터의 일부라고 생각합니다. 달리 말하자면, 모든 X 바이트의 문자열에 대해 Redis는 X + Y 바이트를 요구하며, 그렇기에 분명히 Y는 잘 모르는 것에서 잘 아는 것이 되어야 할 겁니다.

문자열 이론

Redis의 문자열은 대부분은 Salvatore Sanfilippo @ antirez의 서브 프로젝트 중 하나인 sds (혹은 Simple Dynamic Strings 라이브러리)에 의해 구현됩니다. sds 문자열이 Redis 내부적으로 사용 상의 상당한 힘과 용이함을 가져다 주지만, sds 문자열이 만들어져 약간의 오버헤드가 발생합니다:

+--------+-------------------------------+-----------+
| Header | Binary safe C alike string... | Null term |
+--------+-------------------------------+-----------+
         |
         `-> Pointer returned to the user.

(안티레즈의 다이어그램 설명, https://github.com/antirez/sds/blob/master/README.md)

sds 문자열 헤더의 크기는 (당장은) 8바이트이며 추가 바이트로 null 문자가 들어가는데, 이는 문자열 당 총 9바이트의 오버헤드입니다. 7바이트의 "swallow"가 갑자기 그 두 배 이상인 16바이트 RAM으로 Redis에 저장되었습니다!

사이드 노트: 실제로는 더 사용합니다. 모든 키에 대한 참조가 Redis의 keyspace 해시 테이블에 저장되는데, 이는 더 많은 RAM을 요구합니다... 그리고 Redis가 LRU와 같은 일부 데이터들을 조성하기 위한 robj "객체" 또한 존재합니다... 그러나 그건 잘은 모르지만 아는 것으로, 키의 관리 RAM 오버헤드로 두고 당장은 operational RAM으로 던져두도록 하죠 🙂

실제로 한 라인에 코코넛이 있다면...

제비와 코코넛으로 돌아가보겠습니다. 현재 우리는 Redis가 위 예제에서 키와 값을 구성하는 두 개의 문자열을 저장하기 위해 36바이트를 사용한다는 것을 알게 되었습니다. 근데 그게 전부일까요? 코코넛에 대해 좀 더 알아보도록 하죠:

127.0.0.1:6379> OBJECT ENCODING swallow  
"embstr"

점점 더 복잡해지고 있다(또는 어쩌면 코코넛의 털이 자라고 있는걸지도). 이 암호같은 응답은 설명을 필요로 한다. 그런데 모든 코코넛이 동일하게 인코딩되는 걸까? 다른 모양의 문자열 코코넛들에 대해 살펴보자:

127.0.0.1:6379> SET swallow:0 "0"  
OK  
127.0.0.1:6379> SET swallow:1 "An oversized and thickly-haired coconut"  
OK  
127.0.0.1:6379> SET swallow:2 "Ok, this is the mother of all coconuts - it is something that would make Donkey Kong run back to his mama in tears"  
OK  
127.0.0.1:6379> OBJECT ENCODING swallow:0  
"int"
127.0.0.1:6379> OBJECT ENCODING swallow:1  
"embstr"
127.0.0.1:6379> OBJECT ENCODING swallow:2  
"raw"

각각의 코코넛이 모두 다릅니다. Redis가 서로 다른 인코딩을 사용했습니다. "int" 인코딩은 LONGMIN과 LONGMAX 사이의 정수 값을 효율적으로 저장하기 위해 사용되며 shared.integers 구조를 활용해 데이터 중복을 막습니다. 따라서 약간의 공간을 더 사용합니다. 39 바이트보다 긴 문자열은 "raw"로 저장되는 반면, 짧은 것은 "embstr" 인코딩을 사용합니다(이 마법의 숫자는 redis.h의 REDIS_ENCODING_EMBSTR_SIZE_LIMIT에 정의되어 있습니다).

Going (coco)nuts

다른 코코넛 구조들은 어떨까요? 문자열은 Redis가 제공하는 가장 간단한 데이터 구조이며 내부적으로 다른, 좀 더 진보된 구조를 만드는데 사용됩니다. Hash는 각 엔트리가 하나의 linked list인, 딕셔너리 데이터 구조와 함께 추가된 문자열 뭉치(필드와 값)로 구성됩니다... 그러나 완전히 ziplist로 인코딩될 수도 있습니다. 그리고 리스트에 대해 말하자면, Sets와 Sorted Sets에 의해 사용되는 linked list와 ziplist(그리고 아마 Matt Stancliff @ mattsta의 quicklist)가 있습니다.

Redis의 데이터 인코딩에 대한 미묘한 복잡함과 그들이 메모리 소비에 어떠한 영향을 주는지 계속해서 알아볼 수 있지만, 우리 모두를 지루하게 만들어 조만간 곧 죽게 만들 것 같아 두렵군요. 대신, 소스 코드를 체크아웃하여 계속 읽어볼 수 있습니다. 누군가는 그렇게 하리라는 것을 알지만, 어느 정도의 프로그래밍 스킬이 필요할 것입니다.

아직 알지 못하는, Redis가 필요로 하는 RAM은 얼마나 되는가라는 질문이 아직 남아 있습니다. 그리고 그보다는 작은 궁금증인 코코넛이 필요로 하는 RAM은 얼마나 되는가라는 질문도. 그러나 뜻이 있는 곳에 길이 있습니다. 저와 사용 가능한 채널을 통해 얼마든지 연락할 수 있습니다. 언제나 환영합니다 🙂

사이드 노트: 이어서 계속