RDB 스냅샷 — fork와 Copy-on-Write로 데이터 저장하기
인메모리 데이터베이스가 갑자기 꺼지면 데이터가 전부 사라지는 걸까요? Redis는 이 문제를 어떻게 해결할까요?
개념 정의
RDB(Redis Database) 스냅샷은 특정 시점의 Redis 데이터를 바이너리 파일로 저장하는 영속성 메커니즘입니다. fork() 시스템 콜과 Copy-on-Write(CoW) 를 활용하여 서비스 중단 없이 스냅샷을 생성합니다.
왜 필요한가
Redis는 인메모리 데이터베이스이므로 프로세스가 종료되면 데이터가 사라집니다.
- 서버 재시작: 배포, 업그레이드 후 데이터 복구
- 장애 복구: 예기치 않은 크래시에서 데이터 보호
- 데이터 마이그레이션: RDB 파일을 다른 서버로 복사
- 백업: 주기적 데이터 백업
RDB 저장 방식
SAVE — 동기식 (프로덕션에서 사용 금지)
# 메인 스레드가 직접 저장 — 완료될 때까지 모든 요청 블로킹
SAVE
# 데이터가 10GB라면 수십 초간 서비스 중단
BGSAVE — 비동기식 (권장)
# 자식 프로세스를 생성하여 백그라운드에서 저장
BGSAVE
# 결과 확인
LASTSAVE # 마지막 성공적 저장의 Unix timestamp
INFO persistence
# rdb_last_save_time: 1711238400
# rdb_last_bgsave_status: ok
# rdb_last_bgsave_time_sec: 3
자동 저장 설정
# redis.conf — 조건 기반 자동 BGSAVE
save 900 1 # 900초(15분) 동안 1회 이상 변경 시
save 300 10 # 300초(5분) 동안 10회 이상 변경 시
save 60 10000 # 60초(1분) 동안 10000회 이상 변경 시
# 자동 저장 비활성화
save ""
# RDB 파일 경로
dir /var/lib/redis/
dbfilename dump.rdb
# RDB 압축 (기본: yes)
rdbcompression yes
# RDB 체크섬 (기본: yes)
rdbchecksum yes
fork()와 Copy-on-Write
BGSAVE의 동작 흐름
1. BGSAVE 명령 수신
2. fork() 시스템 콜 호출
┌─────────────────────────────────────┐
│ fork() 실행 │
│ │
│ 부모 프로세스 자식 프로세스 │
│ (Redis 서버) (RDB 저장) │
│ ┌───────────┐ ┌───────────┐ │
│ │ 계속 명령어 │ │ 메모리 │ │
│ │ 처리 │ │ 스냅샷을 │ │
│ │ │ │ 디스크에 │ │
│ │ │ │ 저장 │ │
│ └───────────┘ └───────────┘ │
│ │ │ │
│ │ CoW 공유 메모리 │ │
│ └────────┬───────────┘ │
│ │ │
│ ┌──────────┴──────────┐ │
│ │ 물리 메모리 페이지 │ │
│ │ (읽기 전용 공유) │ │
│ └─────────────────────┘ │
└─────────────────────────────────────┘
3. 자식 프로세스가 RDB 파일 저장 완료
4. 부모 프로세스에 완료 시그널 전송
5. 자식 프로세스 종료
Copy-on-Write 상세
fork() 직후:
부모 페이지 테이블 → ┐
├→ 물리 페이지 A (읽기 전용)
자식 페이지 테이블 → ┘
부모가 페이지 A를 수정할 때:
1. OS가 페이지 폴트 발생
2. 페이지 A를 복사하여 새로운 페이지 A' 생성
3. 부모는 A'에 쓰기, 자식은 여전히 원본 A를 참조
부모 → 페이지 A' (수정된 데이터)
자식 → 페이지 A (fork 시점의 원본 데이터)
메모리 사용량
시나리오: Redis 메모리 10GB, 쓰기 비율 30%
fork 직후:
부모: 10GB (논리), 자식: 10GB (논리)
실제 물리 메모리: ~10GB (CoW로 공유)
BGSAVE 진행 중 (쓰기 30%):
수정된 페이지만 복사: 10GB × 30% = 3GB 추가
실제 물리 메모리: ~13GB
→ 서버 메모리는 최소 데이터 크기의 1.5배 확보 필요
THP (Transparent Huge Pages) 주의
# THP가 활성화되면 CoW 복사 단위가 4KB → 2MB로 커짐
# 소량의 수정에도 대량의 메모리가 복사될 수 있음
# THP 비활성화 (Redis 공식 권장)
echo never > /sys/kernel/mm/transparent_hugepage/enabled
# redis.log에서 경고 확인
# WARNING you have Transparent Huge Pages (THP) support enabled
fork() 지연 (Fork Latency)
fork() 자체는 Copy-on-Write 덕분에 빠르지만, 페이지 테이블 복사 비용이 있습니다.
데이터 크기별 fork 지연 (대략):
1GB → ~10ms
10GB → ~100ms
25GB → ~200~500ms
fork 중에는 메인 스레드가 블로킹됨!
→ 대용량 Redis에서는 fork 지연이 서비스에 영향을 줄 수 있음
fork 지연 모니터링
# INFO persistence에서 확인
INFO persistence
# latest_fork_usec: 마지막 fork의 마이크로초 지연
# 예: latest_fork_usec: 125000 → 125ms
fork 지연 줄이는 방법
- 데이터 크기 제한: 가능하면 인스턴스당 25GB 이하 유지
- replica에서 BGSAVE: 마스터 부하 감소
- vm.overcommit_memory = 1: fork 실패 방지
# fork 실패 방지 (Linux)
echo 1 > /proc/sys/vm/overcommit_memory
# 0: 기본 (커널이 판단), 1: 항상 허용, 2: 물리 메모리+스왑 이내만 허용
RDB 파일 구조
┌──────────┬─────────┬──────────┬─────────────┬──────────┐
│ REDIS │ RDB │ AUX │ DB 데이터 │ EOF + │
│ (5bytes) │ version │ fields │ (키-값 쌍) │ checksum │
│ "REDIS" │ "0011" │ 메타데이터│ │ 8bytes │
└──────────┴─────────┴──────────┴─────────────┴──────────┘
AUX 필드 (메타데이터)
redis-ver: "7.2.4"
redis-bits: 64
ctime: 1711238400 (생성 시각)
used-mem: 10737418240 (메모리 사용량)
aof-base: 0
DB 데이터 영역
각 키-값 쌍:
┌──────────┬───────────┬──────┬───────┐
│ 만료시각 │ 타입/인코딩│ 키 │ 값 │
│ (선택적) │ │ │ │
└──────────┴───────────┴──────┴───────┘
RDB 파일 검증
# redis-check-rdb로 파일 무결성 검사
redis-check-rdb /var/lib/redis/dump.rdb
# RDB 파일 크기 확인
ls -lh /var/lib/redis/dump.rdb
RDB 로드 과정
서버 시작
│
▼
AOF가 활성화되어 있는가?
│ Yes → AOF 파일 로드
│ No
▼
RDB 파일이 존재하는가?
│ Yes → RDB 파일 로드
│ No → 빈 데이터베이스로 시작
# 로드 속도 (대략)
# RDB는 바이너리 포맷이라 매우 빠름
# 10GB RDB: ~10~20초 (SSD 기준)
# 로드 중에는 클라이언트 요청을 처리하지 않음
# 로드 진행 상황은 로그에서 확인
# * Loading RDB produced by version 7.2.4
# * DB loaded from disk: 15.234 seconds
실전 설정 패턴
패턴 1: 캐시 전용 (영속성 불필요)
save "" # RDB 비활성화
appendonly no # AOF 비활성화
패턴 2: 적당한 영속성
save 300 10 # 5분마다 10회 이상 변경 시
save 60 10000 # 1분마다 10000회 이상 변경 시
rdbcompression yes
rdbchecksum yes
패턴 3: 높은 영속성 (RDB + AOF)
save 300 10
appendonly yes
appendfsync everysec # 매초 fsync
aof-use-rdb-preamble yes # 혼합 모드 (7.0+)
RDB의 장단점
장점
- 컴팩트: 바이너리 포맷으로 파일 크기가 작음
- 빠른 복구: AOF 대비 로딩 속도가 빠름
- 백업 용이: 단일 파일로 백업/복사 간단
- 성능: BGSAVE가 자식 프로세스에서 수행되어 서비스 영향 최소
단점
- 데이터 유실: 마지막 스냅샷 이후 데이터 유실 가능
- fork 비용: 대용량 데이터에서 fork 지연 발생
- 메모리 오버헤드: CoW로 인한 추가 메모리 사용 (최대 2배)
모니터링
# RDB 관련 INFO 항목
INFO persistence
# 주요 확인 항목:
# rdb_last_save_time — 마지막 저장 시각
# rdb_last_bgsave_status — 마지막 BGSAVE 성공 여부
# rdb_last_bgsave_time_sec — 마지막 BGSAVE 소요 시간
# rdb_current_bgsave_time_sec — 현재 진행 중인 BGSAVE 시간
# rdb_last_cow_size — 마지막 BGSAVE의 CoW 메모리 사용량
# latest_fork_usec — 마지막 fork 지연 (마이크로초)
정리
RDB 스냅샷은 Redis 영속성의 기본 메커니즘입니다.
- fork() + Copy-on-Write로 서비스 중단 없이 스냅샷을 생성합니다
- fork 직후에는 메모리를 공유하고, 수정된 페이지만 복사하여 메모리 효율적입니다
- 대용량 데이터에서는 fork 지연과 CoW 메모리 오버헤드를 주의해야 합니다
- THP 비활성화, vm.overcommit_memory = 1 설정이 권장됩니다
- RDB는 컴팩트하고 복구가 빠르지만, 마지막 스냅샷 이후 데이터 유실 위험이 있습니다
- 데이터 유실을 최소화하려면 AOF와 함께 사용하는 것이 좋습니다
댓글 로딩 중...