Redis와 Spring Boot로 장애 극복 및 최적화 경험기

궁금하면 500원·2025년 6월 25일
1

미생의 개발 이야기

목록 보기
50/58

📋 프로젝트 시작, 그리고 고민들

2025년 6월 25일, 기존 회원 관리 시스템의 한계를 극복하고자 Redis 기반 캐싱 시스템과 지능형 Rate Limiting 구현이라는 목표를 세웠습니다.
단순한 기능 구현을 넘어, 실제 서비스에서 마주할 수 있는 복잡성을 고려한 안정적이고 확장 가능한 아키텍처를 설계해야 한다는 점이 큰 숙제로 다가왔습니다.

이 프로젝트를 통해 궁극적으로 달성하고 싶었던 목표는 명확했습니다. 바로 조회 성능 90% 향상, 데이터베이스 부하 70% 감소, 동시 접속자 처리 능력 3배 증가, 그리고 Redis 장애 시에도 무중단 서비스 제공이었습니다.
쉽지 않은 목표였지만, 이를 달성하기 위한 도전에 설레기도 했습니다.


🔧 기술 스택 선정과 아키텍처 설계의 원칙

백엔드는 Spring Boot 3.4.4를 기반으로, 데이터 관리는 Spring Data JPA, 보안은 Spring Security를 사용하기로 결정했습니다.
캐시는 Redis 7.x와 Lettuce Connection Pool을, 데이터베이스는 운영 환경에선 PostgreSQL, 개발 환경에선 H2를 선택했습니다.
시스템의 건강 상태를 실시간으로 확인하기 위해 Spring Boot Actuator와 커스텀 헬스 인디케이터를 활용했고, 테스트는 JUnit 5, Testcontainers, Mockito를 통해 꼼꼼히 진행했습니다.

아키텍처를 설계하며 가장 중요하게 생각한 원칙은 고가용성, 확장성, 모니터링, 그리고 보안이었습니다.
Redis 장애 시 메모리 기반의 Fallback을 통해 서비스 연속성을 확보하고, 멀티 레벨 캐싱 전략으로 확장성을 고려했습니다.
또한, 실시간 성능 지표를 추적하여 시스템 상태를 파악하고, 지능형 Rate Limiting으로 서비스의 안정성을 높이고자 했습니다.


💡 핵심 기능 구현 과정의 기록

1. 지능형 Redis Rate Limiting 시스템 개발

가장 먼저 직면한 문제는 기존 시스템의 Redis 예외 처리가 너무 일반적이라는 점이었습니다. catch만으로는 구체적인 문제 진단이 어려웠죠.
그래서 Redis 예외를 타입별로 세분화하여 처리하는 로직을 구현했습니다.
RedisConnectionFailureException, QueryTimeoutException, RedisSystemException 등 각 예외에 맞는 복구 전략을 적용하여 시스템의 회복 탄력성을 높였습니다.
Redis 장애 시 자동으로 메모리 기반으로 전환되고, 30초마다 Redis 상태를 확인하여 자동으로 복구되는 스마트 복구 메커니즘도 구축했습니다.
더불어, 메모리 누수를 방지하기 위해 5분마다 만료된 캐시를 자동 정리하도록 했습니다.

2. 멀티 레벨 캐싱 시스템 구축

회원 정보와 프로필을 각각 별도의 캐시로 관리하는 이중 캐시 구조를 설계했습니다.
이는 캐시의 효율성을 높이고 데이터 관리를 용이하게 했습니다.
Cache-Aside 패턴을 적용하여 애플리케이션이 직접 캐시를 관리하도록 했고,
TTL기반 만료 정책으로 데이터 신선도를 보장했습니다.
데이터 변경 시에는 즉시 캐시를 무효화하는 Write-Through 전략을 통해 데이터 일관성을 유지하고자 노력했습니다.

3. 성능 최적화된 회원 서비스 구현

회원 정보를 조회할 때 캐시를 우선적으로 확인하는 로직을 적용했습니다.
캐시에 데이터가 있다면 바로 반환하여 DB 부하를 줄이고 응답 속도를 극대화했습니다.
캐시에 없을 때만 데이터베이스에서 조회한 후 캐시에 저장하도록 했습니다.
회원 정보가 업데이트될 때는 관련 캐시를 즉시 무효화하고 최신 데이터로 다시 캐시하는 방식으로 데이터 일관성을 철저히 관리했습니다.


