- “에버랜드 자유이용권 선착순 이벤트”와 같은 높은 트래픽의 특정 기능이 전체 서비스에 영향을 줄 수 있다면 어떻게 될까?” 라는 질문에서 시작했습니다.
- 이는 우아콘 2020 발표 ‘배달의민족 마이크로서비스 여행기’에 영감을 받은 고민이었습니다.
- 특정 프로모션을 통해 사용자에게 쿠폰을 발급하는 이벤트입니다.
- 아래 발급 조건을 충족해야 쿠폰을 받을 수 있습니다.
- 한 사람당 한 개의 쿠폰만 가질 수 있다.
- 당일 미션 하나를 인증 받아야 한다.
- 이벤트 기간 동안, 매일 특정 시간에 오픈하며 총 지급 수량을 한정한다.
- 쿠폰 지급 수량은 당일 정해진 양을 초과할 수 없다.
- 실제로 선착순 발급 API에 집중된 요청으로 인해, 날씨 조회나 채팅 등 다른 기능까지 느려지는 현상이 발생했고, 이는 모놀리식 구조의 높은 결합도에서 비롯된 문제였습니다.
모놀리식 아키텍처는 모든 기능이 단일 서비스 내에 통합되어 있어 아래와 같은 문제가 발생했습니다:
- 서버 마비: 트래픽이 급증하면 서버 자원이 고갈되어 기본 기능(예: 회원가입, 로그인)조차 정상적으로 작동하지 않음.
- TPS 한계: 평균 TPS 200에 도달 시 웹서버가 병목현상을 일으키며, 전체 서비스가 중단.
default.mp4
- Artillery를 통한 쿠폰 발급 API 부하테스트 스크립트를 실행시킨 후 다른 기능들까지 모두 마비가 되는 것을 확인했습니다.
모놀리식 구조의 한계를 극복하고, 높은 트래픽 상황에서도 안정적인 서비스를 제공하기 위해 MSA를 도입했습니다.
- 각 기능을 독립적인 마이크로서비스로 분리.
- 특정 서비스 장애가 전체 시스템에 영향을 미치지 않도록 설계 가능.
- 사용자(유저)의 정보들을 관리하는 서비스
- 회원가입 및 로그인을 관리하는 서비스
- 날씨에 따른 미션 정보를 관리하는 서비스
- 쿠폰에 정보를 관리하는 서비스
- 커뮤니티를 관리하는 서비스
- 사용자가 만든 미션들을 관리하는 서비스
- 초기에는 서비스 간 통신 방식으로 가장 익숙한 HTTP를 사용.
- 그러나 HTTP는 동기 요청 방식이기 때문에 모놀리식의 한계(서비스 간 결합도)와 장애 전파 문제를 해결하지 못함.
- 기존 HTTP 요청 방식의 결합도 높음과 장애 전파 위험을 해결하기 위해 Kafka를 도입.
- Kafka를 통해 비동기 메시지 기반 아키텍처로 전환.
- 비동기 처리
- 메시지를 큐에 저장하여 서비스 간 비동기적으로 데이터를 처리.
- 내결함성
- 메시지가 큐에 저장되므로 특정 서비스가 다운되더라도 메시지가 유지됨.
- 서비스 복구 후 메시지 재처리 가능.
- 높은 처리량
- Kafka는 높은 스루풋을 지원해 대량의 메시지를 효율적으로 처리.
- 확장성
- 메시지 브로커를 통해 서비스 간 결합도를 낮춰, 각 서비스를 독립적으로 확장 가능.
- 서비스 독립성 강화
- 서비스 간 직접적인 의존성을 제거하고, 메시지 브로커를 통한 간접적 통신으로 독립성 강화.
- 유연한 에러 처리
- 장애 발생 시 메시지를 큐에 저장하여 재처리하거나 다양한 방식으로 에러 처리 가능.
- 성능 향상
- 비동기 메시지 처리로 블로킹 없이 다수의 요청을 처리해 전체 시스템 성능 개선.
Kafka 기반 비동기 메시지 처리로 다음과 같은 성과를 달성했습니다:
- 성능 개선
- 트래픽 폭주 상황에서도 서비스 마비 없이 안정적 동작.
- 신뢰성 향상
- 서비스 다운 시에도 메시지가 큐에 저장되어 복구 후 처리가 가능.
- 확장성 강화
- 각 서비스를 독립적으로 스케일링 가능해 전체 시스템 유연성 증가.
- 데이터 정합성 유지
- 비동기 메시지 처리로 데이터 정합성을 유지하며, 고트래픽 상황에서도 안정적 데이터 처리 가능.
모놀리식 아키텍처에서 MSA로의 전환은 초기에는 복잡해 보일 수 있습니다. 그러나 높은 트래픽과 장애 상황에서 문제를 효과적으로 해결할 수 있는 강력한 방법입니다.
Kafka를 활용한 비동기 메시지 처리는:
- 서비스 간 결합도를 낮추고,
- 시스템 신뢰성과 확장성을 향상시키며,
- 안정적인 사용자 경험을 제공합니다.
이러한 전환을 통해 서비스 간 독립성을 확보하고, 높은 트래픽 상황에서도 확장성과 안정성을 겸비한 아키텍처를 구현할 수 있었습니다.
문제 상황
- 기존 모놀리식 구조에서는 Spring MVC를 사용하여 초당 최대 400명의 사용자 요청을 처리했으나, 이 이상의 트래픽이 발생하면 서버가 과부하로 인해 다운되는 문제가 발생.
- 응답 속도가 느려지고 데이터의 정합성에도 문제가 생길 가능성이 높아짐.
1. MSA 도입과 WebFlux 전환
- 채팅 기능을 독립적인 MSA 서비스로 분리하여 Spring WebFlux를 도입.
- WebFlux의 비동기/논블로킹 특성을 활용해 높은 동시 처리 성능 확보.
- 기존 Servlet 기반의 동기 요청-응답 처리 방식에서 WebFlux 기반의 비동기 논블로킹 방식으로 전환
- 다수의 동시 연결 요청을 처리할 수 있는 확장성 확보
- WebFlux를 적용함으로써 이벤트 루프 기반의 효율적인 리소스 사용으로 응답 시간이 단축됨
- Kafka를 사용하여 메시지 큐 기반의 비동기 통신을 적용, 서비스 간 결합도를 낮추고 안정성 강화.
- 메시지 송수신에서 발생하던 I/O 병목 현상을 완화하기 위해 Kafka를 메시지 브로커로 활용
- WebSocket으로 수신된 메시지를 Kafka Producer로 처리해 분산 브로커에 저장
- Kafka Consumer가 메시지를 Redis에 저장하고, 채팅방 사용자에게 전달
- Kafka를 통해 메시지 큐를 관리함으로써 부하를 분산 처리하고 데이터 손실을 방지
- WebSocket에서 직접 Redis와 연결하는 대신 Kafka를 중간 계층으로 사용해 안정성과 성능 향상
- Redis 캐싱 최적화
- Redis를 사용해 채팅방의 최신 메시지 100개를 캐싱
- 사용자 요청 시 Redis에서 즉시 데이터를 제공해 응답 속도 향상
- 기존 Redis 저장 방식에서 동시성 이슈를 해결하기 위해 락(lock) 메커니즘 적용
- R2DBC로 데이터베이스 성능 개선
- R2DBC를 도입해 MySQL 데이터베이스와의 통신을 비동기 방식으로 전환
- 기존의 동기 JPA 접근 방식에서 발생하던 I/O 대기 시간을 줄여, 채팅 데이터 저장 시 처리 속도 개선
- 트랜잭션 처리가 간단한 채팅 시스템에 적합한 경량 데이터 처리 방식으로 전환
2. 데이터 정합성 보장
- WebFlux와 Kafka를 통해 대량의 데이터를 처리하면서도 4만 건 이상의 데이터 정합성을 100% 유지.
- 데이터 손실 없이 모든 메시지가 안정적으로 DB에 저장되도록 설계.
결과
- 처리량 증가: 초당 400명 → 1500명 이상의 사용자 요청 처리 가능.
- 응답 시간 단축: 평균 응답 시간 80% 이상 감소.
- 데이터 정합성: 100% 데이터 일치 보장.
- 다수의 동시 연결 환경에서도 메시지 손실 없이 실시간 채팅 가능.