오늘 제대로 알고 있지 못한 부분에 대해서 한 독자님의 지적을 받고… AOF 관련 코드를 유심히, 그리고 좀 자세히 보게 되었습니다. 그런데… 음… 제가 완전히 잘못 알고 있던 부분이 있었습니다.
일단, 저는 Redis 의 AOF 가 DB의 WAL(Write ahead Log) 의 변종이라고 생각하고 있습니다. 먼저 Write ahead Log 에 대해서 아주 간략하게 설명하자면…(이번에 조사하면서 WAL조차도 잘못 이해하고 있었다는 걸 알았습니다. T.T)
데이터의 변경이 발생하기 전에 이 변경사항에 대한 Log를 남기고, 이를 이용해서 Data의 durability 를 보장하는 방법입니다. 디비등에서는 실제 데이터 영역의 변경을 하기 전에, 이에 대한 변경 사항을 commit시에 Log로 남기고, 이를 이용해서 나중에 실제 데이터 영역을 변경하기 위해서 사용하기도 합니다.
여기서 중요한 부분은 Log 가 persistent 할 수도 있고, 아닐 수도 있다는 점입니다. 왜냐하면 매번 disk에 write가 발생하면, 느려질테니깐요. 그래서 보통 Log를 Buffering 하고 이를 한꺼번에 쓰는 형태의 작업을 하게됩니다. 그런데… 여기서 이제부터 일반적으로 고민이 시작되는거죠.
DB의 데이터는 중요하다. 그런데 insert, update, delete등에 의해서 변경이 벌어지는데, 이것이 log buffer에 쌓이고, 실제 디스크에 쓰여지지 않는다면, 데이터의 유실이 발생할 수도 있는 것입니다.
그래서 mysql 의 innodb 의 경우는 innodb_flush_log_at_trx_commit 옵션을 이용해서 Disk에 flush 하는 주기를 조절하거나, 매번하도록 되어있습니다.(여기서의 기준은 하나의 Query Event 가 그 단위가 되는 것입니다.)
그럼 Redis 의 AOF는 무엇이 다르냐?
어떻게 보면, 거의 유사합니다. 하지만 다음과 같은 부분이 다릅니다.
1. AOF buffer 에 데이터를 남기는 시점이, 실제 메모리에 데이터가 변경된 이후이다.
-> 데이터의 메모리 변경 후에, 커맨드를 만들어서 AOF buffer 에 저장한다.
2. 그리고 실제 disk 에 flushing 하는 시점은 매 event loop의 시작 부분인 beforesleep 에서 동작한다.
-> 즉 AOF buffer 들어 있는 내용은, 하나의 event loop가 모두 끝난 다음에 디스크에 쓰여진다.
-> 각각의 버퍼는 각 명령 수행뒤에 propagation 에서 만들어짐.
-> Redis 는 single thread로 동작하기 때문에, 이 사이에 만약 1024개의 커맨드가 처리되었다고 한다면,
그 사이에 장애가 발생하면 해당 데이터를 돌릴 수 있는 방법은 없다.
여기서 잘못된 저의 오해는
1. Mysql처럼 Query 단위로, 데이터의 변경이 발생하기 전에 Logging이 되어야 된다고 생각
-> 그러나 실제로 Redis 에서는 커맨드 실행 후에, AOF buffer 만 만들어서 저장
-> event loop 전의 beforeSleep 에서 flushAppendOnlyFile 을 호출해서 AOF Buffer 를 Disk에 Flush 함.
그러면 옵션의 appendfsync 는 어떻게 동작하는가? 다음과 같습니다.
1. aof buffer 의 디스크에 쓰기는 오직 flushAppendOnlyFile()에서만 저장된다.
2. appendfsync가 no 면, 그냥 beforeSleep때 마다 os의 write를 호출하고, 실제 os와 disk간의 sync 는 os에
맡긴다.
3. EVERYSEC의 현재 fsync 작업이 스레드 큐에 존재하면, write를 하지 않고 return.
없으면 write 후에 fsync 를 타 스레드에 하도록 돌림.
만약 계속 fsync 작업이 남아있는걸로 판단하면, 그냥 write 함.
EVERYSEC 으로 되어있지만, 해당 cron 작업에 따라, 더 느려질 가능성도 존재.
4. ALWAYS의 경우, 매번 beforeSleep에서 디스크에 쓰고, fsync 도 동기로 호출
즉 Redis 의 AOF는 어떤 옵션을 쓰더라도, Write가 많을 경우에는 장애가 발생할 경우, 바로 직전의 명령이 아니라, 한 이벤트 루프 안에서 업데이트된 꽤 많은 데이터가 유실될 가능성도 있다는 걸 알아두고 사용하시면 좋을듯 합니다.