🚝열차 좌석 예매에서의 동시성 제어 : Redis 기반 분산락 활용
2025. 8. 8. 00:16

1. 동시성 제어가 필요한 이유

  • 문제 상황
    • 열차 예매 서비스에서는 여러 사용자가 동시에 같은 좌석을 예약하려는 상황이 발생할 수 있음
    • 예: 명절에 KTX 1호차 3A에 좌석에 동시에 3명이 예약을 요청한 경우
  • 이 때 동시성이 제어되지 않으면
    • 중복 예약되어 한 좌석에 여러 명이 예약됨
    • 데이터의 정합성이 깨짐
  • 해결 방법
    • 먼저 예약한 사람만 성공하고, 나머지는 실패하도록 락(Lock)을 걸어야 함
    • 락을 구현하는 방식은 DB 수준의 네임드 락(Named Lock), Zookeeper와 Redis 기반의 분산락이 있음

2. Lock을 구현하기 위해 Redis를 선택한 이유

  • In-memory 기반의 빠른 처리 속도
    • 동시성 이슈를 해결하기 위한 방법으로는 MySQL의 네임드 락, Zookeeper도 존재하지만,
      이들은 디스크 기반 DB이기 때문에 응답 속도 면에서 한계가 있음
    • Redis는 메모리 기반 데이터 저장소로, 디스크 기반의 DB보다 데이터 접근 속도 및 응답 시간이 빠름
    • 예매 시에는 락을 획득하고, 중복인지 검사하고, 예약을 저장하는 흐름이 수 ms내에 빠르게 처리되어야 하므로, 이러한 실시간성 요구에 Redis가 가장 적합한 성능을 제공한다고 판단함
  • 안정적인 동시성 제어를 위한 중항 집중형 락 관리
    • Redis는 싱글 스레드 기반으로 명령을 처리하는 구조로, 동시에 여러 요청이 들어와도 내부적으로 명령어가 순차 처리됨.
    • 동시에 접근하더라도 단 하나의 요청만 락을 획득하고, 나머지는 대기하거나 실패 처리하는 방식.
    • 또한 Redis는 네트워크로 접근 가능한 중앙 저장소 역할을 하므로, 여러 서버 인스턴스가 스레드에 동시에 접근하더라도, 공유 자원(좌석, 대기열)에 대한 락을 중앙에서 안전하게 제어할 수 있음. 해당 프로젝트는 단일 서버 환경에서 동작하지만, 멀티 스레드 환경에서의 안정적인 락 제어를 위하여 도입하였음
  • TTL 설정을 통한 자동 만료 및 이벤트 처리 지원
    • 사용자가 예약 도중 결제를 진행하지 않거나 응답이 없는 경우, 해당 예약 정보를 일정 시간이 지나면 자동으로 삭제해야 함
    • 이 때, Redis는 TTL(Time To Live)를 설정할 수 있어서, 예약의 유효 시간을 직접 제어할 수 있음
    • TTL 만료 시에는 Redis의 Keyspace Event 기능을 활용하여 대기열 자동 제거  및 순번 갱신 로직과 연결할 수 있음
  • Spring Boot 환경에서의 호환성
    • Lettuce, Redisson 과 같은 클라이언트를 지원하기 때문에 분산락 구현이 간편함

3. Redis 분산락 구현 방식 : Redisson 선택

프로젝트에서는 Redis 기반 분산락을 구현하기 위한 클라이언트로 Lettuce와 Redisson 두 가지 방법을 비교하였음.

  • Lettuce
    • Spring Data Redis의 기본 클라이언트
    • 저수준의 명령어(SETNX)를 사용하여 락을 수동 구현
    • 장점 : 의존성 추가 없이 간단하게 락 구현
    • 단점
      • 락 획득, 해제, 재시도 등의 로직을 직접 구현해야 함
      • 예외나 장애 발생시 데드락 가능성
      • 락 소유자 확인할 수 없어서, 락 해제 누락시 위험
      • Spin Lock 방식이기 때문에, 다수의 스레드가 락을 요청할 경우, Redis에 부하 발생
  • Redisson
    • Redis를 기반으로 고수준의 분산 객체와 락 기능을 제공하는 외부 라이브러리
    • 단점 : 외부 라이브러리 의존 (redisson dependency)
    • 장점
      • tryLock(waitTime, leaseTime)과 같은 API로 락 획득과 자동 해제를 직관적으로 처리
      • isHeldByCurrentThread() 메서드를 통해 락 소유자 확인 가능
      • TTL락 자동 해제 기능 있어서 데드락 방지
      • 내부적으로 pub-sub 구조를 사용하여 락 해제를 기다리는 스레드에게 알리므로, 불필요한 polling 부하가 없음
      • Spring Boot와 통합이 쉬워서 유지보수 쉬움 
  • Redisson을 선택한 이유
    : 이 프로젝트는 좌석 예약, 대기열 진입, TTL 기반 예약 만료 처리 등 여러 사용자가 동시에 하나의 자원에 접근하는 상황이 발생할 수 있는 구조를 가지고 있음. 이러한 환경에서는 락 획득 실패시 자동 재시도, 일정 시간 이후에 자동 해제, 락 소유자 확인 등 락을 제어하는 기능이 필수적이며, 이를 위해서 Lettuce보다 Redisson이 더욱 적합하다고 판단하였음.
    Redisson은 락 처리 로직이 직관적이며 안정성이 높고, 개발자가 직접 예외 처리나 재시도 로직을 구현하지 않아도 되는 구조를 제공하며, 복잡한 동시성 제어 로직도 단순하고 안전하게 구현할 수 있다는 점에서 선택하였음

4. Redisson 분산락 적용 사례

  • 좌석 중복 예약 방지
  • 대기열 진입 충돌 방지
  • 예약 TTL 기반 자동 만료 처리

 

 

LIST