← 기술 지식

기술 지식 / 데이터 시스템 / 데이터베이스 / Redis

Redis 비동기 복제와 승격의 확인 경계

한 쓰기가 주 노드·복제본·WAIT·Sentinel 장애 전환을 통과하는 순서를 따라가며 복제 확인과 실제 보존을 분리합니다.

데이터베이스 · 원리·구현·운영
기술 지식 트리

Redis 장애 전환을 이해하는 가장 쉬운 방법은 한 쓰기의 상태를 primary 메모리 적용 → 복제 스트림 처리 → WAIT/WAITAOF 관찰 → Sentinel 후보 선택 → 새 primary에서 값 확인으로 나누는 것입니다. 각 단계의 확인은 다음 단계의 성공을 보증하지 않습니다. 특히 클라이언트가 받은 응답과 승격된 노드의 상태가 어긋날 수 있으므로, 복제 확인·디스크 기록·승격·업무 원장을 별도의 계약으로 설계해야 합니다.

WAIT 확인과 장애 전환 뒤 값 유실 경계

결제 직전에 Redis의 처음 쓰기를 받는 주 노드(primary)가 SET payment:42 approved를 처리했고, 애플리케이션이 복제본(replica) 두 대의 확인까지 기다렸다고 해 보겠습니다. 잠시 뒤 주 노드가 고장 나고 Sentinel이 복제본 C를 새 주 노드로 승격했는데 C에는 그 쓰기가 없을 수 있습니다. 이 장면은 WAIT가 거짓말을 했다는 뜻이 아니라, “이 연결의 앞선 쓰기를 몇 개의 복제본이 받았는가”와 “장애 뒤 실제로 어떤 노드가 승격되어 살아남는가”가 다른 질문이라는 뜻입니다.

Redis의 기본 복제는 비동기입니다. 주 노드는 매번 복제본이 명령을 처리할 때까지 기다리지 않고 계속 요청을 처리합니다. 복제본은 복제 스트림에서 처리한 위치를 주기적으로 주 노드에 알립니다. WAIT는 이 연결에서 앞서 실행된 쓰기가 지정한 복제본 수에 전달되고 확인될 때까지 기다리지만, Redis를 강한 일관성의 합의 시스템으로 바꾸지는 않습니다.

복제 확인과 승격 선택의 흐름

아래에서 실제 offset은 주 노드가 만든 복제 스트림의 바이트 위치입니다. 명령 하나마다 일정하게 1씩 증가하는 숫자가 아니므로, 그림과 예시의 p0·p1은 실제 수치가 아닌 한 쓰기 전후의 논리적 단계 표기로만 사용합니다. A, B, C는 서로 다른 복제본이며, 확인 숫자 자체보다 확인한 노드와 장애 뒤 승격된 노드가 같은지 추적하는 것이 중요합니다.

복제 확인과 승격 경계클라이언트: SET payment:42 approved. 처음 주 노드: 메모리 적용·p1 생성. 복제본 A: p1 처리. 복제본 B: p1 처리. Sentinel: 장애 판단·승격 승인. 복제본 C: p0에 머묾. 승격된 주 노드: C가 선택될 수 있음쓰기 요청복제 스트림복제 스트림ACK 반환ACK 반환상태 보고후보 선택주 노드 승격클라이언트SET payment:42 approved처음 주 노드메모리 적용·p1 생성복제본 Ap1 처리복제본 Bp1 처리Sentinel장애 판단·승격 승인복제본 Cp0에 머묾승격된 주 노드C가 선택될 수 있음
복제 확인과 승격 경계 화살표는 쓰기와 복제 확인의 흐름입니다. 복제본의 ACK는 주 노드로 돌아가며, Sentinel은 별도로 생존 후보를 관찰·선택합니다.
그림을 글로 읽기
  • 클라이언트: SET payment:42 approved
  • 처음 주 노드: 메모리 적용·p1 생성
  • 복제본 A: p1 처리
  • 복제본 B: p1 처리
  • Sentinel: 장애 판단·승격 승인
  • 복제본 C: p0에 머묾
  • 승격된 주 노드: C가 선택될 수 있음
  1. 클라이언트 → 처음 주 노드: 쓰기 요청
  2. 처음 주 노드 → 복제본 A: 복제 스트림
  3. 처음 주 노드 → 복제본 B: 복제 스트림
  4. 복제본 A → 처음 주 노드: ACK 반환
  5. 복제본 B → 처음 주 노드: ACK 반환
  6. 복제본 C → Sentinel: 상태 보고
  7. Sentinel → 승격된 주 노드: 후보 선택
  8. 복제본 C → 승격된 주 노드: 주 노드 승격

