RDB는 snapshot같이 수행한 순간에 데이타를 파일에 기록합니다.
여기서 문제점이 있는데 기록하는 순간에 컴퓨터 파워가 나가거나,
kill -9 로 강제 종료한다면, 마지막 정상적인 rdb파일 이후에 들어온 데이타는 잃어버리게 됩니다.
그래서 append-only-fie을 사용하게 됩니다.
제가 생각한 AOF개념은 데이타를 파일에 쓸때 모든데이타를 한번에 쓰지말고( RDB), 버퍼를 만들어서
일정크기, 일정시간동안 입력들어온 데이타는 버퍼에 기록하다가 파일에 붙여 넣자입니다.
소스를 보니까 파일에 기록된 것 이후에 들어온 query는 파일에 append도 하지만
fork후 child process가 현재 dict에 저장되어있는 데이타를 파일에 기록하고,
기록 하는 동안에 client의 명령어들을 aof_buf, aof_rewrite_buf_blocks에 저장하는 로직도 있습니다.
redis.io에서 말하기는 파일에 기록된 것 이후에 데이타만 append 하면
[set kang 1, set kang 2, set kang 3... set kang 1000] 같은 명령어들이 많이 들어오면
AOF 파일 크기가 어마어마하게 커질 수 있는데, 그 이유로 인해 특정 조건이 충족되면 모든 dict의 데이타를
순회하면서 파일에 기록합니다.
여기서 말하는 특정조건은 이전 AOF 파일의 크기가 정해진 비율이상으로 증가 된 경우,
BGREWRITEAOF명령어를 수행한 경우를 말합니다.
AOF에 대해 보다 보니 생각나는 사물이 있었습니다.
바로바로
물은 데이타, 바닥은 파일, 물레방아에 있는 홉들이 버퍼라고 생각하면 딱이네요. 역시....우리 조상님...
AOF와 관련된 소스는 aof.c, bio.c, redis.c입니다.
Redis에서 파일에 기록하는 데이타는 명령어들로 이루어져있습니다.
그래서 client에서 명령이 들어 올때마다 아래 작업을 진행합니다.
redis.c:processCommand 에서 call(c, flags)를 호출 합니다.
call 내에서는 command가 dirty를 증가 시키는 거면 aof를 체크하고 propagate를 수행합니다.
propagate 함수는 AOF와 Replication을 수행합니다. 지금은 AOF만 보겠습니다.
(replication 포스트 쓸때는 여기서 부터 진행하면 되겠네요...흐흐)
propagate는 feedAppendOnlyFile를 호출 합니다.
client에서 보낸 데이타를 임시 메모리에 저장하는 함수가 되겠습니다.
두 개의 경우가 있는데 AOF_ON상태이면 server.aof_buf에 저장하고(sds), 현재 child process가 사전을 탐색하면서
파일에 쓰는 작업 중인경우에는 server.aof_rewrite_buf_blocks(list)에 저장합니다.
언뜻 보면 둘중에 하나만 해도될것같은데 if...else, if...if로 한 이유는 child process 작업 중이라
server.aof_rewrite_buf_blocks에만 기록 하고있는데, 서버가 중지되면, 그 사이의 내용은 사라지게 됩니다.
(server.aof_rewrite_buf_blocks은 child process가 종료되어야만 파일에 기록됩니다.)
그래서 aof_buf에도 기록하고, aof_rewrite_buf_blocks에도 기록합니다.
맨 먼저 보이는 게 기존에 사용하는 디비 번호와 다른 디비번호인 경우 select command를 저장해주는 부분이 보입니다.
그리고 다음에는 expire command냐, expire이고 set command이냐, 그리고 나머지냐? 로 나누어
buf에 명령어를 저장합니다.
client가 명령어를 날리고, 그걸 AOF에 쓸 수 있게 가공해서 임시 buf에 저장까지 했습니다.
이젠 이 임시 buf를 server의 버퍼(server.aof_rewrite_buf_blocks)에 저장하는
aofRewriteBufferAppend 함수를 호출합니다.
aofRewriteBufferAppend 함수의 기능은 위에서 가공한 buf를 server.aof_rewrite_buf_blocks에 저장 합니다.
aof_rewrite_buf_blocks은 List형으로 되어있고, 한 노드는 aofrwblock 로 이루어졌습니다.
마지막 노드로 이동해서 남은 공간에 buf를 저장하고, 그래도 buf가 남았다면 다시 block을 만들어서 저장합니다.
여기까지가 client가 명령어를 날리고 그 명령어가 server.aof_rewrite_buf_blocks에 저장되는 과정을 알아보았습니다.
redis.c:serverCron에서 AOF관련 로직에 대해 살펴보기 전에 몇가지 설명하고 진행하겠습니다.
rewriteAppendOnlyFileBackground function은 process를 하나 만들어서 child 가 현재 dict의 데이타를 훑으면서 파일에 기록합니다.
소스 보시면 아시겠지만 temp-rewriteaof-%d.aof파일에 기록하고 완료되면 temp-rewriteaof-bg-%d.aof로
파일명을 변경합니다.
그리고 bio.c의 기능을 사용하는데 background I/O의 줄임말로, main thread에서 fsync나 close를 직접 수행하면 block이되기
때문에 bio를 만든것같습니다. thread 두개를 만들어서 하나는 file descriptor를 close하는 Job을 담당,
다른 하나는 file descriptor 를 fsync 하는 Job을 담당합니다. 각각의 Job들은 Queue형식으로 저장을 합니다.
redis.c:serverCron에서 AOF관련 로직은 다음과 같습니다.
마지막으로 Redis 는 fsync를 always, everysec, no 설정을 사용자가 수정 가능하도록 만들었습니다.
write(fd, buf, strlen(buf) 한다고 바로 파일에 기록 되는게 아니고, Kernel 내부 버퍼에 기록을 한다고 합니다.
(대충 알고있는건데 자세히 아시는분은 설명 부탁드려요....굽신굽신)
그래서 이 버퍼에 있는걸 실제 파일에 기록하는 명령이 fsync입니다. 이 작업을 파일에 쓸 때마다 할거냐,
아니면 매 초 할거냐(정확히 1초단위는 아니에요.), 아니면 알아서 파일에 저장하도록 냅둘것 이냐를 정합니다.
flushAppendOnlyFile, backgroundRewriteDoneHandler에 로직이 있으니,
성능상 수정 하실 분들은 참고하시면 좋을 것 같습니다.