rdb_save
Redis RDB SAVE
![]() |
![]() Redis Technical Support |
![]() Redis Enterprise Server |
---|
Synchronization 명령 SAVE
주요 흐름
이 글은 레디스 Version 5.0.4, RDB Version 9 기준으로 작성되었습니다.
RDB save(저장)에 대한 내부 구조(internal)를 설명합니다.
레디스 Persistence와 RDB에 대한 일반적인 내용이 궁금하면 여기를 먼저 보세요.
Save 명령은 Sync 모드로 RDB 파일에 데이터를 저장합니다.
그러므로 이 명령이 수행되는 동안 서버는 클라이언트의 요청을 받지 못 합니다.
반면, Bgsave 명령은 자식 프로세스를 생성해서 백그라운드로 수행하므로 서버는 일을 계속 처리할 수 있습니다.
아래 그림은 save 명령으로 RDB 파일을 저장하는 주요한 기능들을 설명하는 흐름도입니다.
![redis rdb save main flow redis rdb save main flow](/images/internal/rdb_mainflow.png)
- Save 명령을 실행하면 우선 RDB를 저장하는 자식 프로세스가 이미 실행 중인지 확인해서, 이미 실행 중이면 클라이언트에 "백그라운드 저장이 이미 진행 중이다(Background save already in progress)"라는 메시지를 보내고 종료합니다.
- 진행 중이 아니면 rdbSave()를 수행합니다. 임시 파일명(temp-pid.rdb)으로 파일을 오픈하고, rdbSaveRio()를 호출합니다. rdbSaveRio()가 데이터를 RDB 파일에 저장하는 메인 function입니다.
- While loop로 진입해서, dictNext()로 키를 관리하는 데이터 구조체 dictionary(Hash Table)에서 다음 키를 하나씩 읽습니다. 키가 있으면 rdbSaveKeyValuePair(key, val, expire)를 호출합니다.
- rdbSaveKeyValuePair()에서는 우선 expire 시간을 보고 이미 expire 되었으면,
다음 키를 읽으러 while loop의 시작으로 갑니다.
버전 4.0.10부터는 만료된 키도 파일에 저장합니다.
이것은 rdb 파일 저장 중 키가 만료되면 DEL 명령이 발행되고
발행된 DEL 명령이 복제노드에도 에러 없이 실행하기 위해서 입니다.
여기서 잠깐, 만료된 키도 DB에 존재할 수 있습니다. 왜냐하면 만료된 키들을 삭제하는 function은 serverCron()에서 100millisecond마다 수행되기 때문입니다. - 이어서 encoding 타입을 저장하는 rdbSaveObjectType(val),
키를 저장하는 rdbSaveStringObject(key),
값을 저장하는 rdbSaveObject(val)이 차례대로 수행됩니다.
각각에 대해서는 좀 자세한 설명이 필요하므로, 이어지는 섹션에서 설명합니다. - 모든 키를 다 처리했으면, rioWrite(cksum)에서 Check Sum을 저장합니다. 이 Check Sum은 로딩시 파일이 변경되었는지 확인하는 용도로 사용됩니다.
- fflush(), fsync(), fclose(tmpfile)를 차례대로 수행하고, 마지막으로 rename(tmpfile, filename)를 수행해서 임시파일명을 원래 RDB 파일명으로 변경합니다.
- 클라이언트에는 "OK"와 수행 시간을 초로 표시해줍니다.
아래 예는 키 개수가 7백만 개이고 RDB 파일 크기가 3.1GB인 경우입니다.
127.0.0.1:5001> save
OK
(35.95s)
데이터 타입을 저장하는 rdbSaveObjectType
RDB에 데이터를 저장할 때 처음으로 하는 것은 Data/Encoding Type을 저장합니다.
RDB Object Type이라고 표현하는 것이 더 나을 것 같습니다.
레디스는 크게 5가 데이터 타입이 있고, 컬렉션 타입은 다시 두 가지로 나누어집니다.
RDB는 바이너리 형태로 저장하므로, 로딩(Loading) 할 때 이 타입을 보고 그에 맞는
Data/Encoding 형태로 복원합니다.
Lists에서 레디스 버전 3.0.7까지는 Linkedlist와 Ziplist로 구분했는데,
버전 3.2부터는 Quicklist 한 가지만 사용되므로, 두 가지로 구분되지 않고,
Quicklist 한 가지만 저장됩니다.
![redis rdb saveobjecttype redis rdb saveobjecttype](/images/internal/rdb_saveobjecttype.png)
-
다음은 rdb.h에 정의되어 있는 Data/Encoding Type입니다.
- 이 Data/Encoding Type은 rdbSaveType()에서 1 바이트로 저장됩니다.
#define RDB_TYPE_LIST 1
#define RDB_TYPE_SET 2
#define RDB_TYPE_ZSET 3
#define RDB_TYPE_HASH 4
#define RDB_TYPE_ZSET_2 5
#define RDB_TYPE_MODULE 6
#define RDB_TYPE_MODULE_2 7
#define RDB_TYPE_HASH_ZIPMAP 9
#define RDB_TYPE_LIST_ZIPLIST 10
#define RDB_TYPE_SET_INTSET 11
#define RDB_TYPE_ZSET_ZIPLIST 12
#define RDB_TYPE_HASH_ZIPLIST 13
#define RDB_TYPE_LIST_QUICKLIST 14
#define RDB_TYPE_STREAM_LISTPACKS 15
값을 저장하는 rdbSaveObject
값은 각 데이터/인코딩 타입별로 구분되어 저장되며, 크게 세 가지로 구분할 수 있다.
- 스트링(Strings)는 이 단계에서는 구분 없이 저장된다.
컬렉션(Lists, Sets, Sorted Sets, Hashes)은 값이 포인터로 연결되어 저장되는 방식과 메모리를 절약하기 위해서 배열 형태로 저장하는 방식이 있다.
- 포인터로 연결되어 저장되는 방식은 값의 개수(바이트 수가 아님)를 먼저 저장한다. Lists 일 경우 리스트 안에 있는 element(entry) 개수이다. 그리고 값을 하나씩 가져와서(listNext 또는 dictNext) 저장(rdbSaveStringObject) 한다.
- 배열 형태(Ziplist, Intset)는 포인터를 가지고 있지 않으므로 한 번에 저장(rdbSaveRawString) 한다.
![redis rdb saveobject redis rdb saveobject](/images/internal/rdb_saveobject.png)
- rdbSaveStringObject()는 다음 섹션에서 자세히 설명합니다.
- Lists의 Linkedlist는 rdbSaveLen(listLength(list))으로 리스트의 엔트리 개수를 저장하고, 값을 하나씩 가져와서 저장(rdbSaveStringObject) 합니다.
- Lists의 Ziplist는 ziplistBlobLen()으로 바이트수(이때는 엔트리 개수가 아님)를 저장하고, 배열 형태이므로 rdbSaveRawString()로 Ziplist 통째로 저장합니다. 압축 저장 여부에 대해서는 rdbSaveRawString에서 설명합니다.
- Set, Sorted Sets, Hashes는 위와 비슷한 방식으로 자정합니다. 다만 Sorted Sets는 값과 스코어를 각각 저장하고, Hashes는 필드명과 값이 있으므로 이것도 각각 저장합니다.
- Ziplist는 Lists, Sorted Sets, Hashes에서 사용되는데, Ziplist의 데이터 구조는 같지만,
각 데이터 타입에 따라 조금씩 다르게 저장됩니다.
Lists에서는 입력한 순서대로 저장되어 있고, Sorted Set에서는 스코어와 값의 배열로 저장되며 스코어로 정렬되어 있습니다. Hashes에서는 필드명과 값의 배열로 저장되어 있습니다.
- Strings
- Lists: Linkedlist, Ziplist, Quicklist <퀵리스트는 버전 3.2부터 적용됩니다>
- Sets: Hash Table, Intset
- Sorted Sets: Skiplist, Ziplist
- Hashes: Hash Table, Ziplist
키를 저장하는 rdbSaveStringObject
주요 흐름도의 순서에 따라 rdbSaveStringObject()를 키는 저장하는 펑션으로 제목을 달았지만, 바로 위 섹션에서도 보았듯이, 이 펑션은 키, 값 구분 없이 데이터를 저장하는데 사용됩니다.
![redis rdb savestringtype redis rdb savestringtype](/images/internal/rdb_savestringobject.png)
- 데이터가 정수인지 확인해서, 정수이면 rdbSaveLongLongAsStringObject()를 호출합니다. 정수가 아니면 rdbSaveRawString()를 호출해서 수행합니다.
- 이어서 rdbSaveRawString()를 설명드리겠습니다.
rdbSaveRawString
![redis rdb saverawstring redis rdb saverawstring](/images/internal/rdb_saverawstring.png)
- 데이터 길이가 11이하면, 정수로 변환을 시도합니다. 성공하면 정수로 저장하고, 아니면 다음 흐름을 이어갑니다.
- 데이터 길이가 20보다 크면, rdbSaveLzfStringObject()으로 압축을 합니다. 이 펑션은 이어지는 섹션에서 설명합니다.
- 20이하면, 그대로 저장합니다.
rdbSaveLzfStringObject
LZ 압축은 1977년 Lempel 과 Ziv가 고안해낸 알고리즘으로 레디스에서는 Marc Alexander Lehmann가 만든 것을 사용합니다. 압축 속도가 매우 빠르므로 실시간 압축을 해야 하는 레디스에서 사용하기에 적합합니다.
![redis rdb savelzfstringobject redis rdb savelzfstringobject](/images/internal/rdb_savelzfstringobject.png)
- 자체적으로 길이 4이하는 압축하지 않고 종료합니다.
- lzf_compress()으로 압축합니다.
- 압축된 데이터라는 것을 표시하기 위해 16진수로 'C3'을 저장합니다.
- 압축된 데이터 길이를 저장합니다.
- 원래 데이터 길이를 저장합니다.
- 압축된 데이터를 저장합니다.
- 그런데, 길이가 20 이상이라도 압축되지 않을 경우에는 압축하지 않고 그대로 리턴합니다. 예를 들어, "ABC... XYZ"는 26자지만 모두 다른 문자이므로 압축되지 않아 그대로 저장됩니다.
펑션 관계도
![redis rdb function flow redis rdb function flow](/images/internal/rdb_functionflow.png)
info persistence
info persistence 명령으로 RDB와 AOF 정보를 볼 수 있는데, 이중 두 가지가 save 명령과 직접 관계있다.
- rdb_changes_since_last_save: 6
이것은 save 명령 수행 이후에 변경된 데이터 개수이다. 키 단위가 아니고 엔트리 단위이다. 예를 들어 rpush key AAA BBB는 2를 증가시킨다.
이는 server.dirty 값을 보여준다. - rdb_last_save_time: 1462492686
저장이 완료된 시점의 Unix time이다. - rdb_last_bgsave_status: ok
bgsave로 되어 있지만, save 명령 수행 후에 OK로 세팅된다.
server.lastsave = time(NULL);
server.lastbgsave_status = REDIS_OK;
<< AOF Backup | RDB SAVE | RDB BGSAVE >> |
---|