🎫 티켓팅 플랫폼 MSA 프로젝트
대규모 사용자가 몰리는 이벤트 티켓팅 과정에서
빠르고 안정적으로 예약 처리를 보장하는 시스템을 구축하는 것을 목표로 하고 있습니다.
각 기능을 마이크로서비스로 분리하여 독립적으로 운영하며,
분산된 서비스들 간의 데이터 일관성을 보장하여
티켓 예약 처리 과정에서 높은 정확성과 신뢰성을 유지합니다.
이러한 구조를 통해 확장성
과 데이터 일관성
을 모두 확보한
안정적인 시스템을 제공합니다.
이 프로젝트의 목적은 다수의 동시 접속자가 발생하는 상황에서 안정적이고 확장 가능한 티켓팅 시스템을 구축하는 것입니다.
주요 목표는 실시간 좌석 선택, 분산 잠금 처리, 대기열 관리 및 트래픽 분산을 통해 사용자 경험을 최적화하는 것입니다. 이를 위해 Redis, Kafka와 같은 고성능 분산 시스템을 활용하여 트랜잭션 처리 속도를 높이고, 대규모 트래픽에도 빠른 응답성을 제공하는 아키텍처를 설계하는 것이 목표입니다.
인증
- 시큐리티 관리를 위한 유저에서 분리된 별도의 인증서버
이벤트 / 좌석
- 이벤트 및 좌석 정보 생성
- 이벤트 및 좌석 정보 조회 (좌석 정보 캐싱)
티켓
- 좌석 상태에 따른 티켓 예약 (분산락)
- 티켓 생성과 함께 좌석 상태 변경 (비동기 Kafka)
- 성능 향상을 위한 이벤트 별 다중 토픽 처리
결제 기능
- 카카오 데모 결제를 이용한 결제 시스템 실 체험 가능
💻 Application Development and Frameworks
- Java
- Springboot
🚀 성능 최적화 및 캐싱
- Caffeine
- Redis
📩 메시징 및 데이터 처리
- Kafka
☁️ 인프라 및 배포
- EC2
- Docker
- Docker Compose
- RDS (PostgreSQL)
- ElastiCache(Redis)
- S3 - (Redis의 정합성을 위한 백업 장치)
- MSK - AWS에서 제공하는 완전 관리형 Apache Kafka 서비스
🧪 성능 테스트 및 품질 관리
- Apache JMeter
🤝 협업 및 소스 관리
- GitHub
- Notion
📈 로그 관리 및 모니터링
- ELK Stack (Elasticsearch, Logstash, Kibana)
- Prometheus
- Grafana
결제 서비스 분리
-
도입 이유
결제 실패 대응: 결제 과정에서 네트워크 문제나 외부 결제 시스템의 오류가 발생할 가능성이 있기 때문에, 좌석 예약과 결제를 분리함으로써 결제가 실패하더라도 좌석이 불필요하게 취소되지 않고 재시도 기회를 제공하고자 합니다. -
문제 상황
결제 과정의 복잡성: 결제가 실패하거나 결제 지연이 발생할 때 좌석을 어떻게 다시 다른 사용자에게 제공할지, 혹은 결제 상태와 좌석 예약 상태 간의 일관성을 유지하는 방법에 대한 문제가 있었습니다. -
해결 방안
결제 시스템 분리: 좌석 예약과 결제 시스템을 각각 분리하여 결제 지연이 발생하더라도 좌석 예약 과정에 영향을 미치지 않도록 설계했습니다. 결제가 완료되면 좌석을 확정하고, 결제 실패 시 좌석을 다시 다른 사용자가 선택할 수 있게 했습니다. -
의견 조율
성능 vs. 정합성: 팀 내에서 성능 최적화와 데이터 정합성 간의 균형을 맞추는 부분에서 논의가 있었습니다. 좌석 예약과 결제를 완전히 분리하면 성능은 높아지지만, 결제 실패 시 좌석이 어떻게 처리될지에 대한 정합성 문제가 중요하게 다뤄졌습니다. -
최종 의사 결정
좌석 예약과 결제의 완전한 분리: 좌석 예약과 결제 과정을 완전히 분리하고, 임시 좌석 예약은 Redis에 저장하며, 결제가 완료되면 좌석을 DB에 최종적으로 저장하는 방식으로 결정했습니다. 결제 실패 시 좌석을 자동으로 해제하도록 TTL을 설정하여, 좌석이 불필요하게 점유되지 않도록 했습니다.
캐시 선정 (Redis vs Caffeine)
-
도입 이유
본 티케팅 서비스에서는 실시간 처리가 중요한 좌석 서비스의 성능을 극대화하기 위해 Redis 캐시를 도입했습니다. 대규모 트래픽을 처리해야 하는 좌석 서비스에 Redis의 분산 캐시 기능을 활용해 안정적이고 빠른 캐싱을 구현하였고, 트래픽이 집중되는 핵심 서비스에 적합하다고 판단했습니다. 반면, 로그인 처리와 같은 상대적으로 우선도가 낮은 기능에는 로컬 캐시(Caffeine Cache) 를 선택했습니다. 로그인은 빠른 응답이 필요하지만 트래픽 부하가 적기 때문에 Caffeine이 적합하다고 보았습니다. -
문제 상황
Redis는 좌석 서비스와 같은 핵심 기능에서 매우 유용하지만, 모든 기능에 Redis를 사용하는 것은 자원 낭비가 될 수 있다는 문제가 있었습니다. 특히, 로그인과 같은 비교적 적은 트래픽이 발생하는 서비스에서 Redis를 사용할 필요성이 떨어졌습니다. 따라서, 각 기능에 맞는 효율적인 캐시 전략을 수립하는 것이 필요했습니다. -
해결 방안
좌석 서비스와 같은 트래픽이 집중되는 기능에는 Redis 캐시를, 상대적으로 부하가 적은 로그인 처리와 같은 기능에는 로컬 캐시인 Caffeine Cache를 사용하기로 했습니다. Caffeine은 최신 알고리즘을 사용해 빠른 응답 속도를 보장하면서도, 성능과 효율성 면에서 더 나은 결과를 제공하는 점에서 적합했습니다. -
의견 조율
팀 내에서 성능 최적화와 자원 효율성을 고려한 논의가 있었습니다. Redis는 성능 보장 측면에서 우수하지만, 모든 기능에 적용할 경우 비용과 자원 소모가 크다는 의견이 있었습니다. 이에 따라, 핵심 기능에는 Redis를 사용하고, 상대적으로 트래픽이 적은 부분에는 Caffeine을 적용해 균형 있는 캐시 전략을 도입하기로 했습니다. 또한Caffeine의 장점 중 하나는 로컬 메모리에서 동작하기 때문에, Redis처럼 네트워크를 통해 접근하는 지연 시간이 발생하지 않으며, 더 빠르게 캐시를 조회하고 사용할 수 있습니다. 반면, Redis의 장점 중 하나는 캐싱 내역을 확인할 수 있다는 점이지만, 로그인 캐시의 경우 캐시 내역을 확인의 중요도가 다소 떨어졌기 때문에 Redis의 이점은 부각되지 않았습니다. 따라서, 속도가 더 중요한 현재 프로젝트의 상황에서는 Caffeine을 선택하는것이 좀 더 매력적이었습니다.
-
최종 의사 결정
결론적으로, 좌석 서비스와 같은 핵심 기능에는 Redis를, 로그인 처리와 같은 부하가 적은 기능에는 Caffeine Cache를 적용하는 것으로 최종 결정했습니다. 이를 통해, 핵심 기능에서는 성능을 극대화하고, 비핵심 기능에서는 자원을 효율적으로 분배하여 비용을 절감하면서도 성능을 유지하는 구조를 구축할 수 있었습니다.
분산락 적용 (Redis 분산락)
-
도입 이유
좌석 상태 변경 시 동시성 이슈를 해결하기 위함. -
문제 상황
- 좌석 예약/결제 완료/결제 취소에 따라 좌석 상태를 변경하는 과정에서 좌석 상태 불일치 문제 발생
- 여러 사용자가 동시에 동일한 좌석을 선택하는 경우 중복예약의 문제 발생
-
해결 방안
-
라이브러리 선택: Lettuce vs. Redisson
Lettuce 단점:
- 스핀락 형태로 Redis에 많은 부하를 가할 수 있음.
- 타임아웃 처리가 없으므로 무한 대기 상태로 인한 시스템 장애 가능성 존재.
Redisson 장점:
- Pub/Sub 방식으로 Redis 부하를 줄임.
- 타임아웃 설정을 통해 무한 대기를 방지할 수 있음.
-
예상되는 문제 해결 방안
메서드 내에서 락 해제와 트랜잭션 커밋 사이에 다른 쓰레드가 락을 획득하여 아직 커밋되지 않은 데이터를 읽는 문제가 발생할 수 있음.
-
트랜잭션 전파 속성 활용:
REQUIRES_NEW
전파 속성을 사용하여 별도의 트랜잭션에서 락을 처리.@Transactional(propagation = Propagation.REQUIRES_NEW)
-
-
의견 조율
Redisson 사용과 트랜잭션 전파 속성을 통해 데이터 정합성과 성능을 동시에 유지하는 방법 논의. -
최종 의사 결정
Redisson 라이브러리를 사용하여 Redis 기반의 분산락을 구현하고, 트랜잭션 전파 속성을 적절히 활용하여 최적의 성능과 정합성을 유지하기로 결정했습니다.
Redis를 이용한 조회 캐싱 처리 (처리량 28.8/sec → 44.6/sec)
Before
After
개선 방법: 조회가 가장 많을 것으로 생각되는 좌석 조회에 Redis를 통한 캐싱으로 성능 향상
Gateway에서 유저 인증 정보를 검증을 위한 caffeine cache 적용 (처리량 323.9/sec → 398.1/sec)
Before
After
개선 방법: Gateway 내부에서 Caffeine 캐시를 통한 유저 검증 정보 캐싱
의사결정 과정: redis를 이용한 캐싱처리를 할까도 고민했지만 메인 비즈니스 로직이 아니고 부하가 엄청 클 것으로 보이지 않아 Caffeine 캐시로 해결
티켓 예매 서비스의 성능 향상을 위한 Kafka 도입 (처리량 1000건 141.4/sec → 30000건 492.7/sec)
Before
After
개선 방법: 티켓 예약 과정에서 기존에는 동기처리 방식을 사용하여 진행하였지만 대용량 트래픽을 견딜 수 없어 메시징 시스템인 Kafka를 사용
의사결정 방법: RabbitMQ와 Kafka 중에서 고민하였지만, 예약 과정에서 하나의 큐를 사용하는 것보다 이벤트마다 별도의 토픽을 이용한 큐를 사용하는 것이 좋다고 생각하였으며, Kafka는 이러한 요구에 더 적합한 선택이었습니다. Kafka는 토픽 기반 구조를 통해 각 이벤트를 독립적으로 처리할 수 있도록 지원하며, 이를 통해 병렬 처리와 확장성이 용이해집니다. 특히, 대규모 트래픽이나 실시간 데이터 스트리밍이 필요한 환경에서는 Kafka의 분산 처리 능력이 뛰어나 RabbitMQ보다 적합한 선택이었습니다.
버그바운티 - 결제
문제 정의 : 버그바운티를 진행하던 도중 중복 결제 발생에 관한 부분 발견
증상 : 하나의 티켓에 대해 2개 이상의 결제가 생성되며 결제가 이루어짐
원인 분석 :
카카오페이 결제를 위해서는 결제 요청과 승인의 2단계로 진행
결제 요청 시 실제 결제가 이루어지는 redirect_url을 제공
하지만 해당 url을 저장하지 않고 반환만 하기 때문에 사용자가 해당 url을 분실 시 결제를 진행 할 수 없는 문제가 발생 → 따라서 새로운 결제를 생성할 수 있도록 구현
다만 사용자가 결제 url을 저장해두고 새로 발급 받은 url 총 2가지 결제를 진행 시 중복 결제 발생
해결 방법 : 새로운 결제 url을 발급 시 기존 결제에서 저장된 tid(결제 id)를 덮어씌우기를 하여 기존의 결제 url을 무력화
결과 및 확인 : 해당 방법을 적용 후 결제가 요청 수 만큼 생기지 않고 url을 새로 발급 받아도 중복 결제를 방지
팀원명 | 포지션 | 담당(개인별 기여점) | 깃허브 링크 |
---|---|---|---|
박태언 | 사용자 인증/인가 | ▶ 인증 및 인가 서비스 - JWT 기반 인증 및 인가 서비스 설계 및 구현 - 인증 토큰 검증 및 처리 로직 개발 - 인증/인가 관련 서비스 간 통신 및 유틸리티 기능 추가 ▶ 게이트웨이 서비스 - MSA 환경을 위한 게이트웨이 서비스 구축 - Caffeine 캐시를 활용한 캐싱 로직 설계 및 구현 ▶ 유저 서비스 - 유저 관리 기능 설계 및 구현 - 내부 통신 및 기본 유틸리티 기능 추가 |
GitHub tellang |
전석배 | 결제 서비스 개발 | ▶ 결제 서비스 개발 - 실제 예약된 티켓에 대한 결제 서비스 제공 - 카카오페이 결제를 연동하여 실 결제 체험 ▶ AWS 인프라 구성 및 아키텍쳐 설계 - RDS, ElastiCache, S3, EC2 구성 ▶ 로그 관리 및 모니터링 구축 - ELK를 통한 MSA 환경 통합 로그 수집 - Grafana & Prometheus 시스템 성능 모니터링 |
GitHub SerenityZenDev |
박주원 | 공연/좌석 서비스 개발 | ▶ 공연/좌석 서비스 개발 - 다수의 공연 정보와 연관된 좌석들로 티켓팅에 필요한 정보들을 관리 - 티켓 서비스와 Kafka 비동기 통신으로, 티켓이 생성/취소 시의 좌석 변경 플로우 개발 - Redis를 통한 실시간 좌석 관리 기능 개발 |
GitHub juoonge |
최현 | 티켓 서비스 개발 | ▶ 티켓 서비스 개발 - 공연/좌석에 대한 티켓을 생성 및 관리 - 공연/좌석 서비스와 Kafka 비동기 통신으로, 티켓의 상태에 따른 공연/좌석 변경 플로우 개발 ▶ ECS, MSK, Elastic Cache, RDS를 활용하여 호스팅 환경 구축 |
GitHub CZ-punk |