AOF — 모든 쓰기 명령을 기록하는 영속성
Redis에 저장한 데이터는 서버가 꺼지면 전부 사라질까요? 인메모리 데이터베이스인데 어떻게 데이터를 보존할 수 있는 걸까요?
개념 정의
AOF(Append Only File) 는 Redis의 모든 쓰기 명령을 순서대로 파일에 기록하는 영속성 방식입니다. 서버 재시작 시 기록된 명령을 처음부터 재실행(replay)하여 데이터를 복원합니다.
# AOF 파일 내용 예시 (RESP 형식)
*3
$3
SET
$4
name
$5
redis
*3
$3
SET
$3
age
$2
10
위 내용은 SET name redis와 SET age 10 두 명령이 기록된 것입니다.
왜 AOF가 필요한가
Redis의 또 다른 영속성 방식인 RDB(스냅샷)와 비교하면 AOF의 필요성이 명확해집니다.
| 비교 항목 | RDB | AOF |
|---|---|---|
| 저장 방식 | 특정 시점의 메모리 스냅샷 | 모든 쓰기 명령을 순차 기록 |
| 데이터 유실 | 마지막 스냅샷 이후 데이터 유실 가능 | 설정에 따라 최대 1초 이내 유실 |
| 파일 크기 | 작음 (바이너리 압축) | 큼 (명령이 누적) |
| 복구 속도 | 빠름 | 느림 (명령 재실행) |
| 가독성 | 바이너리라 읽기 어려움 | 텍스트라 읽기 쉬움 |
RDB는 주기적으로 스냅샷을 찍기 때문에, 스냅샷 사이에 발생한 쓰기 명령은 유실 될 수 있습니다. 반면 AOF는 모든 쓰기를 기록하므로 데이터 유실을 최소화할 수 있습니다.
실무에서 AOF를 선택하는 대표적인 경우:
- 데이터 유실을 최소화해야 하는 서비스 (결제, 세션 등)
- 장애 복구 시 최대한 최신 상태로 복원해야 하는 경우
- RDB 스냅샷의 fork 비용이 부담스러운 대용량 인스턴스
AOF의 내부 동작
쓰기 흐름
AOF의 쓰기 과정은 크게 세 단계로 나뉩니다:
- **명령 실행 **: 클라이언트의 쓰기 명령이 메모리에 반영됩니다
- ** 버퍼에 추가 **: 실행된 명령이 AOF 버퍼(aof_buf)에 RESP 형식으로 추가됩니다
- ** 디스크 동기화 **: 설정된 정책에 따라 버퍼 내용이 디스크에 기록(fsync)됩니다
클라이언트 → [SET key value]
↓
메모리에 반영
↓
AOF 버퍼에 추가
↓
fsync 정책에 따라 디스크 기록
여기서 핵심은 3번째 단계의 fsync 정책 입니다. 이 정책이 성능과 안전성의 균형을 결정합니다.
appendfsync 옵션
redis.conf의 appendfsync 설정으로 디스크 동기화 주기를 제어합니다.
always
appendfsync always
- 모든 쓰기 명령마다 즉시
fsync()를 호출합니다 - **데이터 안전성 **: 최고. 최대 1개 명령만 유실 가능
- ** 성능 **: 최저. 매번 디스크 I/O가 발생하므로 throughput이 크게 떨어집니다
- ** 사용 사례 **: 데이터 유실이 절대 허용되지 않는 극히 제한적인 경우
everysec (기본값)
appendfsync everysec
- 1초마다 백그라운드 스레드가
fsync()를 호출합니다 - ** 데이터 안전성 **: 좋음. 최대 1초 분량의 데이터 유실 가능
- ** 성능 **: 양호.
always보다 훨씬 빠르면서도 합리적인 안전성 - ** 사용 사례 **: 대부분의 프로덕션 환경에서 권장되는 옵션
no
appendfsync no
- Redis가 직접
fsync()를 호출하지 않고 OS에 위임 합니다 - 리눅스의 경우 보통 30초마다 OS가 플러시합니다
- **데이터 안전성 **: 낮음. OS 버퍼에 있는 데이터가 유실될 수 있음
- ** 성능 **: 최고. 디스크 I/O 부담이 가장 적음
- ** 사용 사례 **: 캐시 용도로만 사용하여 데이터 유실이 무관한 경우
Redis 공식 문서에서도 everysec를 기본값으로 권장합니다. 성능과 안전성의 가장 합리적인 균형점입니다.
AOF Rewrite — 파일이 무한히 커지는 문제 해결
문제: AOF 파일의 비대화
AOF는 모든 명령을 기록하기 때문에 시간이 지나면 파일이 계속 커집니다. 예를 들어:
SET counter 1
INCR counter # counter = 2
INCR counter # counter = 3
INCR counter # counter = 4
DEL counter
SET counter 100
이 6개 명령의 최종 결과는 counter = 100이지만, AOF에는 6줄이 모두 남아 있습니다. 이렇게 ** 중간 과정이 쌓이면 파일이 불필요하게 커집니다 **.
해결: BGREWRITEAOF
AOF Rewrite는 현재 메모리 상태를 기준으로 ** 최소한의 명령으로 새 AOF 파일을 생성 **합니다.
# 수동 실행
redis-cli BGREWRITEAOF
Rewrite 과정:
- Redis가 ** 자식 프로세스를 fork**합니다
- 자식 프로세스가 현재 메모리 상태를 읽어 새 AOF 파일을 작성합니다
- Rewrite 중에 들어오는 새 쓰기 명령은 AOF rewrite 버퍼 에 별도로 쌓입니다
- 자식 프로세스가 완료되면, rewrite 버퍼의 내용을 새 AOF 파일 끝에 추가합니다
- 새 AOF 파일이 기존 파일을 원자적으로(atomically) 대체 합니다
[메인 프로세스] [자식 프로세스]
| |
| -- fork -----------> |
| | 메모리 → 새 AOF 파일 작성
| 새 명령 → rewrite 버퍼 |
| | 작성 완료
| rewrite 버퍼 → 새 AOF |
| 기존 AOF → 새 AOF 교체 |
자동 Rewrite 설정
# AOF 파일이 마지막 rewrite 이후 100% 이상 커졌을 때
auto-aof-rewrite-percentage 100
# 최소 파일 크기 (이보다 작으면 rewrite하지 않음)
auto-aof-rewrite-min-size 64mb
예를 들어 마지막 rewrite 후 AOF가 64MB였다면, 128MB가 되었을 때 자동으로 rewrite가 트리거됩니다.
혼합 영속성 — RDB + AOF (Redis 7.0+)
Redis 7.0부터는 aof-use-rdb-preamble 옵션이 기본으로 활성화되어, AOF rewrite 시 혼합 형식 을 사용합니다.
동작 방식
aof-use-rdb-preamble yes # Redis 7.0+ 기본값
AOF rewrite가 실행되면:
- 새 AOF 파일의 앞부분 에 현재 메모리 상태를 RDB 형식(바이너리) 으로 저장합니다
- rewrite 이후 들어온 명령은 뒷부분 에 기존 AOF 형식(텍스트)으로 추가합니다
┌─────────────────────────────────┐
│ RDB 형식 (바이너리 스냅샷) │ ← rewrite 시점의 전체 상태
├─────────────────────────────────┤
│ AOF 형식 (RESP 명령) │ ← rewrite 이후의 새 명령들
└─────────────────────────────────┘
혼합 모드의 장점
- **복구 속도 향상 **: RDB 부분은 바이너리로 빠르게 로드하고, 나머지 AOF 명령만 재실행합니다
- ** 파일 크기 감소 **: RDB 부분이 압축되어 순수 AOF보다 작습니다
- ** 데이터 안전성 유지 **: rewrite 이후 명령은 여전히 AOF로 기록됩니다
순수 AOF 방식에서 복구가 느렸던 문제를 RDB의 빠른 로딩으로 해결한 셈입니다.
Redis 7.0의 Multi-Part AOF
Redis 7.0에서는 AOF 파일 관리 방식이 크게 바뀌었습니다. 기존에는 하나의 AOF 파일이었지만, 이제는 ** 여러 파일로 분리 **됩니다:
appendonlydir/
├── appendonly.aof.1.base.rdb # 베이스 파일 (RDB 형식)
├── appendonly.aof.1.incr.aof # 증분 파일 1
├── appendonly.aof.2.incr.aof # 증분 파일 2
└── appendonly.aof.manifest # 매니페스트 (파일 목록 관리)
- **base 파일 **: rewrite 시 생성되는 기본 스냅샷 (RDB 형식)
- **incr 파일 **: rewrite 이후 누적된 명령들
- **manifest 파일 **: 어떤 파일을 어떤 순서로 로드할지 관리
이 구조 덕분에 rewrite 중 장애가 발생해도 기존 파일이 손상되지 않습니다.
AOF 설정 실전 예제
기본 설정
# AOF 활성화
appendonly yes
# AOF 파일 저장 디렉토리 (Redis 7.0+)
appenddirname "appendonlydir"
# fsync 정책
appendfsync everysec
# 자동 rewrite 설정
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# 혼합 모드 (Redis 7.0+ 기본값)
aof-use-rdb-preamble yes
AOF 파일 검증 및 복구
AOF 파일이 손상되었을 때 redis-check-aof 도구로 검증하고 복구할 수 있습니다.
# AOF 파일 검증
redis-check-aof appendonly.aof
# 손상된 AOF 파일 복구 (잘린 부분 제거)
redis-check-aof --fix appendonly.aof
# Redis 7.0+ Multi-Part AOF 검증
redis-check-aof --fix appendonlydir/appendonly.aof.manifest
상태 모니터링
# AOF 관련 정보 확인
redis-cli INFO persistence
주요 확인 항목:
aof_enabled:1 # AOF 활성화 여부
aof_rewrite_in_progress:0 # rewrite 진행 중 여부
aof_last_rewrite_time_sec:2 # 마지막 rewrite 소요 시간
aof_current_size:12345678 # 현재 AOF 파일 크기
aof_base_size:6000000 # 마지막 rewrite 후 크기
aof_buffer_length:0 # AOF 버퍼 크기
RDB와 AOF, 어떻게 조합할까
실무에서는 RDB와 AOF를 함께 사용하는 경우가 많습니다. 각 조합의 특성을 정리합니다.
RDB만 사용
save 3600 1 # 1시간마다 스냅샷
appendonly no
- 백업이 간편하고 복구가 빠릅니다
- 마지막 스냅샷 이후 데이터 유실 가능
- 캐시 용도에 적합
AOF만 사용
save "" # RDB 비활성화
appendonly yes
appendfsync everysec
- 데이터 유실 최소화 (최대 1초)
- 복구 속도가 RDB보다 느림
- 파일 크기가 상대적으로 큼
RDB + AOF 동시 사용 (권장)
save 3600 1
appendonly yes
appendfsync everysec
aof-use-rdb-preamble yes
- Redis 재시작 시 AOF를 우선으로 로드 (더 완전한 데이터)
- RDB는 백업/복제 용도로 활용
- 혼합 모드로 복구 속도까지 확보
AOF로 안전성을 확보하고, RDB로 빠른 백업과 복제를 지원하는 조합이 가장 실용적입니다.
함정/Pitfall
- ** 디스크 공간 모니터링 **: AOF 파일이 예상보다 빠르게 커질 수 있습니다.
auto-aof-rewrite-percentage를 적절히 설정해야 합니다 - **rewrite 중 메모리 사용량 **: fork 시 COW(Copy-on-Write)로 인해 일시적으로 메모리 사용량이 증가할 수 있습니다
- **fsync와 성능 트레이드오프 **:
always는 안전하지만 느리고,no는 빠르지만 위험합니다. 대부분everysec가 적절합니다 - **AOF 파일 직접 수정 금지 **: 잘못된 편집은 데이터 손상으로 이어집니다. 반드시
redis-check-aof로 검증해야 합니다
정리
| 항목 | 핵심 내용 |
|---|---|
| 저장 방식 | 모든 쓰기 명령을 RESP 형식으로 순차 기록 |
| fsync 정책 | always(매번), everysec(1초, 권장), no(OS 위임) |
| Rewrite | BGREWRITEAOF로 현재 상태 기준 최소 명령 재작성, fork 기반 |
| 혼합 모드 | 7.0+ RDB(앞부분) + AOF(뒷부분)로 복구 속도와 안전성 동시 확보 |
| 권장 조합 | RDB + AOF 동시 사용, aof-use-rdb-preamble yes |