Redis RDB와 AOF 영속성 전략 비교
Redis는 기본적으로 메모리 기반 데이터 저장소입니다. 이 특성 덕분에 초고속 응답이 가능하지만, 프로세스가 재시작되거나 서버가 다운되는 순간 메모리에만 존재하던 데이터는 그대로 사라집니다.
목차
- 개요
- Redis 영속성 메커니즘의 기초
- RDB 스냅샷 — 시점 복구의 원리
- AOF 로그 — 명령어 기반 내구성
- RDB와 AOF 비교 및 선택 기준
- 혼합 전략과 운영 환경 적용
- 맺음말
개요
문제 배경
Redis는 기본적으로 메모리 기반 데이터 저장소입니다. 이 특성 덕분에 초고속 응답이 가능하지만, 프로세스가 재시작되거나 서버가 다운되는 순간 메모리에만 존재하던 데이터는 그대로 사라집니다. 캐시 용도로만 쓴다면 이 특성이 문제가 되지 않습니다. 그러나 세션 저장소, 분산 락, 리더보드, 이벤트 큐처럼 데이터의 지속성이 요구되는 역할을 Redis에게 맡기는 순간, 재시작 후 복구 여부가 시스템 전체의 신뢰성을 결정짓습니다.
Redis는 이 문제를 해결하기 위해 두 가지 영속성 메커니즘을 제공합니다. 특정 시점의 메모리 스냅샷을 디스크에 기록하는 RDB(Redis Database Backup) 방식과, 실행된 모든 쓰기 명령을 파일에 순차적으로 추가하는 AOF(Append Only File) 방식입니다. 두 방식은 설계 철학부터 복구 보장 수준까지 근본적으로 다르며, 운영 환경에서 어느 쪽을 선택하느냐에 따라 장애 발생 시 데이터 유실 범위와 복구 소요 시간이 크게 달라집니다.
기존 방식의 한계
많은 팀이 Redis를 처음 도입할 때 기본 설정 그대로 사용하거나, 단순히 save 900 1 같은 RDB 설정만 켜두고 운영합니다. 이 접근은 서버가 정상적으로 종료될 때는 큰 문제가 없지만, 예고 없는 프로세스 강제 종료나 OOM 킬러의 개입 같은 비정상 종료 상황에서는 마지막 스냅샷 이후의 모든 변경이 유실됩니다. AOF를 도입했더라도 appendfsync always와 everysec 중 어느 쪽을 선택해야 하는지, AOF 파일이 무한정 커지는 것을 어떻게 제어하는지 이해하지 못하면 의도치 않은 성능 저하나 디스크 고갈을 겪습니다. 이 글은 두 방식의 내부 동작 원리부터 운영 환경에서의 선택 기준까지 다룹니다.
Redis 영속성 메커니즘의 기초
메모리와 디스크 사이의 간격
Redis의 데이터는 인스턴스가 살아 있는 동안 RAM에 존재합니다. 영속성이란 이 메모리 상태를 주기적으로 또는 실시간으로 디스크에 반영하여, 인스턴스가 재시작된 후에도 이전 상태로 돌아올 수 있게 하는 작업입니다. 핵심 질문은 두 가지입니다. 언제 디스크에 기록하느냐, 그리고 무엇을 기록하느냐입니다.
RDB는 "무엇"의 관점에서 전체 메모리의 이진 스냅샷을 저장합니다. AOF는 "언제"를 극단으로 가져가, 각 쓰기 명령이 실행될 때마다(또는 매 초마다) 그 명령을 텍스트 로그로 남깁니다. 두 방식 모두 Redis 프로세스가 시작될 때 이 파일을 읽어 메모리 상태를 재구성합니다.
RDB와 AOF는 메모리 상태를 디스크로 내보내는 방향이 같지만, 그 표현 방식과 복구 시 재구성 방법이 전혀 다릅니다.
영속성이 없을 때 발생하는 장애 시나리오
save ""와 appendonly no를 모두 설정하면 Redis는 순수 인메모리 모드로 동작합니다. 이 경우 SHUTDOWN 명령으로 정상 종료하면 마지막 상태가 저장되지 않고 그냥 종료됩니다. 운영 중 프로세스가 비정상적으로 죽으면 마지막 체크포인트가 없으므로 복구 자체가 불가능합니다.
세션 데이터나 임시 큐처럼 재생성 가능한 데이터라면 이 모드가 오히려 적합합니다. 하지만 영속성을 켜야 하는 경우라면, 어떤 방식을 선택하든 그 트레이드오프를 명확히 이해한 뒤 적용해야 합니다.
| 영속성 모드 | 장애 시 유실 범위 | 재시작 복구 시간 | 주요 사용처 |
|---|---|---|---|
| 없음 | 전체 | 즉시 (빈 상태) | 순수 캐시 |
| RDB만 | 마지막 스냅샷 이후 | 빠름 (이진 로드) | 분석용 스냅샷 |
| AOF만 | fsync 설정에 따라 0~1초 | 느림 (명령 재실행) | 내구성 최우선 |
| RDB + AOF | fsync 설정에 따라 0~1초 | 중간 (AOF 우선, RDB 보조) | 현업 권장 구성 |
RDB 스냅샷 — 시점 복구의 원리
fork와 copy-on-write 메커니즘
RDB 스냅샷의 가장 중요한 특성은 Redis 메인 프로세스가 거의 블로킹되지 않는다는 점입니다. Redis는 BGSAVE 명령이 실행되면 fork() 시스템 콜로 자식 프로세스를 생성합니다. 자식 프로세스는 부모의 메모리 페이지를 물리적으로 복사하는 대신, 운영 체제의 Copy-on-Write(CoW) 메커니즘을 활용합니다. 초기에는 두 프로세스가 동일한 물리 메모리 페이지를 공유하고, 부모 프로세스가 특정 페이지를 수정할 때만 그 페이지를 실제로 복사합니다.
덕분에 자식 프로세스는 fork() 시점의 메모리 스냅샷을 비교적 저렴하게 확보하고, 메인 프로세스는 클라이언트 요청을 계속 처리할 수 있습니다. 그러나 이 메커니즘에는 숨겨진 비용이 있습니다. 스냅샷 진행 중에 쓰기 요청이 몰리면 CoW로 인해 실제 메모리 사용량이 최대 두 배까지 늘어날 수 있습니다. 32GB를 사용 중인 Redis 인스턴스에서 BGSAVE 중 순간적으로 60GB가 넘는 메모리가 필요해지는 사례는 드물지 않습니다.
fork() 이후 자식 프로세스가 스냅샷을 기록하는 동안 메인 프로세스는 쓰기를 계속 받으며, 수정된 페이지만 CoW로 복사됩니다.
RDB 트리거 조건 설정
RDB는 자동 저장 조건을 redis.conf의 save 지시어로 설정합니다. 설정 형식은 save <초> <변경횟수>이며, 해당 초 안에 해당 횟수 이상의 키 변경이 발생하면 BGSAVE가 트리거됩니다.
아래는 일반적으로 많이 쓰는 세 가지 조건을 동시에 설정한 예시입니다. Redis는 이 중 하나라도 충족되면 스냅샷을 시작합니다.
# redis.conf — RDB 자동 저장 설정
save 900 1 # 15분 안에 키 변경이 1건 이상이면 스냅샷
save 300 10 # 5분 안에 변경이 10건 이상이면 스냅샷
save 60 10000 # 1분 안에 변경이 10000건 이상이면 스냅샷
dbfilename dump.rdb
dir /var/lib/redis
# 스냅샷 실패 시 쓰기 거부 여부
stop-writes-on-bgsave-error yes
# RDB 압축 (lzf 알고리즘, CPU 사용 증가)
rdbcompression yes
rdbchecksum yes
stop-writes-on-bgsave-error yes는 스냅샷이 실패했을 때 Redis가 쓰기 요청을 거부하도록 합니다. 이 설정이 없으면 스냅샷 실패가 조용히 지나가고 운영자가 인지하지 못한 채 데이터가 보호되지 않는 상태가 이어질 수 있습니다.
RDB의 강점과 한계
RDB 파일은 특정 시점의 완전한 데이터셋 이진 표현이므로 파일 크기가 작고 로드 속도가 빠릅니다. 수백만 개의 키를 담은 RDB 파일도 AOF에 비해 훨씬 빨리 메모리로 올라옵니다. 대규모 데이터셋에서 재시작 후 빠른 서비스 복구가 필요할 때 RDB가 유리한 이유입니다.
반면 RDB의 치명적인 약점은 마지막 스냅샷 이후의 데이터를 보장하지 못한다는 점입니다. save 60 10000 설정에서 스냅샷이 완료된 직후 서버가 다운된다면, 그 후 최대 60초 동안의 모든 변경이 사라집니다. 초당 수천 건의 쓰기가 발생하는 시스템에서 60초면 수십만 건의 데이터 유실을 의미합니다. 이 유실 허용 범위가 비즈니스 요구사항에 맞지 않는다면 AOF를 고려해야 합니다.
AOF 로그 — 명령어 기반 내구성
쓰기 명령의 순차 기록
AOF는 Redis가 처리하는 모든 쓰기 명령을 RESP(Redis Serialization Protocol) 형식의 텍스트로 파일 끝에 덧붙입니다. 파일을 열면 SET, HSET, LPUSH 등 모든 변경 명령이 실행 순서대로 나열되어 있습니다. Redis가 재시작될 때 이 파일을 처음부터 끝까지 읽으면서 명령을 하나씩 재실행하면 메모리 상태가 복원됩니다.
이 방식은 직관적이지만, 시간이 지남에 따라 AOF 파일이 무한정 커진다는 문제가 있습니다. 같은 키에 대해 1000번 SET이 실행되면 그 기록이 모두 AOF에 남습니다. 실제 복원에 필요한 것은 가장 마지막 값 하나뿐인데도 불구하고 말이죠. 이 문제를 해결하기 위해 Redis는 AOF 재작성(rewrite) 메커니즘을 제공합니다.
AOF 재작성은 현재 메모리 상태에서 최소한의 명령 집합을 생성하므로, 파일 크기가 대폭 줄어듭니다.
fsync 정책과 데이터 내구성 보장 수준
AOF의 핵심 파라미터는 appendfsync입니다. 운영체제는 파일 쓰기를 즉시 디스크에 플러시하지 않고 버퍼에 담아뒀다가 나중에 처리하는 경향이 있습니다. fsync() 시스템 콜은 이 버퍼를 강제로 디스크에 내려쓰도록 합니다. appendfsync 설정은 Redis가 이 fsync()를 얼마나 자주 호출할지를 결정합니다.
# redis.conf — AOF 설정
appendonly yes
appendfilename "appendonly.aof"
dir /var/lib/redis
# fsync 정책 선택 (세 가지 중 하나)
# appendfsync always # 모든 쓰기마다 fsync — 가장 안전, 가장 느림
appendfsync everysec # 1초마다 fsync — 권장 (최대 1초 유실)
# appendfsync no # OS 판단에 맡김 — 빠르지만 내구성 포기
# AOF 재작성 설정
auto-aof-rewrite-percentage 100 # AOF가 초기 크기 대비 100% 커지면 재작성
auto-aof-rewrite-min-size 64mb # 최소 64MB가 되어야 재작성 시작
# 비정상 종료 후 손상된 AOF 허용 여부
aof-use-rdb-preamble yes
always는 각 쓰기 명령이 디스크에 기록될 때까지 클라이언트에 응답하지 않으므로 처리량이 크게 줄어듭니다. 초당 수백 건 수준의 트래픽에서도 쓰기 지연이 눈에 띄게 증가합니다. 반면 no는 OS가 판단하는 시점에만 fsync를 수행하므로 내구성 보장이 없습니다. 대부분의 운영 환경에서는 everysec이 적절한 균형점을 제공합니다.
| appendfsync | 데이터 유실 최대 범위 | 성능 영향 | 권장 상황 |
|---|---|---|---|
always |
0 (거의 없음) | 높음 (처리량 대폭 감소) | 금융 거래, 절대적 내구성 필요 |
everysec |
최대 1초 | 낮음 (권장) | 대부분의 운영 환경 |
no |
OS 버퍼 크기만큼 | 없음 | 내구성 불필요, 캐시 전용 |
AOF 재작성의 내부 동작
AOF 재작성은 RDB 스냅샷과 유사하게 fork()로 자식 프로세스를 생성합니다. 자식 프로세스는 현재 메모리 상태를 기반으로 새 AOF 파일을 씁니다. 이 과정에서 메인 프로세스가 받는 새 쓰기 명령은 기존 AOF 파일과 별도의 재작성 버퍼에 저장됩니다. 자식 프로세스가 새 파일 작성을 마치면, 재작성 버퍼의 내용을 새 파일에 추가하고 원자적으로 기존 파일을 교체합니다.
aof-use-rdb-preamble yes 설정은 Redis 4.0부터 도입된 하이브리드 AOF 모드를 활성화합니다. 재작성 시 새 AOF 파일의 앞부분에 RDB 형식의 스냅샷을 삽입하고, 그 이후의 명령만 AOF 형식으로 이어 씁니다. 이 방식은 재시작 시 RDB의 빠른 로드 속도와 AOF의 세밀한 내구성을 동시에 얻습니다.
RDB와 AOF 비교 및 선택 기준
복구 시나리오별 성능 차이
장애 복구 시 두 방식의 차이는 단순히 데이터 유실 범위에 그치지 않습니다. 복구 소요 시간도 크게 다릅니다. 1억 개의 키를 담은 Redis 인스턴스를 복구한다고 가정하면, RDB 로드는 수십 초 수준이지만 AOF 재실행은 수십 분이 걸릴 수 있습니다. 이유는 간단합니다. RDB는 이진 형식으로 메모리에 직접 매핑하듯 로드하지만, AOF는 각 명령을 파싱하고 메모리에 실행하는 과정을 수백만 번 반복해야 하기 때문입니다.
그러나 aof-use-rdb-preamble yes를 사용하면 이 격차가 크게 줄어듭니다. 하이브리드 AOF의 경우 RDB 스냅샷 시점까지는 빠르게 로드하고, 그 이후 명령만 재실행하면 됩니다.
복구 경로는 영속성 모드에 따라 속도와 정확성 사이의 다른 균형을 가집니다.
기술 선택 기준
데이터 성격과 비즈니스 요구사항에 따라 선택이 달라집니다. 가장 중요한 질문은 "장애 발생 시 몇 초치 데이터 유실을 허용할 수 있는가"입니다.
세션 캐시처럼 만료 후 재생성이 가능한 데이터라면 RDB 단독이나 영속성 없음도 충분합니다. 반면 결제 처리 큐, 재고 변경 이벤트처럼 단 한 건의 유실도 비즈니스 손실이 되는 데이터라면 appendfsync always의 AOF가 필요합니다. 대부분의 서비스는 그 사이 어딘가에 있으며, appendfsync everysec의 AOF와 RDB를 병행하는 혼합 전략이 현실적인 선택입니다.
데이터 특성과 허용 유실 범위를 먼저 정의해야 올바른 영속성 전략이 결정됩니다.
혼합 전략과 운영 환경 적용
RDB + AOF 병행 구성
Redis 공식 문서는 내구성이 중요한 운영 환경에서 RDB와 AOF를 병행 사용하도록 권장합니다. 두 방식을 함께 쓸 때 Redis는 재시작 시 AOF 파일을 우선적으로 사용합니다. AOF가 더 최신의 데이터를 담고 있기 때문입니다. RDB는 주로 빠른 백업 복원이나 슬레이브 초기 동기화, 그리고 AOF가 손상되었을 때의 안전망 역할을 합니다.
Redis는 AOF와 RDB가 모두 있으면 AOF를 우선 사용하므로, 두 파일을 함께 유지하는 것이 가장 안전합니다.
아래는 운영 환경에서 권장하는 혼합 구성입니다.
# redis.conf — RDB + AOF 혼합 운영 구성
# --- RDB 설정 ---
save 900 1
save 300 10
save 60 10000
dbfilename dump.rdb
dir /var/lib/redis/data
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
# --- AOF 설정 ---
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
aof-use-rdb-preamble yes # 하이브리드 AOF 활성화
# --- 메모리 및 로그 설정 ---
maxmemory 8gb
maxmemory-policy allkeys-lru
loglevel notice
logfile /var/log/redis/redis-server.log
no-appendfsync-on-rewrite no는 AOF 재작성 중에도 fsync를 멈추지 않도록 합니다. 이를 yes로 설정하면 재작성 중 OS 버퍼에 쌓인 명령이 플러시되지 않아 내구성이 일시적으로 저하됩니다. 내구성을 중시한다면 기본값인 no를 유지하는 것이 안전합니다.
복제 환경에서의 영속성 전략
Redis Sentinel이나 Redis Cluster를 운영하는 환경에서 영속성 설정은 더 세심한 접근이 필요합니다. 마스터 노드에 영속성을 설정하면 BGSAVE나 AOF 재작성 중 fork() 비용이 마스터의 응답 지연에 영향을 줄 수 있습니다. 이 때문에 일부 팀은 마스터에서 영속성을 끄고 레플리카에서 AOF를 활성화하는 전략을 사용합니다.
그러나 이 구성에는 위험이 있습니다. 마스터가 재시작되면 빈 데이터셋으로 시작하고, 자동 페일오버가 구성되어 있지 않다면 레플리카가 이 빈 마스터와 동기화되어 데이터를 모두 잃을 수 있습니다. 마스터에서 영속성을 비활성화하려면 반드시 자동 페일오버(Sentinel/Cluster)와 함께 구성하고, 마스터 재시작 시 레플리카 승격이 정상적으로 이뤄지는지 정기적으로 검증해야 합니다.
복제 환경에서는 영속성 부담을 레플리카에 분산할 수 있지만, 반드시 자동 페일오버가 선행되어야 합니다.
운영 환경 적용 시 고려사항
흔한 실수와 함정
fork() 레이턴시 스파이크는 현업에서 가장 자주 겪는 문제 중 하나입니다. BGSAVE나 AOF 재작성이 시작되는 순간, fork() 시스템 콜 자체가 수십~수백 밀리초를 소비하는 경우가 있습니다. 이 시간 동안 Redis 메인 프로세스는 블로킹됩니다. 원인은 대부분 투명 대용량 페이지(THP, Transparent HugePage)입니다. THP가 활성화된 리눅스 서버에서는 fork() 시 페이지 테이블 복사 비용이 크게 증가합니다. Redis 공식 문서는 운영 서버에서 반드시 THP를 비활성화하도록 권고합니다.
# THP 비활성화 (즉시 적용)
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag
# 영구 적용 — /etc/rc.local 또는 systemd 서비스에 추가
# 결과 확인: cat /sys/kernel/mm/transparent_hugepage/enabled
# 출력 예: always madvise [never] → [never]가 선택된 상태
AOF 파일 손상도 주의해야 합니다. 디스크 풀이나 서버 강제 종료 중 AOF 파일의 마지막 부분이 불완전하게 기록될 수 있습니다. aof-load-truncated yes(기본값)는 이 경우 마지막 불완전한 명령을 무시하고 복원합니다. redis-check-aof --fix appendonly.aof 명령으로 수동으로 검증하고 수정할 수도 있습니다.
모니터링과 디버깅
영속성 관련 지표는 INFO persistence 명령으로 확인합니다. 운영 환경에서 정기적으로 모니터링해야 할 핵심 지표들이 있습니다.
| 지표 | 확인 방법 | 경보 기준 |
|---|---|---|
| 마지막 성공 스냅샷 시간 | rdb_last_save_time |
설정된 save 주기 2배 이상 경과 |
| RDB 저장 상태 | rdb_last_bgsave_status |
ok 외 값 |
| AOF 마지막 재작성 상태 | aof_last_rewrite_status |
ok 외 값 |
| AOF 버퍼 크기 | aof_buffer_length |
비정상적으로 큰 값 (MB 단위) |
| fork() 지연 | latest_fork_usec |
수백 ms 초과 |
rdb_last_save_time은 Unix 타임스탬프로 반환됩니다. $(date -d @<timestamp>) 형태로 변환해 가독성 있게 확인할 수 있습니다. 프로메테우스 기반 모니터링 환경이라면 redis_exporter를 통해 이 지표를 자동으로 수집하고 Grafana 대시보드에서 시각화할 수 있습니다.
영속성 지표를 외부 모니터링 스택과 연결하면 스냅샷 실패나 AOF 재작성 오류를 실시간으로 감지할 수 있습니다.
확장 및 마이그레이션 시 고려사항
데이터셋 규모가 커질수록 RDB 스냅샷과 AOF 재작성의 부담도 함께 증가합니다. 수십 GB 규모의 Redis 인스턴스에서는 BGSAVE 한 번에 수 분이 소요되고, 그동안 CoW로 인한 메모리 사용량이 실제 데이터셋 크기를 크게 초과합니다. 이 단계에 이르면 데이터 파티셔닝을 검토해야 합니다. Redis Cluster를 도입하여 각 샤드의 크기를 줄이면 개별 인스턴스의 영속성 부담도 분산됩니다.
영속성 설정을 변경할 때는 CONFIG SET 명령으로 런타임에 적용할 수 있지만, 재시작 후에도 유지하려면 반드시 redis.conf에도 반영해야 합니다. AOF를 새로 활성화하는 경우 CONFIG SET appendonly yes 이후 Redis가 현재 메모리 상태를 기반으로 AOF 파일을 자동 생성합니다. 이 과정에서 일시적인 디스크 I/O 급증이 발생할 수 있으므로 트래픽이 낮은 시간대에 적용하는 것을 권장합니다.
맺음말
핵심 요약
Redis 영속성 전략의 핵심은 데이터 유실 허용 범위(RPO)와 복구 시간 목표(RTO)를 먼저 정의하는 것입니다. RDB는 이진 스냅샷으로 빠른 복구를 제공하지만 마지막 스냅샷 이후의 데이터를 보장하지 못합니다. AOF는 쓰기 명령을 실시간 또는 1초 주기로 기록하여 데이터 유실을 최소화하지만, 재시작 복구 시간이 더 깁니다. aof-use-rdb-preamble yes를 통한 하이브리드 AOF는 두 방식의 장점을 결합하여 현업에서 가장 균형 잡힌 선택지가 됩니다. fork() 레이턴시, THP 비활성화, AOF 파일 손상 대비, 영속성 지표 모니터링은 운영 안정성을 위해 반드시 챙겨야 할 요소입니다.
적용 판단 기준
영속성 설정을 선택할 때는 다음 순서로 판단하는 것이 효과적입니다. 먼저 해당 Redis 인스턴스가 담는 데이터가 재생성 가능한지 확인하십시오. 세션, 임시 계산 결과, 순수 캐시라면 영속성은 불필요하며 오히려 성능 저하를 유발합니다. 재생성이 불가하다면 장애 시 몇 초 분량의 유실을 허용할 수 있는지 비즈니스 팀과 합의하십시오. 1초 이내 유실 허용이라면 appendfsync everysec의 AOF와 RDB 혼합이 대부분의 요구를 충족합니다. 유실 자체가 불허되는 환경이라면 appendfsync always와 함께 성능 희생을 감수해야 하며, 이 결정은 반드시 부하 테스트로 검증해야 합니다. 복제 환경에서는 마스터의 영속성 비활성화보다 레플리카 활용이 안전하지만, 이 경우 반드시 자동 페일오버 구성이 선행되어야 합니다.