KTX의 빈자리 알림을 보내주는 서비스를 개발하고 있습니다.
개요
크롤링을 해서 데이터를 가져 온 후 어떻게 하면 사용자에게 빠르게 전달 할수 있을까 고민하며 쓴 글입니다.
서버 분리
우선 나는 크롤러 서버와 일반 서버를 분리 해야한다고 생각했다.
그 이유는
1. 크롤러는 많은 네트워크 IO 작업과 CPU 리소스를 소비 함으로 일반 서버와 함께 하면 크롤러로 인한 서버 성능 저하가 전체 서비스 성능 저하로 이루어질수 있다.
2. 크롤러 서버를 분리 하고, 만약 크롤러 서버가 죽는다면 서비스에 직접 보여지는 부분은 타격을 입지 않게 할수 있다. 왜냐하면 서비스에 직접 보여지는 부분은 DB에 저장된 열차 정보이기 때문이다. 크롤러 서버가 죽음으로써 생기는 이슈는 실시간으로 가져오던 여석 정보를 못가져와서 내부 알림이 조금 늦게 가는것인데 이것은 사용자 입장에서 체감하지 못할수 있다.
메세지 브로커
우리 서비스는 하나의 기차표가 생기면 여러명에게 알림을 보내는 형태로 도입하면 적합할것 같은 기술로 메세지 브로커가 떠올랐다.
메세지 브로커는 택배함 같은 존재로써 서버와 클라이언트가 1대1로 통신을 하는 기존 방식과 다르게, 내 서비스에 적용하면 크롤러가 크롤링 한 데이터를 메세지 브로커(택배함)에 넣으면 사용자가 메세지 브로커를 뒤져서 정보를 비동기로 가져간다.
그래서 여러 메세지 브로커를 비교 분석하여 어떠한 메세지 브로커가 우리 서비스에 적합 할지 찾아보았다.
메세지 큐 역할을 하는 브로커는 크게 메세지 브로커와 이벤트 브로커로 나뉘었다.
메세지 브로커는 대표적으로 RabbitMQ로 메세지 발행자(Pub)가 메세지를 RabbitMQ에 넣으면 사용자(Consumer)가 메시지 큐에 있는 메시지를 하나씩 가져갈때마다 RabbitMQ는 가져간 메세지를 지운다. 브로커가 메시지를 관리하고 Consumer는 데이터를 요청만 하기 때문에 Smart Broker, Dumb Consumer라고 불리운다. 추가로 메시지들이 주로 메모리에 저장되기 때문에 빠르게 메시지를 가져올수 있지만, 메시지 유실 가능성도 있었다.
이벤트 브로커는 대표적으로 Apache Kafka로 메세지 발행자(Pub)가 메세지를 Kafka에 넣으면 Kafka는 데이터를 들고만 있고 데이터의 인덱스를 사용자(Consumer)가 기억하여 데이터를 인덱스에 맞게 가져간다. 브로커는 데이터를 들고만 있고 그에 맞는 데이터를 Consumer가 요청하기에 Dumb Broker, Smart Consumer라고도 불리운다. 추가로 메시지들이 디스크에 저장되며, 클러스터링에 유리하다.
결론적으로 대용량 데이터 처리에는 클러스터링에 유리한 이벤트 브로커가 좋았고, 작은 데이터가 빠르게 전송되어야 하는 경우는 메시지 브로커가 적합했다. 대용량 데이터 처리라는 말이 모호했지만, 서비스 초기 단계 부터 사람들이 얼마나 알림 구독을 많이 할까 생각이 들어 작은 데이터가 빠르게 전송될수 있는 메세지 브로커를 선택하였다.
RabbitMQ
특징
RabbitMQ의 주체는 Producer, Exchange, Binding, Queue, Consumer 가 있다.
Producer : 메세지를 생성하고 발송하는 주체
→ 우리 서비스에선 크롤러가 이 역할이다
Exchange : Producer에게 전달 받은 메세지를 어떤 Queue에 넣을지 정하는 애
→ 각각의 Queue는 열차 정보가 되고 생긴 열차 좌석에 따라서 Exchange가 큐를 선택한다.
Binding : Exchange에게 메세지를 라우팅 할 규칙을 지정하는 행위이다 .
Queue : 메세지를 Consumer가 소비하기 전까지 보관되는 곳
→ 각각의 Queue는 하나의 열차가 된다
Consumer : 메세지를 수신하는 주체
→ 사용자에게 메세지 알림을 전송할 녀석이다
장점
- 다양한 메시징 패턴 지원 하므로 복잡한 구조를 만들어 낼수 있다.
- 메시지의 전송 및 처리를 확인하고, 트랜잭션을 지원하여 메세지 손실이 방지된다. (높은 신뢰성)
- 메시지 순서와 신뢰성이 중요한 곳에서 사용
단점
- 대규모 데이터 애플리케이션에는 취약하다.
Redis Pub/Sub
특징
- 메세지가 큐에 저장되지 않는다.
- 메시지 발행시 push 방식으로 subscriber들에게 전송한다.
- Subscriber가 늘어날수록 성능이 저하된다.
- Subscriber들이 다양하게 채널을 바꾸면서 한시적으로 구독하는 경우 사용
- 최대 1회 전송 패턴이 적합한 경우
+a 최대 1회 전송 패턴이란?
메시지가 수신자에게 0번 또는 1번 전달될 수 있음을 의미 즉, 메시지가 전송되었지만 수신자가 받지 못할 수 있으며, 메시지가 중복되지 않도록 보장
장점
- 빠르다.
- RabbitMQ보다 한번에 더 많은 메세지 보낼수 있다. (RabbitMQ는 수만개 Redis는 수천만개)
단점
- 메세지를 전송하고 메세지를 삭제하기 때문에 Consumer가 메세지를 받지 못한 경우 메세지를 복구할 방법이 없음 (아마 메모리가 작으니까 메세지를 바로 삭제함으로써 메모리를 확보하는 것 같다)
- 장애 발생시 메세지가 손실된다.
Amazon SQS
특징
- 완전 관리형 메시지 큐 서비스
- 표준 큐와 FIFO 큐가 있다.
표준 큐: 높은 처리량을 제공하지만 순서 보장 X
FIFO 큐: 메세지 순서를 보장하지만 처리량 낮음
장점
- AWS 크레딧을 사용할수 있다
- AWS 환경과 호환이 잘 된다.
- IAM 정책을 통해 큐에 대한 접근 제어를 설정할수 있어 보안이 좋다.
단점
- 메세지 크기 제한이 작다 (256MB)
- 고급 메시징 기능이 없다 (RabbitMQ와 비교 됨)
결론
우리 서비스의 열차 빈자리 여석 실시간 탐지 기능에는 Redis Pub/Sub 구조가 적합해 보였다.
왜냐하면
1. 열차의 빈자리 여석을 찾아서 알림을 보내주는 기능은 속도가 생명이다. 그래서 가장 빠른 Redis를 사용하게 되었다.
2. 빈자리가 생겼다는 알림은 몇개 사라져도 크리티컬 한 정보가 아니기 때문에 괜찮다
(다만 운나쁘게 사라진 알림에 해당하는 기차를 예약하지 못할뿐...)
3. Redis는 Subscriber가 늘어날수록 성능이 저하된다는 단점을 가지고 있는데 우리는 동시에 알림을 받은 사용자가 동시 접속하여 상대 서버에 무리가 가는것을 방지하여 Subscriber 수를 제한할 것이다. 그러므로 이 단점은 우리 서비스엔 해당하지 않는다.
4. Subscriber들이 다양하게 채널을 바꾸면서 기차들을 구독할 것이기 때문에 Redis가 적합하다.
최종 데이터 흐름 구조
최종적으로 Redis Pub/Sub 구조와 크롤러 서버를 분리한 아키텍처는 아래와 같다.
사진 출처
얄코 유튜브
https://www.youtube.com/watch?v=0lyrd5FlETQ
RabitMQ 설명하는 블로그
'기술적 고민 > 자리나따' 카테고리의 다른 글
[자리나따] 대량의 데이터를 빠르게 DB에 넣기 위한 고민 (0) | 2024.11.24 |
---|---|
[자리나따] flyway 적용기 (Spring boot, JPA, MySQL) (1) | 2024.11.22 |
[자리나따] 열차 여석 알림 기능을 개발하며 한 고민 (feat. 커넥션 풀, 비동기) (2) | 2024.10.06 |