-
ShMarket - 푸시 알림과 성능 개선ShMarket 2021. 7. 5. 22:03
ShMarket의 주요 기능은 사용자 간의 물품 거래입니다.
물품을 거래하기 위해 사용자 간 소통할 때 알림이 제 때 오지 않는다면 답답하고 쾌적한 거래를 할 수 없을뿐더러
원하는 물품을 키워드로 등록해 알림을 기다릴 때 역시 제 때 오지않는다면 다른 사용자가 먼저 선점하는 등의 문제가 발생할 수 있다고 생각해 푸시 알림을 도입하고자 했습니다.
푸시 알림 기능은 Firebase Cloud Messaging (FCM)을 사용해 웹을 기반으로 사용자에게 푸시 알림을 전송할 수 있도록 제작하였습니다.
앞서 푸시 알림이 필요한 상황을 채팅과 키워드 알림으로 예를 들었는데 FCM을 사용함으로써 이 모두를 충족할 수 있다고 생각했습니다.
1. 채팅
채팅시 1:1로 주고받는 대화를 진행하고 이는 특정 대상에 대해 메시지를 전송합니다.
2. 키워드 알림
키워드 알림 또한 알림을 설정한 사용자들을 모아 List 형식으로 FCM에게 메시지를 요청할 수 있지만 FCM의 topic 구독 기능을 사용해 구현함으로써 보다 편하게 전송할 수 있는 환경을 갖추게 되었습니다.
푸시 메시지를 전송하는 기존의 로직
1. 상품을 등록하거나 채팅 메시지 전송
2. 사용자의 FCM 토큰을 조회 ( 채팅 시에만 )
3. FCM 서버로 푸시 메시지 전송 요청
4. FCM 서버의 응답 대기
5. 알림(Notice) Entity에 저장 및 조회
예상되는 문제점
사용자가 많아졌을 때 요청과 응답 대기 시간이 늘어남에 따른 성능 저하가 있을 것이라 생각됩니다.
기존의 동기식 전송
위와 같이 동작하게 된다면 FCM 서버로 푸시 메시지를 요청한 뒤 FCM 서버에서 응답할 때까지 기다리는 시간이
사용자가 늘어남에 비례해 점점 늘어남으로써 성능이 저하될 수 있다는 가능성이 있다고 생각됩니다.
블락이 걸려있는 동안 스레드는 다른 일을 해결하지 않으니 요청과 응답이 많아 대기 시간이 늘어날수록 성능은 반비례해 성능이 저하됨을 예상할 수 있었습니다.
문제 개선 방향과 해결 방안
위의 방식은 동기적으로 실행했을 때 발생할 수 있는 문제점으로
해결책은 비동기식으로 로직을 변경해 FCM 푸시 메시지 요청과 기타 로직을 처리하는 것이었습니다.
이를 프로젝트에 적용하고자 RabbitMQ를 도입하기로 했습니다.
RabbitMQ를 채택하게 된 이유는
1. 채팅과 키워드 알림이 유실되어 확인하지 못하는 경우 사용자가 불편함을 느껴 이탈할 수 있는 가능성이 있기에
최소 한번은 전달할 수 있고 유실을 최소화하기 위해 선택
2. 메시지 브로커로써 비동기적으로 처리할 수 있기에 선택
RabbitMQ를 적용한 뒤 프로젝트 구조
1. 상품 등록 ( 키워드 알림 ), 채팅 전송
2. Topic Exchange를 통해 키워드 알림이라면 Keyword Queue, 채팅이라면 Chat Queue로 메시지 전달
3. 각 Queue에 연결된 Consumer를 통해 메시지 소비
RabbitConsumer
@RabbitListener(queues = "keyword-queue", concurrency = "6") public void pushConsumer(final Message message) throws IOException { ObjectMapper objectMapper = new ObjectMapper(); NotificationResponse notificationResponse = objectMapper.readValue(message.getBody(), NotificationResponse.class); pushService.sendTopic(notificationResponse); } @RabbitListener(queues = "chat-queue", concurrency = "6") public void pushChatConsumer(final Message message) throws IOException { ObjectMapper objectMapper = new ObjectMapper(); ChatRequestDto chatRequestDto = objectMapper.readValue(message.getBody(), ChatRequestDto.class); pushService.sendByToken(chatRequestDto); chatService.addChatLog(chatRequestDto); }
키워드 알림을 비동기적으로 사용하기 위한 멀티 컨슈머를 적용한 뒤 푸시 메시지를 테스트했을 때 각기 다른 컨슈머에서 메시지를 소비하는 것을 확인할 수 있었습니다.
채팅 메시지 또한 멀티 컨슈머를 적용해 각기 다른 컨슈머에서 메시지를 소비하는 것을 확인할 수 있었습니다.
최종적으로 채팅과 키워드 알림을 사용하는 푸시 알림 서비스는 다음과 같이 구성되어 있습니다.
1. 상품 등록 (키워드 알림), 채팅 전송 등 푸시 알림 서비스 요청
2. Rabbit MQ Producer로 메시지 생성
3. Rabbit MQ Consumer에서 메시지 소비
3.1 사용자 FCM 토큰을 Redis에서 조회
3.2 알림 (Notice) Entity 추가
3.3 채팅 (Chat) Entity에 추가 ( 채팅 내역과 채팅방 )
3.3 FCM 서버로 푸시 알림 요청
4. 클라이언트에게 Back or Fore Ground FCM 푸시 알림 전달
5. 알림을 전달받은 사용자의 Notice, Chat Entity 변경 및 재조회
메시지의 유실, 서버 장애를 대비해 Rabbit MQ 클러스터를 구성하고 노드를 추가하는 작업 추가 예정입니다.
'ShMarket' 카테고리의 다른 글
ShMarket - Redis 캐싱 전략과 장애 대비 (0) 2021.07.31 ShMarket - 조회 성능 최적화 기록기 (0) 2021.06.21 ShMarket - 이슈 #1 N+1문제와 그로 파생되는 문제 해결 (0) 2021.06.20 ShMarket - 프로젝트 소개 (0) 2021.06.19