ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 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);
        }

     

    키워드 알림을 비동기적으로 사용하기 위한 멀티 컨슈머를 적용한 뒤 푸시 메시지를 테스트했을 때 각기 다른 컨슈머에서 메시지를 소비하는 것을 확인할 수 있었습니다. 

    keyword Queue 6개의 컨슈머

    채팅 메시지 또한 멀티 컨슈머를 적용해 각기 다른 컨슈머에서 메시지를 소비하는 것을 확인할 수 있었습니다.

    chat Queue 6개의 컨슈머

     

    최종적으로 채팅과 키워드 알림을 사용하는 푸시 알림 서비스는 다음과 같이 구성되어 있습니다.

     

    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 클러스터를 구성하고 노드를 추가하는 작업 추가 예정입니다.

    댓글

Designed by Tistory.