Redis 영속성 전략 — RDB vs AOF, 그리고 혼합 방식의 트레이드오프
인메모리의 딜레마
Redis의 모든 데이터는 메모리에 있습니다. 이것이 Redis를 빠르게 만들지만, 동시에 가장 큰 약점이기도 합니다. 프로세스가 죽거나 서버가 재시작되면 메모리의 데이터는 사라집니다.
Redis는 이 문제를 해결하기 위해 두 가지 영속성 방식을 제공합니다. RDB(스냅샷)와 AOF(로그). 그리고 Redis 4.0부터는 두 방식을 결합한 혼합 방식이 추가되었습니다. 각 방식은 "데이터를 얼마나 안전하게 보관할 것인가"와 "성능을 얼마나 포기할 것인가" 사이의 서로 다른 지점에 위치합니다.
RDB: 특정 시점의 스냅샷
RDB(Redis Database)는 메모리의 전체 데이터를 바이너리 파일(dump.rdb)로 저장하는 방식입니다. 사진을 찍듯이 특정 시점의 상태를 통째로 기록합니다.
동작 원리: fork()와 Copy-on-Write
RDB 저장이 트리거되면 Redis는 다음 과정을 거칩니다.
1. 메인 프로세스가 fork() 호출 → 자식 프로세스 생성
2. 자식 프로세스: 메모리 데이터를 디스크에 직렬화 (dump.rdb)
3. 메인 프로세스: 클라이언트 명령 계속 처리 (블로킹 없음)
4. 자식 프로세스 완료 → 임시 파일을 dump.rdb로 원자적 교체
여기서 핵심은 fork()입니다. fork()는 부모 프로세스의 메모리를 그대로 복사한 자식 프로세스를 만듭니다. 하지만 실제로 메모리 전체를 복사하면 너무 느리고 메모리가 두 배로 필요합니다.
이때 운영 체제의 Copy-on-Write(COW) 메커니즘이 작동합니다.
fork() 직후:
┌───────────────┐ ┌───────────────┐
│ 부모 프로세스 │ │ 자식 프로세스 │
│ (메인 Redis) │ │ (RDB 쓰기) │
└───────┬───────┘ └───────┬───────┘
│ │
└──────── 같은 물리 메모리 페이지를 공유 ────────┘
부모가 데이터 수정 시:
┌───────────────┐ ┌───────────────┐
│ 부모 프로세스 │ │ 자식 프로세스 │
│ 수정된 페이지 │ │ 원본 페이지 │ ← fork 시점의 데이터 유지
│ (새로 복사) │ │ (공유 유지) │
└───────────────┘ └───────────────┘
부모가 데이터를 수정할 때만 해당 메모리 페이지가 복사됩니다. 쓰기가 적으면 추가 메모리 사용이 거의 없고, 쓰기가 많으면 최악의 경우 메모리가 두 배까지 필요할 수 있습니다.
트리거 조건
# redis.conf
save 3600 1 # 3600초(1시간) 동안 1건 이상 변경 시
save 300 100 # 300초(5분) 동안 100건 이상 변경 시
save 60 10000 # 60초(1분) 동안 10000건 이상 변경 시
# 수동 트리거
BGSAVE # 백그라운드 저장 (논블로킹)
SAVE # 동기 저장 (블로킹, 프로덕션에서 사용 금지)
장점
- 컴팩트한 파일: 바이너리 형식이라 AOF보다 파일 크기가 작습니다
- 빠른 복구: 바이너리를 메모리에 로드하기만 하면 되므로, 대용량 데이터에서 AOF보다 복구가 빠릅니다
- 성능 영향 적음: fork() 후 자식 프로세스가 처리하므로 메인 프로세스는 거의 영향 없음
- 백업에 적합: 한 파일로 완결되어 S3 등에 업로드하기 좋습니다
단점
- 데이터 유실: 마지막 스냅샷 이후의 데이터가 유실됩니다.
save 300 100설정이면 최대 5분간의 데이터 손실 - fork() 비용: 메모리가 클수록 fork() 시간이 길어집니다. 수십 GB 데이터에서는 fork()만으로 수 초가 걸릴 수 있으며, 이 순간 메인 프로세스가 잠깐 멈춥니다
- COW 메모리: 쓰기가 많은 워크로드에서 fork() 후 메모리 사용량이 급증할 수 있습니다
AOF: 모든 쓰기를 기록하는 로그
AOF(Append Only File)는 Redis에 실행된 모든 쓰기 명령을 텍스트 로그로 기록합니다. RDB가 "사진"이라면 AOF는 "일기장"에 가깝습니다.
동작 원리
클라이언트 → SET name "Redis" → 명령 실행 → 메모리 업데이트
→ AOF 버퍼에 추가
→ fsync 정책에 따라 디스크 기록
AOF 파일 내용:
*3\r\n$3\r\nSET\r\n$4\r\nname\r\n$5\r\nRedis\r\n
*2\r\n$3\r\nGET\r\n$4\r\nname\r\n ← 읽기 명령은 기록 안 함
*3\r\n$3\r\nSET\r\n$3\r\nage\r\n$2\r\n30\r\n
AOF는 RESP(Redis Serialization Protocol) 형식으로 명령을 기록합니다. 텍스트 기반이라 사람이 읽을 수 있고, 필요하면 수동으로 편집할 수도 있습니다.
fsync 정책: 핵심 선택지
AOF의 성능과 내구성은 appendfsync 설정에 의해 결정됩니다. 이것이 AOF에서 가장 중요한 설정입니다.
| 정책 | 동작 | 데이터 유실 | 성능 | 적합한 환경 |
|---|---|---|---|---|
always | 매 명령마다 fsync 호출 | 0 (이론적) | 가장 느림 (fsync당 ~1ms) | 금융 데이터 등 한 건도 잃으면 안 되는 경우 |
everysec (권장) | 1초마다 백그라운드 스레드가 fsync | 최대 1초 | 거의 RDB와 동등 | 대부분의 프로덕션 환경 |
no | OS가 알아서 flush (보통 30초) | OS flush 주기만큼 (예측 불가) | 가장 빠름 | 유실 가능한 캐시 전용 환경 |
AOF Rewrite: 파일이 끝없이 커지는 문제
같은 키를 100번 수정하면, AOF에는 100개의 SET 명령이 기록됩니다. 하지만 복구에 필요한 것은 마지막 값 하나뿐입니다. AOF 파일이 끝없이 커지는 것을 방지하기 위해 AOF Rewrite가 존재합니다.
Rewrite 전 AOF:
SET counter 1
INCR counter # counter = 2
INCR counter # counter = 3
INCR counter # counter = 4
DEL counter
SET counter 10
INCR counter # counter = 11
Rewrite 후 AOF:
SET counter 11 # 현재 상태만 기록
Rewrite도 RDB와 마찬가지로 fork()로 자식 프로세스를 만들어 수행합니다. Rewrite 중 들어오는 새 명령은 별도 버퍼에 쌓았다가 Rewrite 완료 후 새 AOF 파일에 추가합니다.
# 자동 Rewrite 조건
auto-aof-rewrite-percentage 100 # AOF 파일이 이전 대비 100% 증가하면
auto-aof-rewrite-min-size 64mb # 최소 64MB 이상일 때
장점
- 낮은 데이터 유실:
everysec기준 최대 1초,always면 사실상 0 - 텍스트 로그: 사람이 읽을 수 있고, 실수로
FLUSHALL실행 시 마지막 줄을 삭제하고 복구 가능 - 점진적 기록: 한 번에 큰 데이터를 쓰지 않으므로 fork()의 메모리 부담이 적음 (Rewrite 제외)
단점
- 큰 파일 크기: 같은 데이터에 대해 RDB보다 파일이 큼
- 느린 복구: 명령을 하나씩 재실행해야 하므로, 대용량에서 RDB보다 복구가 느림
- Rewrite 부담: Rewrite 시 fork()가 발생하며, 이 동안 메모리와 I/O 사용량 증가
혼합 방식 (Redis 4.0+): 두 장점을 결합
Redis 4.0에서 도입된 혼합 방식은 AOF Rewrite를 할 때, 순수 AOF 대신 RDB 스냅샷 + 이후 AOF 명령을 결합합니다.
혼합 AOF 파일 구조:
┌─────────────────────────────────┐
│ RDB 바이너리 (Rewrite 시점) │ ← 빠른 로드
├─────────────────────────────────┤
│ AOF 명령들 (Rewrite 이후) │ ← 최신 데이터
└─────────────────────────────────┘
복구 시:
1. RDB 부분을 빠르게 메모리에 로드
2. AOF 부분을 순차 실행하여 최신 상태로 갱신
aof-use-rdb-preamble yes # Redis 7.0부터 기본값
이 방식은 RDB의 빠른 복구 속도와 AOF의 낮은 데이터 유실을 모두 얻을 수 있어, 현재 Redis의 권장 설정입니다.
Redis 7.0: Multi-Part AOF
Redis 7.0에서는 AOF 구조가 크게 개선되었습니다. 기존의 단일 AOF 파일 대신 여러 파일로 분리하는 Multi-Part AOF가 도입되었습니다.
appendonlydir appendonlydir/
├── appendonly.aof.1.base.rdb # 베이스 파일 (RDB 형식)
├── appendonly.aof.1.incr.aof # 증분 파일 1
├── appendonly.aof.2.incr.aof # 증분 파일 2
└── appendonly.aof.manifest # 매니페스트 (파일 목록)
Rewrite 시 기존 파일을 수정하는 대신 새 베이스 파일을 생성하고, 오래된 증분 파일만 삭제합니다. 이 방식이 더 안전하고 원자적입니다.
비교 요약
| 항목 | RDB | AOF | 혼합 |
|---|---|---|---|
| 데이터 유실 | 수 분 | 최대 1초 | 최대 1초 |
| 파일 크기 | 작음 | 큼 | 중간 |
| 복구 속도 | 빠름 | 느림 | 빠름 |
| 쓰기 성능 영향 | fork() 시점 | fsync 정책 | fork() 시점 |
| 백업 용이성 | 높음 | 중간 | 중간 |
| 수동 복구 가능 | 아니오 | 예 | 부분적 |
| Redis 7.0 기본 | 비활성화 | 활성화 | 활성화 |
실무 선택 기준
어떤 방식을 선택할지는 "얼마나 많은 데이터를 잃어도 되는가"와 "복구가 얼마나 빨라야 하는가"에 달려 있습니다.
순수 캐시 용도
데이터가 원본 DB에 있고 Redis는 캐시일 뿐이라면, 영속성을 아예 끄는 것도 선택지입니다. Redis가 재시작되면 캐시가 비어 있을 뿐, 데이터 유실은 아닙니다.
save "" # RDB 비활성화
appendonly no # AOF 비활성화
일반 프로덕션
대부분의 프로덕션 환경에서는 혼합 방식(AOF + RDB preamble)이 가장 균형 잡힌 선택입니다.
appendonly yes
appendfsync everysec
aof-use-rdb-preamble yes
# RDB도 백업용으로 함께 활성화
save 3600 1
데이터 유실 제로가 필요한 경우
금융 거래, 결제 정보 등 한 건도 잃으면 안 되는 경우입니다. 하지만 솔직히 말하면, 이런 데이터는 Redis보다 RDBMS에 저장하는 것이 더 적합합니다. Redis를 쓴다면 appendfsync always를 설정할 수 있지만, 성능 저하가 큽니다.
appendonly yes
appendfsync always # 매 명령마다 fsync → 느림
대규모 데이터 + 빠른 복구
수십 GB 데이터에서 AOF 복구는 수십 분이 걸릴 수 있습니다. 이 경우 RDB를 주 복구 수단으로, AOF를 보조로 사용합니다.
save 900 1
save 300 10
appendonly yes
appendfsync everysec
aof-use-rdb-preamble yes
fork()의 실무적 비용
RDB 저장이든 AOF Rewrite든, fork()는 피할 수 없습니다. fork()의 비용은 데이터 크기에 비례합니다.
| 메모리 크기 | fork() 소요 시간 |
|---|---|
| 1GB | ~20ms |
| 5GB | ~80ms |
| 10GB | ~160ms |
| 25GB | ~400ms |
| 50GB | ~800ms |
참고값이며 환경에 따라 다릅니다.
fork() 동안 메인 프로세스가 잠깐 멈출 수 있습니다. 50GB Redis에서 800ms 멈춤은 프로덕션에서 심각한 문제입니다. 이것이 Redis 인스턴스의 메모리를 적정 수준으로 유지해야 하는 이유이며, 대규모 데이터에서는 Cluster로 분산하는 것이 좋은 이유이기도 합니다.
Transparent Huge Pages (THP) 주의
Linux의 THP가 활성화되어 있으면, COW 시 4KB 대신 2MB 단위로 메모리 페이지가 복사됩니다. 쓰기가 있을 때마다 2MB씩 복사되므로 메모리 사용량이 급증할 수 있습니다. Redis 시작 시 경고가 나오면 반드시 비활성화해야 합니다.
echo never > /sys/kernel/mm/transparent_hugepage/enabled
정리
Redis 영속성의 핵심은 트레이드오프입니다. 데이터 안전성을 높이면 성능이 떨어지고, 성능을 높이면 유실 위험이 커집니다. 완벽한 답은 없지만, 대부분의 경우 혼합 방식(AOF + RDB preamble) + everysec이 가장 합리적인 선택입니다.
RDB의 fork()와 COW가 어떻게 동작하는지, AOF의 fsync 정책이 성능에 어떤 영향을 미치는지 이해하면, 단순히 "AOF가 더 안전하다" 수준을 넘어 환경에 맞는 판단을 내릴 수 있게 됩니다.
관련 글
Redis 내부 원리 총정리 — 싱글 스레드부터 클러스터까지
Redis를 캐시로만 쓰다가 면접에서 "싱글 스레드인데 왜 빠른가?"라는 질문에 막힌 경험이 있습니다. 싱글 스레드 모델, 자료구조별 시간복잡도, RDB/AOF 영속성, 메모리 관리, Sentinel과 Cluster까지 — Redis 내부 원리를 한 글에 정리했습니다.
Redis가 싱글 스레드인데 왜 빠른가 — I/O 멀티플렉싱부터 6.0 스레드 모델까지
Redis가 싱글 스레드라는 말은 반만 맞습니다. 정확히 무엇이 싱글 스레드이고, I/O 멀티플렉싱은 어떻게 동작하며, Redis 6.0의 I/O 스레드는 무엇을 바꿨는지. 면접에서 "왜 빠른가?"에 확실히 답할 수 있도록 정리했습니다.
Redis Sentinel vs Cluster — 고가용성 아키텍처의 선택 기준
Redis 단일 노드가 죽으면 서비스 전체가 멈춥니다. Sentinel은 자동 failover를, Cluster는 샤딩과 failover를 제공합니다. 각각의 동작 원리, 장애 복구 시나리오, 그리고 어떤 상황에서 무엇을 선택해야 하는지 정리했습니다.