[설계] 예매 시스템에 왜 Redis가 필요할까?

y001·2025년 4월 13일
0
post-thumbnail

도입: 단순 캐시를 넘어서 Redis를 이해하고 싶었다

이번 프로젝트는 Redis를 단순한 캐시 도구로 넘어서, 분산 환경에서의 시스템 안정성과 성능을 지탱하는 핵심 인프라로서 어떻게 활용할 수 있는지 체득하기 위한 실험이었다.

나는 Redis 중심의 단기 실습 프로그램에 참여했다. 이 프로그램에서는 Redis를 단순히 GET, SET을 통한 캐시로 사용하는 것에서 벗어나, 분산 락, 요청 제한, 캐시 계층화 등 다양한 실제 운영 시나리오를 직접 구현하고 실습해보는 과정을 포함하고 있었다. 단순한 기술 데모가 아닌, 실서비스 수준의 문제를 정의하고 이를 Redis로 해결해보는 과정이 핵심이었다.

이 프로젝트에서 구현한 영화 예매 시스템은, 그렇게 내가 직접 고민하고 구현한 결과물이다. 단순 CRUD나 게시판 같은 시스템이 아니라, 강한 동시성과 정합성 요구, 높은 조회 트래픽, 멀티 인스턴스 환경에서의 일관성 문제 등 실제 운영 서비스에서 마주할 법한 복잡한 요구 사항을 품고 있다. 그런 만큼 Redis의 다양한 기능을 실전 환경에 가까운 구조로 설계하고, 직접 성능을 측정하며 효과를 검증할 수 있었다.

GitHub에 전체 코드와 실습 자료를 올려두었고, 앞으로 이 블로그 시리즈를 통해 어떤 문제를 어떻게 Redis로 풀었는지 상세히 기록할 예정이다.
👉 https://github.com/youngyin/redis_2nd


1. 예매 시스템에서 마주친 문제들

이 프로젝트는 영화 예매 도메인을 중심으로 설계되었으며, 이 도메인을 선택한 이유는 명확하다. 동일 좌석에 대한 다중 요청이 빈번하게 발생할 수 있는 구조이기 때문이다. 이는 단순한 게시판처럼 한 방향의 쓰기/읽기 작업이 아닌, 동일 자원에 대한 경쟁과 충돌을 내재한 구조이기 때문에 Redis의 필요성을 시험해보기 매우 적합했다.

시스템을 구현하면서 크게 아래와 같은 문제들을 경험하거나 예상할 수 있었다:

1) 동일 좌석 중복 예약

예를 들어, 특정 영화의 인기 좌석 A1에 대해 수십 명의 사용자가 동시에 예약을 시도한다고 하자. 이때 락 없이 처리할 경우, DB에 동일 좌석이 중복 저장되거나, 여러 사용자가 동일한 좌석을 예약하게 되는 상황이 발생한다. 이는 도메인 정합성을 완전히 무너뜨리는 치명적인 문제다.

2) 메인 조회 API의 트래픽 집중

상영 중인 영화 목록은 메인 페이지 진입 시 거의 모든 사용자가 요청하는 API이다. 이 API는 페이징 없이 전체 목록을 반환하도록 구성되었고, 여기에 이미지, 장르, 러닝타임, 상영 시간표 등 부가 정보까지 함께 제공해야 했다. 이런 요청을 매번 DB에서 처리하게 되면 트래픽이 몰릴 때마다 데이터베이스 부하가 급증하게 된다. 실제 K6 테스트를 통해도 이 API가 병목 지점이 될 수 있다는 사실을 확인했다.

3) 멀티 인스턴스 환경에서 락 무효화

서비스를 확장하다 보면 단일 서버가 아닌 여러 서버 인스턴스를 띄워야 한다. 이때 synchronized, ReentrantLock과 같은 JVM 내부 락은 같은 인스턴스 내에서는 유효하지만, 서버 간에는 락을 공유하지 않기 때문에 무력화된다. 결국 인스턴스마다 각자 좌석을 중복으로 처리할 수 있어, 분산 락이 반드시 필요했다.


2. 이 시스템에서 Redis로 실험해보고자 한 세 가지

