k8s_redis_cluster_java_app_2
쿠버네티스 환경에서 Redis-Cluster Java App 구축 ②
Failover 테스트
🖧 Redis-Cluster 파드 다이어그램
1. Failover(장애조치) 테스트 시작
SSH 터미널을 2개 열어서, Terminal-1에서는 App 파드 생성, 레디스 파드 다운 등을 실행하고, Ternimal-2에서 App 로그를 본다.
1) Java(Spring) App 파드 생성
서버 (Terminal-1 [~/kube/app/cluster/])에서 실행
$ kubectl apply -f app-deployment.yaml -> 파드 생성
💻 App 로그
서버 (Terminal-2 [~/kube/app/cluster])에서 실행
$ kubectl logs -f -l app=spring-redis-cluster-app
1초개 10개씩 입력([SUCCESS])되는 것을 확인할 수 있다.
2) 레디스(Redis-0) 💥 파드 다운(delete)
Terminal-1) $ date; kubectl delete pod redis-cluster-0 -> 시각: 11:04:49
💻 Redis-0 로그
① 49초 레디스 다운 (+0초)
② 50초 레디스 재시작 (+1초)
③ 54초 마스터 -> 복제로 전환 (+5초)
Redis-0 전체 로그
💻 Redis-4 로그
① 49초 마스터와 연결 끊김 (+0초)
② 54초 복제 -> 마스터로 전환(승격) (+5초)
Redis-4 전체 로그
💻 App 로그
Terminal-2) $ kubectl logs -f -l app=spring-redis-sentinel-app
① 49초 레디스 연결 끊김 -> 재연결 시도 (+0초)
② 50초 Redis-1,2에는 데이터 입력됨 (+1초)
③ 53초~54초 Redis-0 4건 입력 실패 (+4초~+5초) 이후 Redis-4가 마스터로 전환(승격)되고 정상 입력됨
④ 59초 Redis-0 접속 성공 -> 입력 성공 (+10초)
App 전체 로그
🖧 Redis-Cluster 파드 다이어그램: Failover 상태 (Redis-4 Master-1)
2. Failback(원상복구) 테스트
1) 레디스(Redis-4) 파드 💥 다운(delete)
Terminal-1) $ date; kubectl delete pod redis-cluster-4 -> 시각: 11:05:49
💻 Redis-4 로그
① 49초 레디스 다운 (+0초)
② 52초 레디스 재시작 (+3초)
③ 55초 마스터 -> 복제로 전환 (+6초)
Redis-4 전체 로그
💻 Redis-0 로그
① 50초 마스터와 연결 끊김 (+1초)
② 55초 복제 -> 마스터로 전환 (+5초)
Redis-0 전체 로그
💻 App 로그
① 50초 Redis-4 연결 끊김 (+1초)
② 54초~56초 5건 입력 실패 (+4초~+6초) 이후 Redis-0이 마스터로 전화(승격)되고 정상 입력됨
③ 59초 Redis-4 연결 성공 (+10초)
App 전체 로그
🖧 Redis-Cluster 파드 다이어그램: 원상복구 상태 (Redis-0 Master-1)
🧑💻 테스트 검토 및 핵심 요약
이번 장애 조치(Failover/Failback) 테스트를 통해 쿠버네티스 환경의 Redis-Cluster와 Java(Spring Boot / Lettuce) 애플리케이션 간의 실제 장애 대응 동작 및 클러스터 토폴로지 재구성 과정을 검증했습니다.
⏱️ 장애 조치 타임라인 (Timeline)
| 구분 | 시각 (상대 시간) | Redis Cluster 노드 동작 | Java (Spring) App 동작 |
|---|---|---|---|
| 장애 발생 | 11:04:49 (+0초) | Master(Redis-0) 파드 종료 (delete) | Redis-0 연결 끊김 및 백그라운드 재접속 시도. 📍정상 노드(Redis-1, 2)를 향하는 데이터는 Async 처리에 의해 블로킹 없이 계속 입력 성공 (11:04:50~) |
| 재시도 한계 도달 | 11:04:53~54 (+4~5초) | Replica(Redis-4)가 장애 감지 후 승격 투표(Election) 진행 중 | Redis-0 슬롯으로 향하던 일부 요청(4건)이 백그라운드 5회(5초) 재시도 임계치를 넘겨 최종 실패(FAIL) 처리됨 |
| Failover 완료 | 11:04:54 (+5초) | 투표 완료 후 Replica(Redis-4)를 신규 Master로 승격 | 클러스터 토폴로지 갱신 시도 (ClusterTopologyRefresh) |
| 완전 정상화 | 11:04:55 (+6초) | 신규 Master(Redis-4)가 해당 슬롯(0~5460) 쓰기/읽기 시작 | 변경된 토폴로지가 적용되어 모든 슬롯에 대한 데이터가 다시 100% 정상(SUCCESS) 쓰기 재개됨 |
| 구 마스터 재기동 | 11:04:59 (+10초) | 재시작된 구 마스터(Redis-0)가 신규 마스터(Redis-4)의 Replica 노드로 재구성됨 | Redis-0 노드 연결 재확립(Reconnected) 완료 |
💡 실무 적용을 위한 핵심 시사점 (Key Takeaways)
-
1. Redis-0 마스터가 다운되어도 살아있는 Redis-1,2에는 정상적으로 입력됨
-
2. Cluster 노드 간 장애 감지 및 승격 시간 (약 4~5초):
Master 노드가 다운되면 나머지 노드들이cluster-node-timeout동안 상태를 확인(FAIL 판정)한 후, Replica 노드가 자동 승격하기까지 약 4~5초의 다운타임이 발생합니다. -
3. Spring / Lettuce 클라이언트의 Dynamic Topology Refresh(토폴로지 자동 갱신)의 중요성:
Redis Cluster 환경에서는 Master 노드가 변경되면 해당 노드가 담당하던 해시 슬롯(Hash Slot)의 소유권이 넘어갑니다. Spring App(Lettuce)의 클러스터 토폴로지 자동 갱신 옵션이 동작하여 변경된 Master IP와 슬롯 정보를 실시간으로 감지했기 때문에 별도의 서비스 재시작 없이 연동이 유지되었습니다. -
4. Client-Side Retry(5초)의 효과와 한계:
첫 번째 테스트(Redis-0 다운)에서는 App의 Retry(5초) 범위 내인 5초 시점에 Failover가 완료되어 성공률 100%(유실 0개)를 기록했습니다. 반면 두 번째 테스트(Redis-4 다운)에서는 토폴로지 재조율 시간 차이로 인해 1개의 실패 요청(keyA-00000607)이 발생했습니다. 따라서 실무에서는 App Retry뿐만 아니라 비동기 큐 처리 및 재시도 메커니즘을 함께 설계해야 완벽한 무중단을 보장할 수 있습니다. -
5. Cluster의 자동 노드 역할 재구성(Re-configuration):
복구된 구 Master(Redis-0) 노드가 다시 켜지면, 클러스터 버스를 통해 변경된 노드 상태를 수신하고 자동으로 신규 Master(Redis-4)의 Replica 노드로 전환됩니다. 따라서 관리자의 수동 개입 없이도 클러스터 전체의 HA(고가용성) 타깃 수량이 복구됩니다. -
6. 사용자 경험(UX) 및 타임아웃 고려사항:
클라이언트 관점에서는 장애 발생 시 약 4~5초간의 응답 지연(Latency)이 발생합니다. 웹/앱 프론트엔드 API 호출 시 사용자 화면이 멈추지 않도록 Front-end Timeout 및 Circuit Breaker(Resilience4j 등)를 적절히 조합하는 것을 권장합니다.
🛡️ Front-end Timeout 및 Circuit Breaker 설명
🛡️ 레디스 장애 시 시스템 마비를 막는 안전장치
센티널의 장애 조치(Failover) 동안 발생하는 4~5초간의 응답 지연(Latency)은 백엔드 서버와 사용자 화면으로 장애가 전파되는 연쇄 장애(Cascading Failure)를 유발할 수 있습니다. 이를 방지하기 위해 실무에서는 다음 두 가지 안전장치를 반드시 결합하여 구축합니다.
1. Front-end Timeout (프론트엔드 타임아웃)
사용자 브라우저나 모바일 앱이 백엔드 API를 호출한 후 "응답이 오지 않을 때 무한정 대기하지 않고 요청을 포기하는 최대 시간"입니다.
- 문제 상황 (Timeout 미설정 시): 레디스 장애 동안 4초간 응답이 멈추면, 답답한 사용자가 '새로고침'이나 '버튼'을 연타합니다. 이로 인해 백엔드 Tomcat/Spring 쓰레드(Thread)가 고갈되어 웹 서버 전체가 다운됩니다.
- 해결 방안 (2~3초 Timeout 적용): 2초 내 응답이 없으면 즉시 요청을 중단하고 "일시적인 네트워크 지연입니다. 잠시 후 다시 시도해 주세요"라는 안내를 띄워 백엔드 폭주를 차단합니다.
2. Circuit Breaker (서킷 브레이커 - Resilience4j)
전기 배전반의 '차단기(두꺼비집)'처럼 백엔드(Spring) 애플리케이션 내부에 설치하여, 레디스 장애 감지 시 차단기를 열어(Open) 레디스로의 접속 시도를 즉시 중단시키는 라이브러리입니다.
- 🟢 CLOSED (정상): 모든 캐시/데이터 요청을 레디스로 정상 전달합니다.
- 🔴 OPEN (차단): 레디스 지연/에러율이 지정치를 넘으면 차단기를 열어 레디스 호출을 아예 시도조차 하지 않고, 미리 준비된 대체 로직(Fallback: 예: Main RDB 직접 조회)을 즉시 실행합니다.
- 🟡 HALF-OPEN (검증): 레디스가 복구되었는지 확인하기 위해 소수의 요청만 살짝 보내보고, 정상 동작이 확인되면 다시 🟢 CLOSED 상태로 복구합니다.
🔄 장애 발생 시 두 기술의 협업 프로세스
- 0초 (레디스 Master 다운): Sentinel이 Failover를 시작하며 응답 지연 발생
- 1~2초 (Circuit Breaker 작동): Spring App의 Resilience4j가 에러/지연을 감지하고 차단기를 🔴 OPEN으로 전환 ➔ 레디스 대신 RDB를 조회하거나 기본값 반환(Fallback)
- 2초 (Front-end Timeout 작동): 프론트엔드가 요청을 안전하게 끊어내어 무한 로딩 및 사용자 연타로 인한 WAS 폭주 방지
- 4~5초 (Failover 완료 & 자동 복구): Sentinel이 신규 Master 승격을 완료하면, Resilience4j가 이를 감지하여 🟢 CLOSED로 돌려놓고 캐시 서비스 자동 정상화
| << K8s Redis-Cluster App ① | K8s Redis-Sentinel 준비 ① >> |
|---|
