- 핵심: 클러스터 노드가 내려갔다 올라올 때 데이터가 남으려면 세 가지가 동시에 필요하다.
- 디스크 지속성(AOF/RDB)이 켜져 있어 재시작 시 메모리를 복원할 수 있다.
nodes.conf(클러스터 설정 파일)가 살아남아 같은 노드 ID로 클러스터에 재합류한다.- 그 사이 failover가 일어났다면 복귀 노드의 데이터는 어차피 버려지므로, replica 쪽에 최신 데이터가 있어야 한다.
- 흔한 오해: “AOF만 켜두면 재시작해도 데이터가 그대로다.” → failover가 끼면 복원한 데이터는 전부 폐기된다. 지속성은 failover가 없었을 때와 클러스터 전체가 내려갔을 때를 위한 안전장치다.
- AOF와 RDB가 각각 무엇인지는 Redis 지속성 - RDB와 AOF 참고.
데이터가 사라지는 세 가지 경로#
1. 지속성이 꺼져 있다#
- Redis는 인메모리 저장소다.
appendonly no + save "" 상태면 프로세스가 죽는 순간 데이터셋이 사라지고, 재시작한 노드는 빈 데이터셋으로 올라온다. - 캐시 용도로 일부러 지속성을 끄는 구성이 흔한데, 이때 auto-restart가 켜져 있으면 최악의 조합이 된다.
- 마스터 A, replica B·C 구성. A가 죽고 프로세스 매니저가 즉시 재시작한다.
- A는 빈 데이터셋으로 복귀한다. 클러스터가 장애를 감지해 failover를 시작하기도 전에 살아 돌아왔으므로 여전히 마스터다.
- B와 C가 A와 재동기화하면서 자기들이 가진 멀쩡한 사본까지 비운다. 전체 유실.
- 공식 문서가 명시적으로 경고하는 시나리오다. 지속성을 못 켤 상황이면 자동 재시작을 끄는 것이 정답이다.
2. 이미 failover가 일어났다#
- 노드가
cluster-node-timeout 이상 응답하지 않으면 마스터 과반이 FAIL로 합의하고, replica 중 하나가 승격된다. 승격은 보통 1~2초 안에 끝난다. - 이후 옛 마스터가 AOF로 데이터를 온전히 복원해 돌아와도, 클러스터 규칙에 따라 강등된다.
- 규칙: 마지막 해시 슬롯을 빼앗아 간 노드의 replica가 된다. 담당 슬롯이 0이 되는 순간 역할이 뒤집힌다.
- replica가 되면 새 마스터와 동기화하면서 자기 데이터셋을 버린다. 디스크에서 애써 읽어 올린 데이터는 의미가 없어진다.
- 즉 이 경로에서 데이터를 지키는 주체는 지속성이 아니라 replica다. failover 시점에 replica가 얼마나 따라잡았는지가 유실량을 결정한다.
- Redis 클러스터는 비동기 복제 + “last failover wins” 병합이라, 마스터가 클라이언트에 응답한 쓰기가 replica에 도달하기 전에 죽으면 그 쓰기는 영구히 사라진다. 구조적으로 유실 윈도우가 존재한다.
3. nodes.conf가 사라졌다#
- 노드 ID는 최초 기동 시 160비트 난수로 만들어져
cluster-config-file(관례상 nodes.conf)에 저장되고, 그 파일이 지워지지 않는 한 영구히 유지된다. - 이 파일에는 노드 ID, 슬롯 소유권,
currentEpoch/configEpoch가 들어 있고, 값이 바뀔 때마다 디스크에 fsync된다. - 컨테이너 환경에서
/data를 볼륨으로 안 잡으면 재시작 때 이 파일이 통째로 날아간다. 그러면 노드는 새 ID를 들고 클러스터를 모르는 상태로 올라오고, AOF 파일이 남아 있어도 슬롯 소유권을 되찾지 못한다.
대응 1: 지속성 켜기#
- AOF와 RDB를 둘 다 켜는 것이 기본 권장 구성이다. 재시작 시에는 더 완전한 AOF를 우선 로드한다.
appendonly yes
appendfsync everysec
save 900 1
save 300 10
save 60 10000
| 값 | 동작 | 유실 범위 |
|---|
always | 명령 배치마다 fsync | 사실상 없음 (매우 느림) |
everysec | 1초마다 fsync (기본·권장) | 최대 1초 |
no | OS에 맡김 | 리눅스 기준 최대 30초 |
- RDB만 쓰면 마지막 스냅샷 이후 쓰기가 통째로 날아간다. 백업·빠른 재시작용으로는 좋지만 유실 최소화 목적으로는 부족하다.
- Redis 7.0부터 AOF는 multi-part 구조다. base 파일(RDB 포맷 스냅샷) + incr 파일 여러 개 + manifest가
appenddirname 디렉터리에 함께 놓인다. 백업할 때 파일 하나만 챙기면 안 된다. - 운영 중인 서버에 AOF를 켤 때는 절차를 지켜야 한다. 설정 파일만 고치고 재시작하면 유실된다.
redis-cli config set appendonly yes
redis-cli config rewrite # 설정 파일에 반영. 빠뜨리면 재시작 시 원복되어 데이터가 날아간다
- 재시작 전
INFO persistence에서 aof_rewrite_in_progress와 aof_rewrite_scheduled가 0이고 aof_last_bgrewrite_status가 ok인지 확인한다.
대응 2: 클러스터 신원 영속화#
- 데이터 파일과
nodes.conf를 같은 영속 볼륨에 둔다.
dir /data
cluster-enabled yes
cluster-config-file /data/nodes.conf
appendonly yes
- 쿠버네티스라면 StatefulSet +
volumeClaimTemplates로 파드마다 고정 PVC를 붙이고 /data에 마운트한다. 파드가 재스케줄되어도 같은 노드 ID·같은 AOF로 복귀한다. - 노드 ID를 새로 발급받게 만드는
CLUSTER RESET HARD는 이 맥락에서 사실상 데이터 포기 선언이다. 복구 절차에 함부로 넣지 않는다.
대응 3: replica로 failover 유실 윈도우 줄이기#
- 마스터마다 replica를 최소 1개 둔다. replica가 없는 마스터가 죽으면 failover 자체가 불가능하고, 해당 슬롯은 노드가 돌아올 때까지 통째로 멈춘다.
- 비동기 복제의 유실 창을 제한하는 설정:
min-replicas-to-write 1
min-replicas-max-lag 10
- 지연이
min-replicas-max-lag초 이하인 replica가 min-replicas-to-write개 미만이면 마스터가 쓰기를 거부한다. 유실을 없애지는 못하고 한계를 씌우는 장치다. - 개별 쓰기 단위로 확인이 필요하면
WAIT numreplicas timeout으로 복제 완료를 기다린다. 다만 WAIT도 Redis를 CP 시스템으로 만들지는 못한다 — 확인받은 쓰기조차 failover 중에 사라질 수 있고, 다만 확률이 크게 낮아진다. - 파티션 상황에서 소수파에 남은 마스터는
cluster-node-timeout 경과 후 스스로 쓰기를 거부한다. 그 이후 유실은 더 이상 늘지 않는다.
대응 4: 계획된 재시작 절차#
- 정상 종료는
SHUTDOWN을 쓴다. save & quit이 수행되어 마지막 상태가 디스크에 남는다.- replica는 이때 RDB 파일에 replication ID와 offset을 함께 저장한다. 재기동 시 **부분 재동기화(partial resync)**로 붙어 전체 전송을 피할 수 있다.
- 단, AOF로 재기동한 replica는 부분 재동기화가 불가능해 full resync를 한다. 업그레이드 전에 잠시 RDB로 전환해두는 우회법이 있다.
- 클러스터 노드를 한 번에 과반 이상 재시작하지 않는다. 쿼럼이 깨지면 failover가 불가능해지고 클러스터 전체가 쓰기 불가 상태에 빠진다. 롤링으로 하나씩 내린다.
전체 노드가 동시에 내려갔다 올라올 때#
- 결론: 정상 종료였다면 유실되지 않고, 급사였다면
appendfsync 정책만큼만 잃는다.- 각 노드가 자기 디스크의 AOF/RDB에서 데이터를 복원하고,
nodes.conf에 남은 역할·슬롯 소유권으로 클러스터를 그대로 재구성한다. - 오히려 failover가 개입하지 않으므로, 복원한 데이터가 강등 때문에 폐기되는 문제가 없다. 이 시나리오만은 replica가 아니라 지속성이 유일한 방어선이다.
종료 방식이 유실량을 결정한다#
| 종료 방식 | 유실량 |
|---|
SHUTDOWN / SIGTERM / SIGINT | 없음 |
전원 차단, SIGKILL, OOM kill | appendfsync everysec 기준 최대 1초 |
| 위와 같으나 RDB만 사용 | 마지막 스냅샷 이후 전부 |
SHUTDOWN은 프로세스를 그냥 끄는 게 아니라 정해진 순서를 밟는다.- 뒤처진 replica가 있으면 쓰기 클라이언트를
CLIENT PAUSE로 멈추고 shutdown-timeout(기본 10초)만큼 따라잡기를 기다린다. - 모든 클라이언트를 끊는다.
- save point가 하나라도 설정돼 있으면 블로킹
SAVE를 수행한다. - AOF가 켜져 있으면 버퍼를 flush한다.
SIGTERM과 SIGINT을 받아도 동일한 시퀀스를 탄다. systemctl stop이나 쿠버네티스 파드 종료처럼 SIGTERM이 정상 전달되는 경로면 데이터는 온전히 안착한다.
그래도 유실이 생기는 네 가지 경로#
- 종료 시퀀스가 잘린다. 쿠버네티스의
terminationGracePeriodSeconds가 짧으면 SIGTERM 직후 SIGKILL이 날아간다. 위 시퀀스는 replica 대기 최대 10초에 데이터셋 크기에 비례하는 SAVE 시간이 더해지므로, 기본값 30초로는 큰 데이터셋에서 부족할 수 있다. 이러면 정상 종료가 아니라 급사가 된다. - 마스터와 replica의 디스크 내용이 다르다. 비동기 복제라 마스터에는 있고 replica에는 없는 쓰기가 항상 존재한다. 원래 마스터가 정상 복귀하면
nodes.conf 덕에 다시 마스터가 되고 replica가 재동기화하므로 문제없다. 하필 그 마스터만 디스크 손상 등으로 못 올라오면 replica를 승격시키는 수밖에 없고, 그 차이만큼 사라진다. nodes.conf가 없다. 데이터 파일이 멀쩡해도 노드들이 서로를 모르는 상태로 올라와 슬롯 소유권을 되찾지 못한다. 사실상 데이터에 접근할 수 없다.- AOF를 막 켠 직후에 죽었다. 초기 AOF 파일을 만드는 rewrite가 끝나기 전에 종료되면 “AOF는 켜져 있는데 파일은 없는” 상태로 재시작해 데이터셋 전체를 잃는다.
SHUTDOWN이 이 상황에서 FORCE 없이 명령을 거부하는 이유다.
유실이 아닌 것과 구분하기#
- 노드 일부만 먼저 올라온 시점에는 슬롯이 다 커버되지 않아
cluster_state:fail로 보인다. 가용성 문제일 뿐 데이터는 각자 디스크에 그대로 있다. - 나머지 노드가 올라오면 정상화된다. 이 상태를 보고 성급하게
CLUSTER RESET이나 클러스터 재생성으로 넘어가면 그때 진짜로 데이터가 날아간다.
복구 후 점검#
CLUSTER INFO의 cluster_state:ok는 슬롯이 누군가에게 할당되어 있다는 뜻이지 데이터가 있다는 뜻이 아니다. 빈 마스터가 슬롯을 들고 복귀해도 상태는 ok로 보인다.- 그래서 재시작 후에는 노드별
DBSIZE를 재시작 전 값과 비교한다. 슬롯 커버리지와 키 개수는 별개의 지표다. CLUSTER NODES로 복귀 노드의 역할(master/slave)과 슬롯 범위를 확인한다. 옛 마스터가 replica로 강등되어 있으면 정상 동작이다.INFO persistence의 rdb_last_bgsave_status, aof_last_write_status가 ok인지, loading:0으로 로딩이 끝났는지 본다.- AOF가 잘린 채 남았다면
aof-load-truncated yes(기본값)로 마지막 불완전 명령만 버리고 기동한다. 파일 중간이 손상된 경우에는 redis-check-aof --fix를 쓰되, 손상 지점 이후가 전부 잘려나갈 수 있으니 먼저 --fix 없이 실행해 위치를 확인한다.
참고 자료#