그림의 예에서 WAIT 2가 반환되려면 A와 B가 쓰기 뒤의 단계 p1에 해당하는 바이트 위치 이상을 처리했다고 주 노드에 알렸을 수 있습니다. 그러나 A와 B가 같은 전원 도메인에서 함께 사라지고, C는 쓰기 전 단계인 p0에 머문 채 승격되면 payment:42가 새 주 노드에서 사라집니다. WAIT의 확인 집합과 Sentinel이 실제로 선택한 승격 집합이 자동으로 고정되는 것은 아닙니다.

WAIT 반환값의 복제 처리 의미

선택 기준은 업무 효과의 보존 요구에서 시작합니다. 단순 캐시라면 비동기 복제로 지연을 감수할 수 있지만, 재시도 시 중복 청구가 가능한 쓰기라면 WAIT 숫자만 늘리기보다 요청 ID를 권위 원장과 연결하고, 장애 뒤 새 primary·옛 primary·원장을 대사하는 경로를 둡니다. WAIT가 반환한 숫자는 복제 확인 사건의 증거이지 성공 응답의 최종 업무 결과 자체가 아닙니다.

SET payment:42 approved 뒤 같은 연결에서 WAIT 2 1000을 실행했다고 하겠습니다. 반환값이 2이면 앞선 쓰기가 적어도 두 복제본에 전달되고 그 복제본들이 해당 복제 스트림 위치 이상을 처리했다고 주 노드에 알린 것입니다. 반환값이 1이면 1000밀리초 동안 두 대를 기다렸지만 한 대만 확인한 상태로 반환한 것입니다.

timeout은 앞선 SET을 취소하거나 되돌리지 않습니다. 주 노드에는 이미 값이 남아 있을 수 있으므로 timeout 뒤 같은 효과를 무조건 재전송하면 중복 업무가 생길 수 있습니다.

이 확인은 다음을 보장하지 않습니다.

  • 확인한 복제본이 장애 뒤 살아남거나 반드시 승격된다는 보장
  • 복제본의 AOF가 fsync됐거나 저장장치 장애에도 남는다는 보장
  • Sentinel의 장애 판단과 승격이 즉시 완료된다는 보장
  • 외부 결제 승인, 메시지 발행 같은 효과와 Redis 쓰기가 하나의 원자 작업이라는 보장
  • timeout 뒤 명령이 실행되지 않았다는 보장

복제 문서의 표현대로 WAIT는 특정 실패 뒤 쓰기를 잃을 가능성을 크게 낮출 수는 있지만, 모든 장애 순서에서 손실을 없애지는 않습니다. 특히 네트워크 분할로 옛 주 노드가 잠시 살아 있으면 클라이언트가 옛 노드에 계속 쓰고, 분할이 해소된 뒤 그 노드가 새 주 노드의 복제본으로 재구성되면서 그 쓰기가 버려질 수 있습니다.

WAITAOF의 AOF fsync 확인 층

로컬 AOF와 복제본 AOF의 fsync까지 업무 조건으로 삼아야 하는 경우에는 Redis 7.2 이상에서 제공되는 WAITAOF를 별도로 봅니다. 예를 들어 SET payment:42 approved 뒤 WAITAOF 1 2 1000을 호출하면 로컬 Redis 한 대와 복제본 두 대가 이 연결의 앞선 쓰기를 AOF에 fsync했는지 기다리는 형태입니다. 반환값은 [local_count, replica_count]이고, timeout에 도달해도 실제 완료된 숫자를 반환하므로 클라이언트는 목표와 비교해야 합니다.

WAIT와 WAITAOF를 다음처럼 분리하면 혼동이 줄어듭니다.

