...
Redis 데이터의 영속성 (Redis Persistence)
Redis는 In-memory DB 임에도 불구하고, 메모리 데이터를 disk에 저장할 수 있는 특징이 있다.
그래서 서버가 꺼진 후 restart되더라도, disk에 저장해놓은 데이타를 다시 읽어서 메모리에 로딩하기 때문에 데이타 유실되지 않는다.
이런 Persistent(영속성) 기능은 휘발성 메모리 DB를 데이터 스토어로서 활용한다는 장점이 있지만 이 기능 때문에 장애의 주 원인이 되기도 한다.
그러므로 이 기능을 잘 알아보고 사용하는게 중요하다.
redis에서는 데이타를 저장하는 방법이 RDB (snapshotting) 방식과 AOF (Append only file) 두가지가 있다.
- RDB 방식은 특정한 각격마다 메모리에 있는 레디스 데이터 전체를 디스크에 쓰는 것이다. (백업에 용이)
- AOF 방식은 명령이 실행될때 마다 데이터를 파일에 기록하여 데이터의 손실이 거의 없다.
이 두가지에 대해 상세히 알아보고 차이점과 활용법을 알아보자.
RDB (snapshotting) 방식
Redis는 인메모리 데이터를 주기적으로 파일에 저장하는데, Redis 프로세스가 장애로 인해 종료되더라도 해당 파일을 읽어들이면 이전의 상태를 동일하게 복구할 수 있다.
우리가 직접 세팅하지 않더라도 Redis는 자동으로 .rdb 라는 확장자의 파일에 인메모리 데이터를 저장하도록 디폴트 설정 되어있다.
즉, 기본적으로 설정되어 있는 백업 방식으로, 위의 dump.rdb 파일 또한 RDB 방식으로 생성된 것이다.
RDB(snapthot)는 순간적으로 메모리에 있는 내용을 스냅샷을 떠서 DISK에 옮겨 담는 방식이다.
스냅샷을 뜬다는 말은 특정 시점의 메모리에 있는 데이터를 바이너리 파일로 저장하는 뜻이다.
스냅샷이라는 용어는 Git이나 AWS를 배워보신 분들이라면 익숙한 용어일 것이다. 그래서 특정 시점으로 데이터를 복구하는 것이 가능하며, 레디스 데이터의 버저닝 또한 가능하다.
.rdb 파일은 AOF 파일보다 사이즈가 작다는 특징이 있다. 따라서 로딩 속도가 AOF 보다 빠르다.
RDB 는 RDBMS 라는 오해를 많이 받지만 RDB 는 단순히 메모리의 스냅샷을 파일 형태로 저장할 때 쓰는 파일의 확장자명이다.
스냅샷은 바이너리 파일 형태로 저장되어서 직접 읽을 순 없다.
RDB 방식은 메모리의 snapshot을 그대로 저장하기 때문에 서버를 재구동시할 때 snapshot을 다시 읽으면 되므로 속도가 빠른 장점이 있다.
그러나, snapshot을 추출하는데 시간이 오래걸리고 도중에 서버가 꺼지면 이후의 데이터를 모두 사라진다는 단점이 있다.
실제로 SAVE 옵션으로 50GB 의 메모리 상태를 저장한다면 7 ~ 8분 정도 소요된다고 한다.
RDB 설정 하기
RDB 기능을 이용하려면 Redis 설정 파일인 redis.conf 파일에 다음과 같은 내용을 추가해야한다. (윈도우 OS는 redis-windows-service.conf 를 수정하고 서비스 재시작 하면 적용된다)
RDB 설정 항목
설정항목 | 설정사례 | 설명 |
save | 900 1 | 900초(15분) 이후 1개의 쓰기 발생 |
save | 300 10 | 300초(3분) 이후 10개의 쓰기 발생 |
save | 60 10000 | 60초 이후 10,000개의 쓰기 발생 시 디스크에 데이터 복제 |
stop-writes-onbgsave-error | yes |
|
rdbcompression | yes | dump.rdb 파일을 LZF로 압축 |
rdbchecksum | yes |
|
dbfilename | dump.rdb | 변경 지정 가능 |
dir | ./ | dump.rdb 파일과 AOF 파일 생성 위치 |
저장 시점 정하기
RDB 저장 방식에는 SAVE와 BGSAVE 두 가지가 있다.
- SAVE는 순간적으로 redis의 동작을 정지시키고 그 snapshot를 디스크에 저장.(blocking 방식)
- BGSAVE는 백그라운드 SAVE 라는 의미로 별도의 자식 프로세스를 띄운 후, 명령어 수행 당시의 snapshot을 disk에 저장하고, redis는 동작을 멈추지 않게 된다. (non-blocking 방식)
SAVE 동작순서
1) Main process가 데이터를 새 RDB temp 파일에 쓴다.
2) 쓰기가 끝나면 기존 파일을 지우고, 새 파일로 교체한다.
BGSAVE 동작 순서
1) Child process를 fork() 한다.
2) Child process는 데이터를 새 RDB temp 파일에 쓴다.
3) 쓰기가 끝나면 기존 파일을 지우고, 이름을 변경한다.
SAVE 조건은 여러 개를 지정할 수 있고, 모두 or 조건이다.
즉 어느 것 하나라도 만족하면 저장한다.
만일 RDB 저장을 사용하지 않으려면 redis.conf 에서 SAVE를 모두 주석 처리하면 된다.
BGSAVE 방식은 fork를 하기 때문에 메모리를 거의 두배 가량 사용하므로 이에 주의해야 한다.
# save [Seconds] [Changes]
save 900 1 # 900초(15분) 동안 1번 이상 key 변경이 발생하면 저장
save 300 10 # 300초(5분) 동안 10번 이상 key 변경이 발생하면 저장
save 60 10000 # 60초(1분) 동안 10000번 이상 key 변경이 발생하면 저장
RDB 파일명 지정
디렉토리는 dir로 지정된 워킹디렉토리를 따른다.
dbfilename dump.rdb
RDB 저장 실패시 데이터 읽기 여부
RDB 파일 저장이 실패했을 경우 데이터를 받아 들일지 말지를 정하는 파라미터이다
stop-writes-on-bgsave-error yes
- 이 값이 yes 일때, 레디스는 RDB 파일을 디스크에 저장하다 실패하면, 모든 쓰기 요청을 거부한다.
쓰기에 문제가 발생했으니, 빨리 조치를 취하라는 의미다. default는 yes이다. - 이 값을 no 로 설정하면, 디스크 저장에 실패하더라도, 레디스는 쓰기 요청을 포함한 모든 동작을 정상적으로 처리한다.
디스크 쓰기에 실패하는 경우는 여유 공간이 부족하거나, 권한(permission) 부족, 디스크 물리적 오류 등이 있을 수 있다. - 이 파라미터는 save 이벤트에만 해당한다. BGSAVE 명령을 직접 입력했을 경우에는 해당하지 않는다.
Redis-cli 이용하기
RDB 관련 info 조회
> info persistence
# Persistence
loading:0
rdb_changes_since_last_save:0
rdb_bgsave_in_progress:0
rdb_last_save_time:1655100738
rdb_last_bgsave_status:ok
rdb_last_bgsave_time_sec:-1
rdb_current_bgsave_time_sec:-1
aof_enabled:0
aof_rewrite_in_progress:0
aof_rewrite_scheduled:0
aof_last_rewrite_time_sec:-1
aof_current_rewrite_time_sec:-1
aof_last_bgrewrite_status:ok
aof_last_write_status:ok
수동 SAVE 하기
redis.conf 파일에 설정해서 주기적으로 돌아가게 만들수 있지만, 직접 터미널에서 명령으로도 RDB 파일을 수동 생성할 수 있다.
바로 BGSAVE 또는 SAVE 명령어를 실행하면 된다.
> BGSAVE
Background saving started
AOF (Append Only File) 방식
AOF(Append On File) 방식은 redis의 모든 write/update 연산 자체를 모두 log 파일에 기록하는 형태이다.
default로 appendonly.aof 파일에 기록되며, 조회를 제외한 입력/수정/삭제 명령이 실행될 때 마다 기록된다
그리고 서버가 재시작될 때, log에 기록된 write/update 연산을 재 실행하는 형태로 데이터를 복구하는 방식이다.
즉, 다음과 같은 순서로 데이터가 저장된다.
- 클라이언트가 Redis 에 업데이트 관련 명령을 요청한다.
- Redis 는 해당 명령을 AOF에 저장한다.
- 파일쓰기가 완료되면 실제로 해당 명령을 수행해서 Redis 메모리에 내용을 변경한다.
이처럼, operation이 발생할때 마다 매번 기록하기 때문, RDB 방식과는 달리 특정 시점이 아니라 항상 현재 시점까지의 로그를 기록할 수 있으며, 기본적으로 non-blocking 으로 동작 된다.
AOF는 log 파일에 대해서만 append하기 때문에 log write 속도가 빠르고 어떤 시점에 서버가 다운되더라도 데이터가 사라지지 않는 장점이 있다.
RDB는 바이너리 파일이라서 수정이 불가능했지만, AOF 로그 파일은 text 파일이므로 편집이 가능하다.
Redis 데이터 복구 시나리오
그래서 만일 실수로 flushall 명령으로 메모리에 있는 데이터를 모두 날렸을 때, 레디스 서버를 shutdown하고 appendonly.aof 파일에서 flushall 명령을 제거 한 후 레디스를 다시 시작하면 데이터 손실없이 데이터를 살릴 수 있다.
그러나, 모든 write/update 연산을 log파일에 남기기 때문에 log 데이터 양이 굉장히 크고, 복구 시 저장된 모든 write/update연산을 다시 실행하기 때문에 재시작 속도가 느린 단점이 있다.
예를 들어 set 명령이 key는 같고 값을 다른 조건에서 여러번 수행되었다고 하면, 메모리에는 마지막 수행된 값만 남아있지만, AOF에는 수행된 모든 기록이 남아있기 때문이다.
그렇기에 AOF 방식은 rewrite 기능을 제공하여 특정 시점에 데이터 전체를 다시 쓰는 기능이 있다.
rewrite를 수행하면 이전 기록은 모두 사라지고 최종 데이터만 기록되기에 파일의 크기가 줄어들게 된다.
AOF 사용법
AOF(APPEND ONLY MODE) 항목
설정항목 | 설정사례 | 설명 |
appendonly | yes | AOF 파일 사용 여부 |
appendfilename | "appendonly.aof" | AOF 파일 이름 지정 |
appendfsync | always | 운영체제의 fsync( )에 의한 지연된 쓰기 옵션 (메모리 -> Disk)
|
no-appendfsync-on-rewrite | no | 쓰기 명령에 대한 fsync( ) 리턴 지연 시간이 너무 길어지면 운영체제의 간섭이 있을 수 있으므로, 스냅샷 생성을 위한 BGSAVE 또는 AOF 파일 기록을 위한 BGREWRITEAOF가 실행되고 있을 때는 fsync( )의 호출을 블록킹 함 |
auto-aof-rewrite-percentage | 100 | AOF 파일 초기화 및 재기록 시작을 위해 최초 AOF 파일의 크기를 기준으로 사용 비율을 지정(‘0’으로 지정 시 AOF 파일 초기화 없음) |
auto-aof-rewrite-min-size | 64mb | AOF 파일 초기화 및 재기록 시작을 위해 파일 크기를 지정(‘0’으로 지정 시 AOF 파일 초기화 없음) |
redis.conf 설정 방식
레디스는 서버가 시작될 때 데이터 파일을 읽는다.
이때 어떤 데이터 파일을 읽을지는 redis.conf 설정 파일의 appendonlyfile 설정을 따르게 된다.
appendonly yes
- appendonlyfile yes : aof 파일을 읽음
- appendonlyfile no : rdb 파일을 읽음
AOF 파일명 지정
Append only file 명을 지정하는 파라이터이다.
이 파라미터는 appendonly가 yes일때 적용된다.
이 파리미터는 config set 명령으로 변경할 수 없다.
appendfilename "appendonly.aof"
AOF에 기록되는 시점 지정
appendfsync는 appendonly 파일에 데이터가 쓰여지는 시점을 정하는 파라미터이다.
AOF 는 파일에 저장할 때 파일을 버퍼 캐시에 저장하고 적절한 시점에 이 데이터를 디스크로 저장하는데 appendfsync 는 디스크와 동기화를 얼마나 자주 할 것인지에 대해 설정하는 값으로 다음과 같이 3가지 옵션이 존재한다.
appendfsync everysec
파라미터 옵션
- always : 명령 실행 시 마다 AOF에 기록. 데이터 유실은 거의 없지만 성능이 매우 떨어짐.
- everysec : 1초마다 AOF에 기록. 권장
- no : AOF에 기록하는 시점을 OS가 정함(일반적으로 리눅스의 디스크 기록 간격은 30초). 데이터가 유실될 수 있음..
AOF Rewrite 설정
rewrite란 AOF 파일의 상태가 특정 조건(파일 사이즈가 얼마 이상) 일 때 AOF 파일을 현재 상태에 맞춰서 설정에 따라 덮어쓰기 하거나 새로 생성된다.
처음 레디스 서버가 시작할 시점의 AOF 파일 사이즈가 100% 이상 커지면 rewrite 하게 되어있다
만약 레디스 서버 시작 시 AOF 파일 사이즈가 0이었다면, auto-aof-rewrite-min-size를 기준으로 rewrite 한다
하지만, min-size가 64mb 이하이면 rewrite를 하지 않는데, 이는 파일이 작을 때 rewrite가 자주 발생하는 것을 방지하기 위함이다.
# AOF 파일 사이즈가 특정 퍼센트 이상 커지면 rewrite 한다.
# 비교 기준은 레디스 서버가 시작할 시점의 AOF파일 사이즈이다.
# 0으로 설정하면 rewrite를 하지 않는다
auto-aof-rewrite-percentage 100
# AOF 파일 사이즈가 64mb 이하면 rewrite를 하지 않는다.
# 파일이 작을때 rewrite가 자주 발생하는 것을 막아준다.
auto-aof-rewrite-min-size 64mb
AOF 파일을 이용한 복구하기
만일 명령어 실수로 redis db 데이터를 모두 날려먹었을 때에 복구 방법을 알아보자.
redis cli를 실행하고 키 a,b,c에 값을 대입한 뒤 flushall 커맨드를 실행해 데이터를 모두 지워버렸다.
> set a 11
OK
> set b 22
OK
> set c 33
OK
> keys *
1) "b"
2) "a"
3) "c"
> flushall
OK
> keys *
(empty list or set)
appendonly.aof 파일 형식
만일 redis 명령어를 다음과 같이 실행하면 appendonly.aof 파일에 로그가 쌓이게 된다.
- AOF는 명령 실행 순서대로 텍스트로 쓰여즈며 편집이 가능하다.
- * 는 명령 시작을 나타낸다. 숫자는 명령과 인수의 개수이다.
- $ 는 명령이나 인수, 데이터의 바이트 수이다. (한글은 UTF8로 했을 경우 한 글자에 3바이트)
*2
$6
SELECT
$1
0
*3
$3
set
$1
a
$2
11
*3
$3
set
$1
b
$2
22
*3
$3
set
$1
c
$2
33
*1
$8
flushall
이제 aof 파일 마지막에 flushall 적혀있는 부분을 지워주고 저장해준다.
그리고 다시 레디스를 재기동 해주면 데이터가 복구가 되어있음을 확인 할 수 있다.
Redis-cli 이용하기
수동 Rewrite
AOF Rewrite는 BGREWRITEAOF 커멘드를 이용해 CLI 창에서 수동으로 AOF 파일 재작성할 수 있다.
> BGREWRITEAOF
Background append only file rewriting started
RDB vs AOF 선택 기준
RDB 사용 주의할 점
RDB(Snapshot) 방식의 문제점은 매우 명확한데, Redis에 장애가 발생했을 때 백업 시점을 제외한 중간 시점에서 발생한 데이터는 유실될 수 있다는 것이다.
그리고 rdb 파일을 생성하는 cli 명령어 save 는 single thread로 수행하기 때문에 작업이 완료되기 까지 모든 요청이 대기하게 된다.
따라서 bgsave 커맨드로 background 자식 프로세스를 통해 RDB 작업 수행하도록 할 것을 권장되는 편이다.
그러나 bgsave 커맨드 수행 시엔 memory 사용률 조심해야 된다.
redis 서비스에서 사용중인 데이터는 모두 메모리 위에 있는데 이를 “서비스 영향 없이” 스냅샷으로 저장하기 위해서는 Copy-on-Write(COW) 방식을 사용한다.
자식 프로세스 fork() 후 부모 프로세스의 메모리에서 실제로 변경이 발생한 부분만 복사하게 되는데, 만일 write 작업이 많아서 부모 페이지 전부에 변경이 발생하게 되면 부모 페이지 전부를 복사하게 되는 현상이 발생하게 된다.
예를들어, 서버 memory 10GB, redis memory 6GB 사용하는 서비스에서 RDB 수행 중 Copy-on-Write(COW)를 실행하여백업하는데 메모리 두배가 필요하다고 하자.
그러면 2GB 가 부족한 상황 오게 되는데 그러면 swap 이 발생하여 서비스 지연이 발생하게 되므로, redis의 max-memory 설정을 주의 깊게 해야 된다.
save 커맨드는 redis process가 직접 수행하여 Copy-on-Write(COW)가 발생하지 않아 문제는 없다.
AOF 사용 주의할 점
언뜻 보면 "쓰기 작업의 기록을 저장해 replay하는 형식으로 복구 할수 있기 때문에 안정적이고에 이상적이지 않느냐" 라고 생각할 수 있으나, 단순히 그렇지만은 않다.
예를 들어 100번의 Increment 작업을 통해 0의 데이터를 100으로 만들었다고 생각해보자.
그렇다면 최종적으로 저장되어 있는 데이터는 100이지만, AOF 방식에서 기록된 파일을 읽어와 복구하려면 불필요하게 100번의 Increment 쓰기 작업을 무식하게 실행해야만 한다.
RDB 방식에서는 100이란 값을 단순히 읽어오기만 하면 되는데도 말이다.
뿐만 아니라 RDB 방식에 비해 백업 데이터가 크기도 하고, 서버 자원 또한 많이 잡아먹는 편이다.
따라서 Redis의 공식 문서에서는 RDB와 AOF 방식을 적절히 혼재해서 사용할 것을 권장하고 있다.
또한 Redis의 로드맵으로서 추후에 AOF와 RDB 방식을 합치는 것을 계획 중에 있다고 한다.
따라서 현재 시점에서 가장 중요한 것은 각 방식의 trade-off를 정확히 이해하고 적용하는 것이 되겠다.
RDB vs AOF 선택
그럼 어느 경우에 AOF 와 RDB 중 어떤 방법을 사용해 파일을 읽을까?
우선 redis를 캐시로만 사용한다면 "굳이" 백업 기능은 필요 없다. 저장 공간 낭비가 될 수 있다.
그래도 백업은 필요하지만 어느 정도의 데이터 손실이 발생해도 괜찮은 경우, RDB를 단독 사용하는 것을 고려한다.
redis.conf 파일에서 SAVE 옵션을 적절하게 변경해서 사용하면 된다.
SAVE 900 1 # 900초 동안 1개 이상 키가 변경되었을 때 RDB 파일 재작성
하지만, 장애 상황 직전까지 모든 데이터가 보장되어야 할 경우 AOF 사용 (appendonly yes) 하면 된다.
appendfsync everysec
그렇지만 RDB와 AOF 방식의 장단점을 상쇄하기 위해서 두가지 방식을 혼용해서 사용하는 것이 바람직하다.
주기적으로 RDB(snapshot)으로 백업하고, 다음 snapshot까지의 저장을 AOF 방식으로 수행하는 식으로 혼용한다.
이렇게 하면 서버가 restart될 때 백업된 snapshot을 reload하고, 비교적 적은 양의 AOF 로그만 replay하면 되기 때문에, restart 시간을 절약하고 데이타의 유실을 방지할 수 있다.
# 참고자료
https://rmcodestar.github.io/redis/2018/12/10/redis-persistence/
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.