캐시 장애에도 멈추지 않는 아키텍처 설계

by DD
2시간 전
조회수 0

트래픽 급증 시 Redis 캐시 병목으로 인한 서비스 장애 발생 및 확장성 한계 직면

캐시 계층의 단일 장애점(Single Point of Failure) 문제 해결을 위해 아키텍처 재설계 결정

MongoDB CPU 부하 20~30% → 10% 이하 감소 및 처리량 3배 향상 달성

캐시 무력화 시에도 서비스 중단 없는 내고장성(Fault Tolerance) 확보에 집중

Redis 캐시 병목의 근본 원인 분석

기존 아키텍처는 제휴점 × 날짜 조합별 대용량 응답 전체를 Redis에 캐싱하여 트래픽 증가 시 네트워크 대역폭 한계에 직면했습니다.

문제점: Redis 노드의 아웃바운드 네트워크 대역폭 초과로 인한 패킷 드랍 및 응답 지연 누적, 결국 클라이언트 타임아웃 발생

인프라 한계: 애플리케이션 로직이 아닌 인프라 대역폭 제한이 병목이었으며, 스케일아웃 시에도 동일 Redis를 바라보므로 효과가 없었습니다.

구조적 한계: 캐시가 단일 장애점(Single Point of Failure)으로 작용하며, Redis 부하 증가는 애플리케이션 스케일아웃을 방해하는 요인이었습니다.

이러한 문제 해결을 위해 데이터 격리 아키텍처(Data Isolation Architecture) 관점에서 캐시 구조 재설계가 필수적이었습니다.

MongoDB 조회 비용 절감을 위한 Document 통합

기존 RDB 테이블 구조를 그대로 MongoDB에 옮겨, PLP 조회 시 15개 이상 컬렉션 조인으로 인한 비효율이 발생했습니다.

문제점: 파편화된 Document 구조는 다중 조인 쿼리를 유발하여 네트워크 대역폭 낭비 및 느린 응답 시간의 원인이었습니다.

해결 방안: 정규화(Denormalization) 우선 설계로 관련 데이터를 단일 Document로 통합하여, 10여 개 조인 쿼리를 3~4개 단순 쿼리로 감소시켰습니다.

결과: MongoDB 직접 조회 비용 자체를 크게 절감하여, 향후 캐시 없이도 서비스가 버틸 수 있는 기반을 마련했습니다.

이는 2편에서 상세히 다룰 숙소 메타 + 가격 계산 모듈 분리와 함께, 조회 성능 개선의 핵심 토대가 되었습니다.

Virtual Thread를 활용한 제휴점별 병렬 처리

기존 제휴점별 순차 처리 방식은 제휴점 수 증가에 따라 응답 시간이 선형적으로 증가하는 문제를 야기했습니다.

해결책: Java Virtual Thread 도입으로 수십 건의 IO를 경량 스레드로 병렬 처리하여 응답 시간을 단축했습니다.

이점: 플랫폼 스레드 풀의 스레드 고갈 및 컨텍스트 스위칭 오버헤드 없이 대규모 병렬 처리가 가능해졌습니다.

안정성 강화: 개별 제휴점 실패가 전체 응답에 영향을 미치지 않도록 에러 격리(Error Isolation)를 적용했습니다.

이를 통해 응답 시간 개선뿐만 아니라, 개별 실패에 대한 서비스 복원력(Resilience)을 높였습니다.

CDC 기반 실시간 메타 캐시 아키텍처

TTL 기반 캐시 대신, MongoDB 변경 감지(CDC)를 활용하여 이벤트 기반으로 캐시를 실시간 갱신하는 구조를 채택했습니다.

아키텍처: 숙소 정보 변경 시 Kafka CDC 메시지를 발행하고, 전시 가격 애플리케이션 Consumer가 이를 받아 Redis에 반영합니다.

이점:

항상 최신 상태의 캐시 유지로 데이터 불일치 문제 원천 차단

메타 정보만 캐싱하여 Redis egress 부담 경감 및 중복 저장 문제 해결

가격 경로는 MongoDB 직접 조회로 분리하여 Redis 장애 시에도 서비스 유지

결과: Redis 부하를 10% 이하로 유지하고, 애플리케이션 pod 증가 시 처리량 비례 스케일아웃이 가능해졌습니다.

3단계 조회 경로를 통한 내고장성 설계

캐시 계층의 잠재적 장애에 대비하여, 3단계 조회 경로(3-Tiered Lookup Path)를 설계하여 서비스 중단을 방지했습니다.

방어선:

1. Redis 캐시 (Resilience4j Circuit Breaker 적용)

2. 로컬 캐시 (Local Cache)

3. MongoDB 직접 조회

Circuit Breaker 설정: Redis는 실패율 임계값 50%, 대기 시간 10분으로 민감하게 차단하고, Local 캐시는 메모리 고갈 등 다른 장애 유형에 대해 더 긴 대기 시간을 설정했습니다.

검증: 실제 장애 상황에서 트래픽이 로컬 캐시로 원활하게 전환되었으며, 사용자는 장애를 인지하지 못했습니다.

이 구조는 캐시 계층의 장애가 서비스 전체에 미치는 영향을 최소화하여, 안정적인 서비스 운영을 가능하게 합니다.

더 빠르게, 그리고 무너지지 않게 — 전시 아키텍처 개선기 (1/3)