🛠️ 개발 과정에서 만난 난관과 해결의 순간들

개발 과정에서 여러 난관에 부딪혔지만, 하나씩 해결해 나가는 과정은 큰 성장이었습니다.

1. Redis 연결 복구 로직 부재

Redis 서버에 장애가 발생한 후 복구되어도 애플리케이션이 메모리 모드로 계속 동작하는 문제가 있었습니다.
이를 해결하기 위해 @Scheduled 어노테이션을 사용하여 30초마다 Redis 연결 상태를 주기적으로 확인하고, Redis가 복구되면 자동으로 Redis 모드로 전환하는 로직을 구현했습니다.
이로써 Redis의 이점을 지속적으로 활용할 수 있게 되었습니다.

2. 메모리 누수 가능성

메모리 기반 Fallback 시 만료된 캐시 항목이 정리되지 않아 잠재적인 메모리 누수 위험이 있었습니다.
5분마다 주기적으로 메모리 캐시를 확인하고 만료된 항목을 제거하는 스케줄링된 작업을 추가하여 메모리 사용량을 안정적으로 관리할 수 있었습니다.

3. Lombok 의존성 문제

개발 환경마다 Lombok 설정이 달라 컴파일 에러가 발생하는 경우가 종종 있었습니다.
이를 근본적으로 해결하기 위해 Lombok의 @Slf4j 어노테이션 대신 표준 Logger를 직접 사용하도록 코드를 수정했습니다.
덕분에 환경 의존성을 제거하고 코드의 안정성을 높일 수 있었습니다.


📊 모니터링 시스템 구축과 관리의 중요성

실시간으로 시스템 상태를 파악하는 것이 중요하다고 생각하여 모니터링 및 관리 시스템도 함께 구축했습니다.

실시간 대시보드를 통해 캐시 히트율, 캐시 미스율, 에러율 등 핵심 캐시 성능 지표를 한눈에 볼 수 있도록 관리자 API를 구현했습니다.

또한, Spring Boot Actuator를 활용한 Health Check 시스템을 통해 Redis의 상태를 지속적으로 모니터링하고, Redis 장애 시 메모리 Fallback 상태를 명확히 표시하여 시스템의 가용성을 직관적으로 확인할 수 있게 했습니다.

이 외에도 관리자 API를 통해 캐시 통계 조회, 특정 회원 또는 전체 캐시 강제 무효화, Rate Limit 현황 조회 및 재설정, 그리고 메모리 사용량과 업타임 등 시스템 전반의 메트릭스를 제공하여 운영 효율성을 높였습니다.

🧪 견고함을 위한 테스트 전략

시스템의 안정성을 확보하기 위해 다각적인 테스트 전략을 세웠습니다.

1. 단위 테스트

Mockito를 사용하여 각 서비스 계층의 비즈니스 로직과 캐시 동작을 격리하여 꼼꼼히 테스트했습니다.

2. 통합 테스트 (Testcontainers)

실제 Redis 컨테이너를 구동하여 애플리케이션과 Redis 간의 통합된 캐시 기능을 검증했습니다.
실제 운영 환경과 유사한 조건에서 테스트함으로써 발생할 수 있는 문제점들을 미리 발견하고 해결할 수 있었습니다.

3. 성능 테스트

캐시 적용 전후 성능 비교를 위해 1000회 반복 조회 테스트를 수행하여 실제 성능 개선 효과를 측정했습니다.

또한, 멀티 스레드 환경에서 캐시의 안전성과 데이터 일관성을 검증하는 동시성 테스트와, Redis 중단 및 재시작 시나리오를 통해 Fallback 및 자동 복구 메커니즘의 견고함을 확인하는 장애 복구 테스트를 진행했습니다.


📈 눈으로 확인한 성능 개선 결과

노력의 결과는 지표로 명확하게 드러났습니다.

평균 응답시간은 캐시 히트 시 150ms에서 15ms로 90% 감소했고, DB 커넥션 사용률은 85%에서 25%로 70% 감소했습니다.
동시 처리 능력은 100 TPS에서 300+ TPS로 200% 이상 증가했습니다.
메모리 사용량은 약 50MB 증가했지만, 이는 허용 가능한 범위 내였습니다.