명령 기다리는 사건 반환값 여전히 모르는 것
WAIT n t 앞선 쓰기의 복제 스트림 처리 확인 확인한 복제본 수 디스크 fsync, 승격 대상, 외부 효과
WAITAOF 1 n t (Redis 7.2 이상) 로컬 AOF와 복제본 AOF fsync 확인 [local, replicas] 모든 장애 도메인 생존, 승격 선택, 강한 일관성
min-replicas-to-write 일정 지연 이내에 연결된 복제본 수가 있는지에 따른 쓰기 수락 조건 조건 미충족 시 쓰기 오류 해당 쓰기가 실제로 각 복제본에 도착했는지
persistence 설정만 사용 각 노드의 RDB/AOF 기록 정책 파일·로그 상태 어떤 복제본이 승격될지, 옛 주 노드의 분할 쓰기

WAITAOF도 Redis를 강한 일관성 저장소로 만들지 않습니다. 공식 명령 문서는 모든 구성원이 디스크에 fsync할 때가 아니면 장애 전환이나 재시작에서 데이터가 여전히 손실될 수 있다고 명시합니다. 또한 WAITAOF는 복제본에서 실행할 수 없고 로컬 AOF가 꺼져 있으면 0이 아닌 numlocal을 사용할 수 없는 등 버전·설정 전제가 있습니다. Redis 7.2 미만 환경에서는 이 명령 자체를 전제로 삼지 말고, 현재 환경에서 지원 여부와 실제 반환 형식을 확인해야 합니다.

Sentinel 장애 전환과 WAIT 확인·승격 후보의 분리

Sentinel 장애 전환에는 여러 단계가 있습니다. 한 Sentinel이 주 노드에 닿지 않는다고 보는 SDOWN만으로는 충분하지 않고, 설정된 수의 Sentinel이 동의하는 ODOWN이 되어야 합니다. 이후 실제 장애 전환을 진행하려면 Sentinel 다수의 승인과 주도자 선출이 필요합니다. 따라서 설정된 quorum=2는 장애를 객관적으로 판단할 관찰 조건이지, 5개 Sentinel 중 3개가 승인해야 하는 장애 전환 다수 조건과 같은 숫자가 아닐 수 있습니다.

승격 후보를 고를 때도 “가장 많은 WAIT 확인을 보낸 노드”를 단순히 고르는 규칙은 아닙니다. 공식 Sentinel 문서의 흐름은 먼저 연결이 끊긴 시간이 너무 긴 후보를 제외하고, 그다음 replica-priority 값이 낮은 후보를 우선하며, 값이 같으면 복제 오프셋이 더 앞선 후보를 고르고 그래도 같으면 실행 ID로 결정합니다. 따라서 오프셋은 후보 선택에 영향을 주지만 WAIT가 확인한 특정 복제본을 고정하지 않으며, replica-priority=0인 노드는 승격 후보에서 제외됩니다.

장애 전환 추적
1. 여러 Sentinel이 주 노드의 비가용성을 관찰한다.
2. quorum을 충족해 ODOWN을 만든다.
3. Sentinel 다수의 장애 전환 승인을 얻는다.
4. 연결이 너무 오래 끊긴 복제본을 제외한다.
5. 복제 우선순위 → 처리한 오프셋 → 실행 ID 순서로 후보를 고른다.
6. 후보를 새 주 노드로 바꾸고 다른 복제본에 새 주인을 알린다.
7. 클라이언트는 새 주소를 찾고 오래된 연결·불확정 요청을 정리한다.

이 순서에서 3단계의 Sentinel 합의는 1단계에서 사라진 데이터를 복원하지 않습니다. 또 5단계의 오프셋 비교는 “이 후보가 과거 스트림에서 더 앞섰다”는 정보이지, 모든 성공 응답이 이 후보의 디스크에 영구 보존됐다는 약속이 아닙니다.

단일 쓰기의 성공·유실 추적

초기 상태를 다음과 같이 둡니다.

