개요
DelFood에 드디어 푸시 메세지 전송 기능이 추가되었습니다. Firebase Cloud Messaging(이하 FCM)을 기반으로 앱, 웹으로 사용자에게 푸시 메세지 전송 기능을 제작하였습니다. 여러 사용자에게 순차적으로 푸시 메세지를 전송할 수도 있고, 한 명의 사용자에게 푸시 메세지를 전송할 수도 있습니다. 원한다면 하나의 topic을 따르는 사용자들에게도 일괄적으로 전송할 수 있습니다.
당장 구현을 할 때는 몰랐지만 구현을 다 마치고 곰곰히 코드를 보며 생각해 보니 의문점이 떠올랐습니다. '푸시메세지를 보낼 사용자가 엄청 많아지면 어떻게 되는거지?' 라는 생각이 들었습니다.
변경 전 로직
한 사용자에게 푸시 메세지를 전송하는 로직은 다음과 같습니다.
1. 사용자가 가지고 있는 토큰들을 조회한다. 2. 해당 토큰과 푸시 메세지 정보를 FCM 서버로 전송한다. 3. FCM 서버에서 응답이 정상적으로 올 때 까지 기다린다. 4. 다음 토큰을 조회하여 푸시 메세지 정보를 FCM 서버로 전송한다. ...(반복) |
사용자가 얼마 없을 때는 큰 문제가 없을 것입니다. 하지만 사용자가 10만명, 100만명 이상으로 늘어난다면 어떻게 될까요? 사용자가 기기를 10대, 20대씩 등록하여 푸시메세지를 동시에 여러 건 전송해야 한다면 어떻게 될까요?
FCM서버로 전송하고 응답을 받을 때 까지의 시간이 점점 성능적인 부분에서 문제를 발생시킬 가능성이 보입니다. 요청과 응답 사이 해당 스레드는 IO Blocking이 걸리기 때문에 스레드가 작동을 멈추기 때문입니다. Blocking이 걸린 동안 다른 일을 하지 않으니 요청과 응답이 많아질수록 성능은 기하급수적으로 떨어지게 됩니다.
개선 방향
이 문제를 해결하기 위해 여러 방법을 고민해보았습니다.
해당 로직의 문제는 외부 FCM서버에 다녀올 때 까지 스레드가 대기 상태로 기다려야 한다는 것입니다. 그렇다면 응답 까지의 대기를 다른 스레드에 맡긴다면 되지 않을까요?
블로킹 문제가 발생한다면 이를 비동기 처리로 변경하는 것이 해결 방법이 될 수 있을 것이라 생각합니다. 더이상 요청-응답-요청-응답... 식의 푸시 전송이 아닌 동시에 요청을 전송하고 응답이 오는대로 처리를 할 수 있을 테니까요.
제가 생각한 해결 방법은 새로운 스레드를 만든 후 요청과 응답까지 해당 스레드에게 책임을 부여하는 것입니다. 그렇다면 메인 스레드에서는 일괄적으로 스레드를 생성하여 요청을 전송하기만 하면 될 것입니다.
또한 응답이 온 후 성공, 실패 여부에 따라 알맞은 콜백 메서드를 설정하여 실행한다면 후처리 또한 간단하게 진행 될 것입니다.
위 방식으로 변경한다면 더 이상 해당 스레드의 IO Blocking기간동안 응답을 기다리지 않아도 됩니다. 한번에 여러 스레드를 사용하여 요청을 보내고, 응답이 왔을 때 후처리 로직을 실행하면 될것이니까요. 동시에 요청을 보낼 수 있으니 성능과 속도 또한 개선될 것으로 기대됩니다.
DelFood에서는 따로 클라이언트를 개발하지 않고 있습니다. 그렇기 때문에 실제 FCM 토큰을 받아서 푸시메세지를 전송하는 테스트를 적용할 수 없습니다. 추후 클라이언트 개발까지 진행한다면 성능 테스트를 진행할 수 있을것이지만 당장 속도와 성능 테스트를 할 수 없다는 것이 아쉽습니다.
변경 방법
Spring에서는 이를 간편하게 해결할 수 있는 비동기 어노테이션을 제공하고 있습니다.
@Async 어노테이션을 사용하면 간단하게 메서드를 비동기 방식으로 변경할 수 있습니다.
또한 Google Firebase 라이브러리에서도 비동기 메세지 전송을 제공합니다. 'FirebaseMessaging' Class를 살펴보면 다음과 같은 메서드가 있습니다.
/**
* Similar to {@link #send(Message)} but performs the operation asynchronously.
*
* @param message A non-null {@link Message} to be sent.
* @return An {@code ApiFuture} that will complete with a message ID string when the message
* has been sent.
*/
public ApiFuture<String> sendAsync(@NonNull Message message) {
return sendAsync(message, false);
}
해당 메세지 전송을 비동기로 처리할 수 있도록 처리해 준다는 것입니다. 비동기 처리이기 때문에 Future의 구현체를 리턴합니다.
이 두가지 어노테이션을 사용하여 전송 메세지를 비동기 방식으로 변경하였습니다. 자세한 변경 사항은 아래 PR 기록을 살펴주세요.
https://github.com/f-lab-edu/food-delivery/pull/56
Reference
https://firebase.google.com/docs/cloud-messaging
'Project > DelFood' 카테고리의 다른 글
[DelFood] Ngrinder로 진행한 성능 테스트 (0) | 2020.02.13 |
---|---|
[Delfood] CI/CD 서버 구축과 첫 배포 (0) | 2020.02.11 |
[이슈 #9] Mybatis <collection> 태그 N+1 문제 없이 사용하기 (4) | 2019.12.18 |
[이슈 #8] 1:N:M 관계 INSERT 시 N + M번의 쿼리 발생을 리팩토링하기 (1) | 2019.12.18 |
[이슈 #7] 서버 부하를 줄이기 위한 캐싱 적용 (0) | 2019.11.21 |
댓글