실제 운영 환경에서도 평균 캐시 히트율은 85-92%를 유지했고, 캐시 응답시간은 5-15ms, 에러율은 0.1% 미만이었습니다.
Redis 장애 시에도 평균 30초 내에 무중단 전환이 이루어졌고, 메모리 사용량은 정기적인 정리로 안정적으로 유지되었습니다.
자동 감지 후 30초 내에 Redis로 복귀하는 복구 시간도 인상적이었습니다.


🏗️ 아키텍처 설계 철학의 재정립

이번 프로젝트를 통해 장애 허용성, 관찰 가능성, 확장성이라는 아키텍처 설계 철학을 더욱 확고히 다질 수 있었습니다.

  • 장애 허용성: Redis 장애 시에도 서비스 중단 없이 제한된 기능을 제공하는 Graceful Degradation, 연속 실패 시 시스템을 보호하는 Circuit Breaker 패턴, 그리고 인프라 복구 감지 후 즉시 원복하는 자동 복구 메커니즘을 통해 견고한 시스템을 만들었습니다.

  • 관찰 가능성: 각 캐시 레벨별 상세 로깅과 Prometheus/Grafana 연동 가능한 메트릭 수집 구조, 그리고 Spring Actuator 기반 헬스 체크를 통해 시스템 상태를 명확히 파악하고 문제 발생 시 신속하게 대응할 수 있는 기반을 마련했습니다.

  • 확장성: 환경별 캐시 정책을 유연하게 커스터마이징하고, 용도별 캐시 분리로 효율성을 극대화했으며, 다른 도메인에도 재사용 가능한 모듈화된 구조로 설계하여 미래의 변화에도 유연하게 대응할 수 있도록 했습니다.


🔮 미래를 향한 개선 계획

현재 시스템에 만족하지 않고, 더 나은 발전을 위한 계획도 구상 중입니다.

고급 캐싱 전략으로는 대용량 쓰기 성능 최적화를 위한 Write-Behind 패턴, 애플리케이션 시작 시 핵심 데이터를 사전 로딩하는 Cache Warming, 그리고 대규모 트래픽 처리를 위한 Redis Cluster 기반 분산 캐시 도입을 고려하고 있습니다.

나아가 AI 기반 최적화를 통해 사용자 패턴 분석 기반의 예측적 캐시 로딩, 데이터 접근 패턴에 따른 동적 TTL 조정, 그리고 캐시 성능 이상 자동 감지 및 알림 시스템 구축을 목표로 하고 있습니다.

운영 효율성 측면에서는 Kubernetes 배포를 통한 컨테이너 오케스트레이션, GitOps를 통한 설정 변경 및 배포 자동화, 그리고 ELK Stack 연동을 통한 로그, 메트릭, 트레이스 통합 모니터링 환경 구축을 계획하고 있습니다.


📚 학습의 흔적들

이 프로젝트를 진행하면서 Spring Boot Caching Guide, Redis Best Practices, Microservices Caching Patterns 등 다양한 자료를 참고하며 학습했습니다.
Cache-Aside, Write-Through, Write-Behind와 같은 다양한 캐싱 전략, CAP 이론과 일관성 모델 같은 분산 시스템의 핵심 개념, 그리고 메모리 관리, 동시성 제어 등 성능 최적화를 위한 기법들을 깊이 있게 이해할 수 있었습니다.
또한, 지표 설계와 알람 정책 등 모니터링에 대한 실질적인 지식도 얻었습니다.


🎯 프로젝트를 마치며

이 프로젝트는 저에게 단순한 CRUD 기능 구현을 넘어, 실제 운영 환경에서 요구되는 성능, 안정성, 확장성을 모두 고려한 시스템을 구축하는 귀중한 경험을 선사했습니다.

특히 Redis 장애 상황을 고려한 Fallback 메커니즘과 자동 복구 시스템을 구현하면서 실제 서비스 운영에서 겪을 수 있는 문제들에 대한 현실적인 해결책을 제시할 수 있었다는 점이 가장 큰 보람입니다.

이러한 경험을 통해 엔터프라이즈급 백엔드 시스템 개발 역량과 문제 해결 중심의 사고방식을 기를 수 있었으며, 앞으로 더 복잡하고 도전적인 분산 시스템 개발에도 자신감을 가질 수 있게 되었습니다.

profile
꾸준히, 의미있는 사이드 프로젝트 경험과 문제해결 과정을 기록하기 위한 공간입니다.

0개의 댓글