주 노드 P: 논리 단계 p0, balance:42 = 100
복제본 A: 논리 단계 p0
복제본 B: 논리 단계 p0
복제본 C: 논리 단계 p0
  1. 클라이언트가 DECRBY balance:42 30을 보내고 P가 메모리에서 잔액을 70으로 바꿉니다. 이 쓰기 뒤 복제 스트림의 실제 바이트 위치는 p0보다 앞선 어떤 값 p1이 됩니다. p0와 p1은 설명을 위한 논리 단계이지, 명령마다 1씩 증가하는 실제 offset 숫자가 아닙니다.
  2. A와 B가 p1에 해당하는 바이트 위치까지 처리하고 P에 확인합니다. C는 네트워크 지연으로 p0에 머뭅니다.
  3. 같은 연결에서 WAIT 2 1000을 호출해 2를 받습니다. 애플리케이션은 “두 복제본에 복제 확인”을 관찰했지만, 아직 어느 노드가 승격될지는 모릅니다.
  4. P와 A, B가 함께 있는 랙의 전원이 나가고 C와 Sentinel만 살아남습니다. Sentinel이 ODOWN과 다수 승인을 거쳐 C를 승격하면 C의 잔액은 100일 수 있습니다.
  5. 애플리케이션이 timeout 또는 연결 오류를 보고 같은 출금을 다시 보내면, 새 주 노드의 실제 값이 100인 경우 한 번만 차감되어 70이 됩니다. 그러나 첫 번째 출금이 사실 C에 이미 있었던 다른 장애 순서라면 재전송으로 40이 되어 이중 차감될 수 있습니다.
  6. 따라서 출금 요청 withdraw-9를 Redis의 상태와 별도로 조회 가능한 처리 기록 또는 권위 원장에 연결해야 합니다. 같은 ID가 이미 accepted인지, 잔액 변경과 기록이 어떤 순서로 남았는지 확인한 뒤에만 재시도를 검토합니다.

WAIT는 3단계에서 복제 확인을 얻을 가능성을 높일 수 있지만, 5단계의 불확정 결과를 없애지는 않습니다. 결과 응답을 잃었다면 확실한 사실은 클라이언트가 결과를 모른다는 것뿐입니다. 새 주 노드에서 요청 ID 기록이 보이지 않아도 원래 쓰기가 없었다고 결론 내릴 수 없습니다. 옛 주 노드나 확인 복제본에만 남았다가 승격 과정에서 사라졌을 수 있으므로, 권위 원장·Redis 변경의 원자 적용 계약·장애 뒤 대사 절차 없이 없음이라는 조회만으로 안전한 재시도를 결정하면 안 됩니다.

장애 순서별 관찰과 책임

장애 순서 클라이언트가 본 것 Redis에 남을 수 있는 것 안전한 처리
주 노드 적용 전 연결 끊김 오류 쓰기 없음일 수 있음 새 요청으로 시도할 수 있지만 멱등 ID 사용
주 노드 적용 후 WAIT 전 연결 끊김 오류 또는 timeout 주 노드에만 쓰기가 있을 수 있음 결과 조회 후 재시도 검토
WAIT 목표 미달 timeout 반환값 1 등 주 노드와 일부 복제본에 이미 쓰기 timeout을 되돌림으로 해석하지 않기
WAIT 목표 달성 후 ACK 복제본 손실 반환값 목표 이상 승격 노드가 오래됐으면 쓰기 소실 승격 후보와 장애 도메인 기록 비교
옛 주 노드가 분할 중 계속 쓰기 일부 성공 응답 재결합 시 옛 주 노드 쓰기 폐기 가능 min-replicas-to-write, fencing, 원장·멱등성
응답만 유실되고 쓰기는 성공 클라이언트가 모름 새 주 노드에 이미 반영됐을 수 있음 요청 ID 조회만으로 없음 판정 금지
WAITAOF 로컬 확인만 성공 [1,0] 로컬 AOF에는 fsync, 복제본에는 미확인 장애 전환·복제본 조건을 별도 확인
Sentinel quorum만 달성 장애 판단 가능 failover leader 승인 전일 수 있음 quorum과 majority를 분리해 관찰

min-replicas-to-write도 “현재 지연이 짧은 복제본이 충분히 연결되어 있을 때 쓰기를 받는다”는 최선 노력(best-effort) 안전 장치입니다. 특정 쓰기가 실제로 그 복제본에 도착했음을 확인하는 기능은 아니므로 WAIT와 같은 뜻으로 사용하면 안 됩니다. 반대로 WAIT의 숫자를 늘리는 것만으로 같은 랙·전원·네트워크 도메인에 몰린 복제본의 공통 장애를 해결할 수도 없습니다.

검증 입력과 예상 관찰 결과

예상 결과를 기록할 때는 “명령이 실행됐는가”, “어느 복제본이 처리했는가”, “어느 후보가 승격됐는가”, “외부 업무 효과가 확정됐는가”를 별도 열로 둡니다. 이 네 값을 하나의 success=true로 합치면 timeout·응답 유실·공통 장애 도메인 손실을 진단할 수 없습니다. 문서 기반 실험 계획과 실제 Redis/Sentinel 실행 결과를 혼동하지 않아야 합니다.

