Redis RDB SAVE

Redis Server Course 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
    그림 1-1   RDB 저장 주요 흐름도
  • 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
    그림 1-2   RDB 데이터 타입 저장 rdbSaveObjectType
    다음은 rdb.h에 정의되어 있는 Data/Encoding Type입니다.
    #define RDB_TYPE_STRING 0
    #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
    HASH_ZIPMAP은 현재 RDB 버전 7에서 저장 시에는 사용되고 있지 않고, 과거 버전 RDB 파일을 로딩할 때 사용하며, Zipmap은 Ziplist으로 바뀌어서 저장됩니다.
  • 이 Data/Encoding Type은 rdbSaveType()에서 1 바이트로 저장됩니다.
처리 순서상으로는 키를 저장하는 rdbSaveStringObject()를 먼저 설명해야겠으나,   데이터 타입별로 구분해서 처리하는 방식이 바로 위에서 설명한 데이터 타입을 저장하는 rdbSaveObjectType()과 같아 이해하기 쉬울 것이므로, 값을 저장하는 rdbSaveObject()부터 먼저 설명합니다.

값을 저장하는 rdbSaveObject

값은 각 데이터/인코딩 타입별로 구분되어 저장되며, 크게 세 가지로 구분할 수 있다.

  • 스트링(Strings)는 이 단계에서는 구분 없이 저장된다.

컬렉션(Lists, Sets, Sorted Sets, Hashes)은 값이 포인터로 연결되어 저장되는 방식과 메모리를 절약하기 위해서 배열 형태로 저장하는 방식이 있다.

  • 포인터로 연결되어 저장되는 방식은 값의 개수(바이트 수가 아님)를 먼저 저장한다.   Lists 일 경우 리스트 안에 있는 element(entry) 개수이다.   그리고 값을 하나씩 가져와서(listNext 또는 dictNext) 저장(rdbSaveStringObject) 한다.
  • 배열 형태(Ziplist, Intset)는 포인터를 가지고 있지 않으므로 한 번에 저장(rdbSaveRawString) 한다.

redis rdb saveobject
    그림 1-3   RDB rdbSaveObject
  • 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에서는 필드명과 값의 배열로 저장되어 있습니다.
각 인코딩 타입에 대한 데이터 구조, 메모리 사용량, 성능 등에 대한 자세한 설명은 다음을 참고하세요.

키를 저장하는 rdbSaveStringObject

주요 흐름도의 순서에 따라 rdbSaveStringObject()를 키는 저장하는 펑션으로 제목을 달았지만, 바로 위 섹션에서도 보았듯이, 이 펑션은 키, 값 구분 없이 데이터를 저장하는데 사용됩니다.

redis rdb savestringtype
    그림 1-4   RDB 키 저장 rdbSaveStringObject
  • 데이터가 정수인지 확인해서, 정수이면 rdbSaveLongLongAsStringObject()를 호출합니다.   정수가 아니면 rdbSaveRawString()를 호출해서 수행합니다.
  • 이어서 rdbSaveRawString()를 설명드리겠습니다.

rdbSaveRawString

redis rdb saverawstring
    그림 1-5   RDB rdbSaveRawString
  • 데이터 길이가 11이하면, 정수로 변환을 시도합니다.   성공하면 정수로 저장하고, 아니면 다음 흐름을 이어갑니다.
  • 데이터 길이가 20보다 크면, rdbSaveLzfStringObject()으로 압축을 합니다.   이 펑션은 이어지는 섹션에서 설명합니다.
  • 20이하면, 그대로 저장합니다.

rdbSaveLzfStringObject

LZ 압축은 1977년 Lempel 과 Ziv가 고안해낸 알고리즘으로 레디스에서는 Marc Alexander Lehmann가 만든 것을 사용합니다.   압축 속도가 매우 빠르므로 실시간 압축을 해야 하는 레디스에서 사용하기에 적합합니다.

redis rdb savelzfstringobject
    그림 1-6   RDB rdbSaveLzfStringObject
  • 자체적으로 길이 4이하는 압축하지 않고 종료합니다.
  • lzf_compress()으로 압축합니다.
  • 압축된 데이터라는 것을 표시하기 위해 16진수로 'C3'을 저장합니다.
  • 압축된 데이터 길이를 저장합니다.
  • 원래 데이터 길이를 저장합니다.
  • 압축된 데이터를 저장합니다.
  • 그런데, 길이가 20 이상이라도 압축되지 않을 경우에는 압축하지 않고 그대로 리턴합니다.   예를 들어, "ABC... XYZ"는 26자지만 모두 다른 문자이므로 압축되지 않아 그대로 저장됩니다.

펑션 관계도

redis rdb function flow
    그림 1-7   RDB Save function flow

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로 세팅된다.
아래는 save 명령 완료 후 마지막 시점에 세팅되는 값이다.
server.dirty = 0;
server.lastsave = time(NULL);
server.lastbgsave_status = REDIS_OK;

<< AOF Backup RDB SAVE RDB BGSAVE >>

조회수 :

Email 답글이 올라오면 이메일로 알려드리겠습니다.