이번 프로젝트에서 나는 Redis를 단순한 성능 개선 도구가 아니라, 시스템 안정성과 확장성을 확보하기 위한 구조적 도구로 접근하고자 했다. 그 과정에서 Redis로 직접 구현하고 검증하고자 했던 세 가지 핵심 포인트는 다음과 같다.

✅ 1) Redisson 기반 분산 락 적용 실험

좌석 충돌 문제를 해결하기 위해 Redisson 라이브러리를 활용해 Redis 기반의 분산 락을 적용했다. 락 키는 lock:seat:{seatId} 형식으로 구성했고, Redisson의 tryLock(timeout, leaseTime) 구조를 활용해 락 획득 여부를 제어했다. 테스트에서는 10개의 동시 요청을 보내 중복 예약을 시도했는데, 결과적으로 오직 1건만 성공하고 9건은 모두 예외 처리되었다. 분산 락이 실제로 유효하게 작동하는지 검증할 수 있었다.

✅ 2) 3단 캐시(Caffeine → Redis → DB) 구조 구현

조회 트래픽 병목을 해소하기 위해 캐시 구조를 계층화했다. Caffeine을 1차 로컬 캐시로 두고, Redis를 2차 글로벌 캐시로 구성했다. Caffeine은 빠르지만 서버별로 데이터가 나뉘고, Redis는 공유 자원이지만 느릴 수 있다. 이 구조를 통해 응답 속도와 일관성, 자원 효율성 사이의 균형을 잡았다. 실제 응답 시간은 214ms → 74ms로 약 65% 이상 단축되었고, Redis 메모리 사용량도 효과적으로 제어할 수 있었다.

✅ 3) Redis + Lua를 이용한 RateLimit 실험

Redis는 단순히 데이터를 저장하는 데 그치지 않고, Lua 스크립트를 이용해 원자적 제어 로직을 구현할 수 있다. 이 기능을 활용해 조회 API에는 IP 기반의 요청 제한을, 예약 API에는 사용자 기반의 5분 제한 정책을 적용했다. AOP를 이용해 기술 정책을 도메인 로직에서 분리했고, 실제 서비스 흐름과 맞물리게 유스케이스 내부에서 제한을 검사하는 구조를 만들었다.


3. Redis가 실제로 해결한 문제들

아래는 이 프로젝트에서 Redis를 통해 해결하거나 완화한 주요 사례다.

문제 상황Redis 적용 방식결과
동일 좌석 중복 예약Redisson tryLock() 기반 분산 락중복 저장 없이 하나의 요청만 처리됨
메인 조회 API 트래픽 병목Caffeine + Redis 3단 캐시평균 응답 속도 65% 이상 개선
인기 데이터 판별 및 캐시 동기화 기준조회 횟수 5회 이상일 때만 Redis 저장Redis 메모리 사용량 최소화, 불필요한 키 생성 방지
반복 예약 요청Lua 스크립트 기반 유저별 TTL 제어동일 시간대 중복 예약 차단 성공

4. 기술 실험을 넘어서, 구조적 이해를 얻다

이번 프로젝트는 단순한 기능 구현을 넘어서, 왜 이런 구조가 필요하고, 어떻게 시스템을 보호할 수 있을지를 고민하며 설계한 실험이었다. Redis를 단지 빠르게 응답하기 위한 도구로 사용하는 것이 아니라, 시스템 전체의 신뢰성과 성능을 설계하는 전략적 자원으로 다룰 수 있다는 확신을 얻을 수 있었다.

특히 멀티 인스턴스 환경에서는 단순한 기술로는 제어할 수 없는 문제들이 많다는 것을 체감했고, 이런 환경에서 Redis 기반의 분산 락이나 중앙 캐시, 요청 제한 정책이 얼마나 중요한지 몸소 확인할 수 있었다.


다음 편 예고

다음 글들에서는 이러한 Redis 기반 기능들이 잘 녹아들 수 있도록, 이 예매 시스템을 어떻게 멀티 모듈 + 헥사고날 아키텍처 구조로 구성했는지를 소개한다. 단순한 기능 분할이 아닌, 유지보수성과 테스트 가능성까지 고려한 구조 설계 과정을 함께 정리할 예정이다.


📎 참고 자료

0개의 댓글