원문: https://github.com/sripathikrishnan/redis-rdb-tools/wiki/Redis-RDB-Dump-File-Format
Redis의 *.rdb 파일은 인 메모리(in-memory) 저장소의 바이너리 표현이다. 바이너리 파일은 Redis의 상태를 완벽히 복원하기에 충분하다.
rdb파일 포맷은 빠른 읽기와 쓰기를 위해 최적화되어 있다. LZF가 가능한 곳(환경)에서는 파일 사이즈를 줄이기 위해 압축이 사용된다. 일반적으로, 오브젝트는 길이 정보가 앞쪽에 위치해서, 오브젝트를 읽기 전에 얼마만큼의 메모리를 할당해야할지를 정확히 알 수 있다.
빠른 읽기/쓰기를 위해 최적화하는 것은 디스크상(on-disk)의 포맷이 가능한한 인 메모리(in-memory)표현과 가까워야 한다는 것을 의미한다. 이것은 rdb 파일이 취하는 접근법이다. 따라서, Redis 데이터 구조의 인 메모리(in-memory)표현에 대한 어느 정도의 이해가 없이는 rdb파일을 파싱할 수 없다.
At a high level, the RDB file has the following structure
고수준에서, RDB 파일은 다음과 같은 구조를 가진다.
----------------------------# RDB는 바이너리 포맷이다. 파일내에 새로운 행(new line)이나 공백은 없다.
52 45 44 49 53 # 매직 스트링(Magic String)은 "REDIS"이다.
30 30 30 37 # 4자리의 ASCCII RDB 버전 숫자. 여기서는 version = "0007" = 7
----------------------------
FE 00 # FE = 데이터베이스 선택자를 표시하는 코드. db number == 00
----------------------------# 키-값 쌍이 시작됨
FD $unsigned int # FD는 "초 단위의 만료시간을 표기한다". 그 후, 만료 시간은 부호없는 4byte의 정수로 읽힌다.
$value-type # 1 byte 플래그는 값(value)의 타입을 표시한다. - set, map, sorted set 등등
$string-encoded-key # 레디스 문자열(string)으로 인코딩된 키(key)
$encoded-value # 값(value). 값의 타입($value-type)에 따라 다르다.
----------------------------
FC $unsigned long # FC는 밀리초(ms)의 만료 시간을 나타낸다. 그 후, 만료 시간은 8byte의 부호없는 long 타입으로 읽혀진다.
$value-type # 값(value)의 타입을 나타내는 1 byte의 플래그. set, map, sorted set, etc..
$string-encoded-key # 레디스 문자열 (redis string)으로 인코딩된 키 (key)
$encoded-value # $value-type에 따라 인코딩된 값 (value)
----------------------------
$value-type # 이 키-값(key-value) 쌍은 만료 시간을 가지지 않는다, $value_type이 FD, FC, FE, FF가 아닌 것을 보장한다.
$string-encoded-key
$encoded-value
----------------------------
FE $length-encoding # 이전 db가 끝나고, 다음 db가 시작된다. 데이터베이스 번호는 인코딩 길이 (length-encodig)를 사용해서 읽어진다.
----------------------------
... # 이 데이터베이스에 대한 키-값 쌍들, 그리고 추가적인 데이터베이스
FF ## RDB파일의 끝에 대한 지시자
8 byte checksum ## 전체 파일의 CRC 64 체크섬(checksum)
파일은 매직 문자열 "REDIS"와 함께 시작된다. 이것은 우리가 rdb 파일을 다루고 있음을 알기 위한, 빠른 새너티 체크(sanity check)이다.
52 45 44 49 53 # "REDIS"
다음 4byte는 rdb 포맷의 버전 번호를 저장한다. 4byte는 아스키 문자열로 해석된 다음, 정수형으로 변환되는데, 이때 문자열과 정수형 컨버전을 이용한다.
00 00 00 03 # Version = 3
레디스 인스턴스는 여러 개의 데이터베이스를 보유할 수 있다.
단일 byte 0xFE
는 데이터베이스 선택자(selector)의 시작을 표시한다. 이 byte 다음에, 변수 길이 필드는 데이터베이스 번호를 표시한다. 데이터베이스 번호를 읽는 방법을 이해하려면, 다음 섹션인 "Length Encoding"을 참고하라.
데이터베이스 선택자 이후, rdb 파일은 키-값 쌍의 연속을 포함한다.
각 키-값 쌍은 4가지 부분으로 나뉜다 -
이 섹션은 1byte의 플래그로 시작한다. FD
는 만료 시간이 초 단위로 지정되었다는 것을 나타낸다. FC
는 만료 시간이 밀리초 단위로 지정되었다는 것을 나타낸다.
만약 시간이 밀리초로 지정된다면, 다음 8byte는 유닉스 타임(unix time)을 표현한다. 이 숫자는 초 또는 밀리초 정밀도의 유닉스 타임스탬프 (unix timestamp)이고, 이 키의 만료 시간을 나타낸다.
이 숫자가 인코딩되는 방법에 관해서는 "Redis Length Encoding" 섹션을 참고하라.
임포트 프로세스동안, 만료된 키는 폐기될 것이다.
이 1 byte 플래그는 값을 저장하기 위해 사용되는 인코딩을 나타낸다.
키는 단순히 레디스 문자열(Redis string)로 인코딩된다. 키가 인코딩되는 방법에 대해서 알고 싶다면, "String Encoding"를 참고하라.
값의 인코딩은 값 타입 플래그에 따라 달라진다.
길이 인코딩(Length encoding)은 스트림 내의 다음 오브젝트의 길이를 저장하는데 사용된다. 길이 인코딩은 가능한 한 더 적은 byte를 사용하도록 설계된 변수 byte 인코딩이다.
이것은 길이 인코딩이 동작하는 방법이다:
00
으로 시작하면, 그 다음 6비트는 길이를 나타낸다.01
로 시작하면, 스트림으로부터 추가적인 byte를 읽어야한다. 조합된 14비트로 길이를 나타낸다.10
으로 시작하면, 나머지 6비트는 버려진다. 스트림으로부터 추가적으로 4byte를 읽고, 이 4byte가 길이(RDB의 버전이 6에서는 빅 엔디안 포맷)를 나타낸다. 11
로 시작하며느 다음 오브젝트는 특별한 포맷으로 인코딩된다. 나머지 6비트는 이 포맷을 표시한다. 이 인코딩은 일반적으로 숫자를 문자열로 저장하거나, 인코딩된 문자열을 저장하기 위해 사용된다. "String Encoding"을 참고이 인코딩의 결과로 -
레디스 문자열(Redis Strings)은 바이너리에 안전하다. 이것은 무엇이든 레디스 문자열로 저장할 수 있다는 것을 의미한다. 어떤 특별한 end-of-string 토큰을 가지지 않는다. 레디스 문자열은 byte의 배열로 생각하는 것이 가장 좋다.
레디스에는 세 가지 타입의 문자열이 있다 -
길이가 접두사 문자열은 매우 간단하다. byte로된 문자열의 길이는 먼저 "길이 인코딩(Length Encoding)"을 이용해서 인코딩된다. 이 이후에, 문자열의 원시 byte(raw bytes)가 저장된다.
먼저 "길이 인코딩(Length Encoding)" 섹션, 특히 최상위 2비트가 11
인 부분을 읽는다. 이 경우, 남은 6비트를 읽는다.
만약 이 6비트의 값이 -
먼저 "길이 인코딩(Length Encoding)" 섹션, 특히 최상위 2비트가 11
인 부분을 읽는다. 이 경우, 남은 6비트를 읽는다.
만약 이 6비트의 값이 4라면, 이것은 압축된 문자열이 이어진다는 것을 의미한다.
압축된 문자열은 다음과 같이 읽는다 -
clen
을 스트림에서 "길이 인코딩 (Length Encoding)"을 이용해서 읽는다.clen
byte를 스트림에서 읽는다.레디스 리스트(redis list)는 문자열의 연속으로 표현된다.
size
의 크기를 스트림에서 "길이 인코딩 (Length Encoding)"을 이용해서 읽는다.size
문자열을 스트림에서 "문자열 인코딩 (String Encoding)"을 이용해서 읽는다.셋은 정확히 리스트처럼 인코딩된다.
size
의 크기를 스트림에서 "길이 인코딩 (Length Encoding)"을 이용해서 읽는다.size
의 크기를 스트림에서 "길이 인코딩 (Length Encoding)"을 이용해서 읽는다.2 * size
문자열을 스트림에서 "문자열 인코딩 (String Encoding)"을 이용해서 읽는다.2 us washington india delhi
는 맵 {"us" => "washington", "india" => "delhi"}
로 표현된다.NOTE : Zipmap 인코딩은 레디스 2.6부터 사용되지 않는다. 이제, 작은 해시맵은 ziplists를 이용해서 인코딩된다.
zipmap은 문자열로 직렬화된 해시맵이다. 본질적으로, 키-값 쌍은 연속적으로 저장된다. 이 구조에서 키를 찾는 것은 O(N)이다. 이 구조는 키-값 쌍의 수가 적을 때, 딕셔너리 대신 사용된다.
zipmap을 파싱하려면, 우선 문자열을 스트림에서 "문자열 인코딩 (String Encoding)"을 이용해서 읽는다. 이 문자열은 zipmap감싸여져 있다. 문자열의 내용은 zipmap으로 나타난다.
이 문자열 내의 zipmap 구조는 다음과 같다 -
<zmlen><len>"foo"<len><free>"bar"<len>"hello"<len><free>"world"<zmend>
Worked Example
18 02 06 4d 4b 44 31 47 36 01 00 32 05 59 4e 4e 58 4b 04 00 46 37 54 49 ff ..
18
은 문자열의 길이라는 것을 알 수 있다. 따라서, 우리는 그 다음 24byte를 읽을 것이고, 즉(i.e.) ff
까지이다.02 06...
으로 시작하는 문자열을 분석할 것이다.02
는 해시맵내의 엔트리 수이다.06
은 다음 문자열의 길이이다. 254보다 작기 때문에, 추가적인 byte를 읽지 않아도 된다.4d 4b 44 31 47 36
를 읽고, 키(key) "MKD1G6"을 얻는다.01
은 다음 문자열인 값(value)의 길이이다.00
은 여유(free) byte의 길이이다.0x32
를 읽는다. 그래서 값 "2"를 얻는다.05
는 다음 문자열의 길이이고, 이 경우에는 키(key)이다.59 4e 4e 58 4b
를 읽고, 키(key) "YNNXK"를 언든다.04
is the length of the next string, which is a value04
는 다음 문자열인 값(value)의 길이이다.00
은 값(value) 이후의 여유(free) byte의 수이다.46 37 54 49
를 읽고, 값 "F7TI"를 얻는다.FF
를 만나게 되고, 이것은 zipmap의 끝을 의미한다.{"MKD1G6" => "2", "YNNXK" => "F7TI"}
를 표현한다.ziplist는 문자열로 직렬화된 리스트이다. 본질적으로, 리스트의 엘리먼트들은 양방향으로 리스트를 효율적으로 순회할 수 있도록 플래그와 오프셋과 함께 연속적으로 저장된다.
ziplist를 파싱하려면, 우선 스트림에서 문자열을 "문자열 인코딩 (String Encoding)"을 이용해서 읽는다. 이 문자열은 ziplist로 감싸여져 있다. 문자열의 내용은 ziplist로 나타난다.
이 문자열 내 ziplist의 구조는 다음과 같다 -
<zlbytes><zltail><zllen><entry><entry><zlend>
255
이다. ziplist의 끝을 나타낸다.ziplist내의 각 엔트리는 다음과 같은 포맷을 가진다 :
<length-prev-entry><special-flag><raw-bytes-of-entry>
length-prev-entry : 이 필드는 이전 엔트리의 길이를 저장하는데, 만약 첫 번째 엔트리라면 0을 저장한다. 이것은 리스트를 역방향으로 쉽게 탐색할 수 있게 해준다. 이 길이는 1byte 또는 5byte로 저장된다. 만약 첫 byte가 253보다 같거나 작으면, 길이로 간주된다. 첫 byte가 254라면, 그 다음 4byte가 길이를 저장히기 위해 사용된다. 4byte는 부호없는 정수형으로 해셕된다.
Special flag : 이 플래그는 엔트리가 문자열인지 정수형인지를 표시한다. 또한, 문자열의 길이나 정수형의 크기를 표시하기도 한다.
이 플래그의 다양한 인코딩은 다음과 같다 :
Raw Bytes : 스페셜 플래그 (special flag) 이후, 엔트리의 원시 byte(raw bytes)가 이어진다. byte의 수는 이전 스페셜 플래그의 일부로서 결정된다.
Worked Example 1
23 23 00 00 00 1e 00 00 00 04 00 00 e0 ff ff ff ff ff ff ff 7f 0a d0 ff ff 00 00 06 c0 fc 3f 04 c0 3f 00 ff ...
| | | | | | | |
23
은 문자열의 길이이므로, 다음 35byte인 ff
까지 읽을 것이다.23 00 00 ...
으로 시작하는 문자열을 파싱할 것이다.23 00 00 00
는 이 ziplist의 전체 길이를 byte로 표현한다. 이것은 리틀 엔디안 포맷임을 주의하라.1e 00 00 00
는 마지막(tail) 엔트리의 오프셋을 나타낸다. 1e
= 30 이고, 이것은 0을 기준으로한 오프셋이다. 0번째 위치는 23
, 첫번째 위치는 00
, 그리고 기타 등등. 그렇게 04 c0 3f 00 ..
로 시작하는 마지막 엔트리까지 이어진다.04 00
는 리스트 내의 엔트리의 개수를 나타낸다.00
은 이전 엔트리의 길이를 나타낸다. 0은 이것이 첫 엔트리라는 것을 나타낸다.e0
은 스페셜 플래그이다. 이것은 비트의 패턴이 1110____
로 시작하기 때문에, 다음 8byte를 정수로 읽는다. 이것은 리스트의 첫 번째 엔트리이다.0a
는 이전 엔트리의 길이이다. 이전 10byte는 1byte(로 표현). 길이(length)를 나타내는 (1)byte + 스페셜 플래그 1byte + 정수 8byte.d0
은 스페셜 플래그이다. 이것은 비트의 패턴이 1101____
로 시작하기 때문에, 다음 4byte를 정수로 읽는다. 이것은 리스트의 두 번째 엔트리이다.06
은 이전 엔트리의 길이이다. 이전 6byte는 1byte(로 표현). 길이(length)를 나타내는 (1)byte + 스페셜 플래그 1byte + 정수 4byte.c0
은 스패셜 플래그이다. 이것은 비트의 패턴이 1100____
로 시작하기 때문에, 다음 2byte를 정수로 읽는다. 이것은 리스트의 세 번째 엔트리이다.04
는 이전 엔트리의 길이이다.c0
은 2byte의 숫자를 나타낸다.ff
를 만나며, 이것은 ziplist내의 모든 엘리먼트를 소비했다는 것을 말해준다.[0x7fffffffffffffff, 65535, 16380, 63]
을 저장한다.Intset은 정수의 이진 탐색 트리이다. 이 이진 트리는 정수의 배열로 구현된다. 집합(set)의 모든 엘리먼트가 정수일 때, Intset은 사용된다. Intset은 최대 64비트의 정수까지 지원한다. 최적화로써, 만약 정수가 작은 바이트로 표현될 수 있다면, 정수의 배열은 16 또는 32비트로 구성될 수 있다. 새로운 엘리먼트가 입력될 때의 구현은 필요하다면 업그레이드하도록 관리한다.
Intset은 이진 탐색 트리이기 때문에, 이 집합내의 숫자는 항상 정렬된다.
Intset은 집합(set)의 외부(external) 인터페이스를 가지고 있다.
Intset을 파싱하려면, 먼저 스트림에서 "문자열 인코딩 (String Encoding)"을 이용해서 문자열을 읽는다. 이 문자열은 Intset으로 감싸여져 있다. 이 문자열의 내용은 Intset으로 표현된다.
이 문자열내에서, Intset은 매우 단순한 레이아웃을 가진다 :
<encoding><length-of-contents><contents>
Example
14 04 00 00 00 03 00 00 00 fc ff 00 00 fd ff 00 00 fe ff 00 00 ...
14
는 문자열의 길이이므로, 다음 20byte인 00
까지 읽을 것이다.04 00 00 ...
로 시작하는 문자열을 해석하기 시작한다.04 00 00 00
는 인코딩이다. 이것은 4로 평가되기 때문에, 4바이트의 정수로 다루어야한다는 것을 알 수 있다.03 00 00 00
는 컨텐츠의 길이이다. 그러므로, 각각 4바이트의 long타입인 정수 3개를 다루어야한다는 것을 알 수 있다.0x0000FFFC, 0x0000FFFD, 0x0000FFFE
처럼 보인다. 정수는 리틀 엔디안 포맷. 즉, 최하위 비트가 첫번째로 오는 것에 주의해야한다.ziplist로 인코딩된 정렬된 셋(sorted set)은 위에서 설명한 Ziplist같이 저장된다. 정렬된 셋내의 각 엘리먼트는 ziplist의 점수(score)로 이어진다.
Example
['Manchester City', 1, 'Manchester United', 2, 'Tottenham', 3]
보다시피, 점수(score)는 각 엘리먼트에 뒤따른다.
여기에서, 해시맵의 키=값
쌍은 ziplist내에 연속적인 엔트리로 저장된다.
Note : This was introduced in rdb version 4. This deprecates zipmap encoding that was used in earlier versions.
Example
{"us" => "washington", "india" => "delhi"}
이것은 ziplist 안에서 다음과 같이 저장된다 :
["us", "washington", "india", "delhi"]
RDB 버전 5부터, 8byte CRC 64 체크섬(checksum)이 파일 끝에 추가되었다. redis.conf내의 파라미터를 통해서 이 체크섬을 비활성화시키는 것이 가능한다.
체크섬이 비활성화되면, 이 필드는 0이 될 것이다.