• 핵심: 클러스터 노드가 내려갔다 올라올 때 데이터가 남으려면 세 가지가 동시에 필요하다.
    1. 디스크 지속성(AOF/RDB)이 켜져 있어 재시작 시 메모리를 복원할 수 있다.
    2. nodes.conf(클러스터 설정 파일)가 살아남아 같은 노드 ID로 클러스터에 재합류한다.
    3. 그 사이 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
  • appendfsync 세 가지 정책:
동작유실 범위
always명령 배치마다 fsync사실상 없음 (매우 느림)
everysec1초마다 fsync (기본·권장)최대 1초
noOS에 맡김리눅스 기준 최대 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_progressaof_rewrite_scheduled가 0이고 aof_last_bgrewrite_statusok인지 확인한다.

대응 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 killappendfsync everysec 기준 최대 1초
위와 같으나 RDB만 사용마지막 스냅샷 이후 전부
  • SHUTDOWN은 프로세스를 그냥 끄는 게 아니라 정해진 순서를 밟는다.
    1. 뒤처진 replica가 있으면 쓰기 클라이언트를 CLIENT PAUSE로 멈추고 shutdown-timeout(기본 10초)만큼 따라잡기를 기다린다.
    2. 모든 클라이언트를 끊는다.
    3. save point가 하나라도 설정돼 있으면 블로킹 SAVE를 수행한다.
    4. AOF가 켜져 있으면 버퍼를 flush한다.
  • SIGTERMSIGINT을 받아도 동일한 시퀀스를 탄다. systemctl stop이나 쿠버네티스 파드 종료처럼 SIGTERM이 정상 전달되는 경로면 데이터는 온전히 안착한다.

그래도 유실이 생기는 네 가지 경로

  1. 종료 시퀀스가 잘린다. 쿠버네티스의 terminationGracePeriodSeconds가 짧으면 SIGTERM 직후 SIGKILL이 날아간다. 위 시퀀스는 replica 대기 최대 10초에 데이터셋 크기에 비례하는 SAVE 시간이 더해지므로, 기본값 30초로는 큰 데이터셋에서 부족할 수 있다. 이러면 정상 종료가 아니라 급사가 된다.
  2. 마스터와 replica의 디스크 내용이 다르다. 비동기 복제라 마스터에는 있고 replica에는 없는 쓰기가 항상 존재한다. 원래 마스터가 정상 복귀하면 nodes.conf 덕에 다시 마스터가 되고 replica가 재동기화하므로 문제없다. 하필 그 마스터만 디스크 손상 등으로 못 올라오면 replica를 승격시키는 수밖에 없고, 그 차이만큼 사라진다.
  3. nodes.conf가 없다. 데이터 파일이 멀쩡해도 노드들이 서로를 모르는 상태로 올라와 슬롯 소유권을 되찾지 못한다. 사실상 데이터에 접근할 수 없다.
  4. AOF를 막 켠 직후에 죽었다. 초기 AOF 파일을 만드는 rewrite가 끝나기 전에 종료되면 “AOF는 켜져 있는데 파일은 없는” 상태로 재시작해 데이터셋 전체를 잃는다. SHUTDOWN이 이 상황에서 FORCE 없이 명령을 거부하는 이유다.

유실이 아닌 것과 구분하기

  • 노드 일부만 먼저 올라온 시점에는 슬롯이 다 커버되지 않아 cluster_state:fail로 보인다. 가용성 문제일 뿐 데이터는 각자 디스크에 그대로 있다.
  • 나머지 노드가 올라오면 정상화된다. 이 상태를 보고 성급하게 CLUSTER RESET이나 클러스터 재생성으로 넘어가면 그때 진짜로 데이터가 날아간다.

복구 후 점검

  • CLUSTER INFOcluster_state:ok슬롯이 누군가에게 할당되어 있다는 뜻이지 데이터가 있다는 뜻이 아니다. 빈 마스터가 슬롯을 들고 복귀해도 상태는 ok로 보인다.
  • 그래서 재시작 후에는 노드별 DBSIZE를 재시작 전 값과 비교한다. 슬롯 커버리지와 키 개수는 별개의 지표다.
  • CLUSTER NODES로 복귀 노드의 역할(master/slave)과 슬롯 범위를 확인한다. 옛 마스터가 replica로 강등되어 있으면 정상 동작이다.
  • INFO persistencerdb_last_bgsave_status, aof_last_write_statusok인지, loading:0으로 로딩이 끝났는지 본다.
  • AOF가 잘린 채 남았다면 aof-load-truncated yes(기본값)로 마지막 불완전 명령만 버리고 기동한다. 파일 중간이 손상된 경우에는 redis-check-aof --fix를 쓰되, 손상 지점 이후가 전부 잘려나갈 수 있으니 먼저 --fix 없이 실행해 위치를 확인한다.

참고 자료