다음은 별도 주 노드·복제본·Sentinel 환경에서 수행할 검증 계획입니다. 이 세션에서 Redis를 실행한 결과가 아니라, 확인해야 할 입력과 예상 관찰입니다.

  1. 처음 주 노드에서 SET payment:42 approved 후 복제본을 일부러 지연시키고 WAIT 2 1000을 호출합니다. 목표에 도달하면 2, timeout이면 실제 확인 수 0 또는 1이 반환되는지 확인합니다. 어느 연결의 앞선 쓰기를 기준으로 하는지도 기록합니다.
  2. 같은 쓰기 뒤 Redis 7.2 이상에서 WAITAOF 1 2 1000을 호출합니다. 반환값 [local, replicas]를 목표와 비교하고, [1,1]이 [1,2] 목표를 만족하지 않는다는 것을 확인합니다. AOF가 꺼진 로컬 구성에서는 numlocal=1이 거절되는지도 확인합니다. Redis 7.2 미만에서는 이 실험을 지원되는 대체 확인으로 바꾸고 WAITAOF 결과로 기록하지 않습니다.
  3. WAIT 성공 직후 확인한 A와 B를 동시에 중단하고 C를 승격시킵니다. C의 실제 복제 스트림 바이트 위치와 키 값을 비교해 WAIT ACK 집합과 승격 대상이 다를 때의 관찰을 남깁니다. 명령당 offset이 1씩 증가한다고 계산하지 않습니다.
  4. Sentinel을 5개, quorum=2로 구성해 한 주 노드 장애를 재현합니다. 두 Sentinel이 장애를 봐도 다수 승인에 필요한 연결이 없으면 장애 전환이 시작되지 않는지, 세 Sentinel이 연결되면 진행되는지 확인합니다. quorum을 failover majority로 기록하지 않습니다.
  5. 복제본 A와 B의 replica-priority, 연결 끊김 시간, 실제 오프셋을 다르게 설정하고 후보 선택 로그를 확인합니다. priority가 오프셋보다 먼저 적용되고, priority 0 후보가 제외되는지 현재 버전에서 확인합니다.
  6. 쓰기 요청을 보낸 뒤 네트워크에서 응답만 버리고 새 주 노드에서 같은 요청 ID를 조회합니다. 기록이 보이면 중복 적용 없이 기존 결과를 확정할 수 있지만, 기록이 없다는 결과만으로 유실을 판정하거나 안전한 재시도를 결정하지 않습니다. 옛 주 노드·확인 복제본·권위 원장의 상태와 변경의 원자 적용 계약, 장애 뒤 대사 절차를 함께 확인합니다.

Redis 복제·WAIT·WAITAOF·Sentinel 공식 문서 참조

  • Redis replication: 비동기 복제, 처리 오프셋, 장애 전환 뒤 부분 재동기화
  • WAIT: 앞선 쓰기의 복제 확인 수, 시간 초과, 최선 노력 장애 전환
  • WAITAOF: Redis 7.2 이상에서 AOF fsync를 확인하는 범위와 한계
  • High availability with Redis Sentinel: quorum, 장애 전환 다수 승인, 복제본 선택과 분할 상황

결론은 확인을 세 문장으로 분리하는 것입니다. WAIT는 “내가 보낸 앞선 쓰기를 몇 복제본이 처리했는가”를 말합니다. WAITAOF는 그중 AOF fsync까지 완료된 로컬·복제본 수를 더 좁혀 말합니다. Sentinel 장애 전환은 “장애 뒤 어떤 후보를 새 주 노드로 정할 것인가”를 별도의 관찰·승인·선택 절차로 결정합니다. 어느 확인도 불확정 결과를 자동으로 exactly-once로 만들지 않으므로, 권위 있는 원장과 요청 ID, 결과 조회, 장애 뒤 대사를 함께 설계해야 합니다.

후속·관련 개념

함께 읽을 문서

전체 기술 지식
선택 연습 · 관련 질문 3개

질문은 이해를 확인하는 보조 자료입니다. 학습 경로와 문서의 범위는 질문 수와 독립적입니다.