에러 내용
여러 개의 FCM 토큰에 각기 다른 내용을 전송하기 위해 sendAll() 메서드를 사용하니, 404 에러가 발생했다.
해결
Firebase Cloud Messaging(FCM)은 여러 사용자에게 알림을 보낼 때, 다음과 같은 API 메서드를 제공한다.

MulticastMessage 의 한계
- MulticastMessage 는 여러 FCM 토큰에 동일한 메시지를 보낼 수 있음
- 그러나 개별 토큰마다 내용을 다르게 설정할 수 없음
- 최대 500개의 FCM 토큰만 허용
- 일부 토큰 전송 실패 시 개별 실패 원인을 파악하기 어려움
MulticastMessage message = MulticastMessage.builder()
.addAllTokens(Arrays.asList("token1", "token2", "token3"))
.putData("title", "공통 알림 제목")
.putData("body", "이 메시지는 모든 사용자에게 동일하게 전송됩니다.")
.build();
BatchResponse response = FirebaseMessaging.getInstance().sendMulticast(message);
따라서 개별 메시지를 보내야 하는 경우 sendAll() 로 여러 개의 서로 다른 메시지를 한 번의 요청으로 전송하거나, send() 메서드를 반복 사용하는 방법을 사용할 수 있다.
sendAll() 의 한계 (HTTP v1 API 와의 문제점)
sendAll() 메서드는 여러 개의 서로 다른 메시지를 한 번의 요청으로 전송할 수 있지만, Firebase 의 최신 HTTP v1 API 에서는 지원되지 않는다.
- 최신 HTTP v1 API 와 호환되지 않음 (404 오류 발생)
- Firebase 에서 권장하지 않는 방식, 폐지될 가능성 존재
- 대량 메시지를 전송할 때 트래픽 부하 발생 가능
List<Message> messages = Arrays.asList(
Message.builder()
.setToken("token1")
.putData("title", "사용자 1 알림")
.build(),
Message.builder()
.setToken("token2")
.putData("title", "사용자 2 알림")
.build()
);
BatchResponse response = FirebaseMessaging.getInstance().sendAll(messages);
따라서 이를 해결하기 위해 개별 send() 호출로 병렬 처리하는 방법을 사용할 수 있다.
send() 메서드를 개별적으로 호출
FCM 의 최신 HTTP v1 API 를 활용하려면, 각 메시지를 개별적으로 send() 메서드를 호출하여 전송해야 한다.
장점
- 각 토큰에 개별적인 데이터를 보낼 수 있음
- 최신 Firebase HTTP v1 API 와 호환됨
- 메시지 전송 실패 시 개별적으로 재시도 가능
단점
- 하나씩 전송해야 하므로 성능 이슈 발생 가능
- 네트워크 부하가 증가할 수 있음
for (UserNotificationData notification : notifications) {
Message message = Message.builder()
.setToken(notification.token())
.putData("scheduleName", notification.scheduleName())
.putData("days", notification.days())
.putData("busStopName", notification.busStopName())
.putData("firstBusName", notification.firstBusName())
.putData("firstArrPrevStCnt", String.valueOf(notification.firstArrPrevStCnt()))
.putData("firstArrTime", String.valueOf(notification.firstArrTime()))
.build();
try {
String response = FirebaseMessaging.getInstance().send(message);
log.info("메시지 전송 성공: {}, 토큰: {}", response, notification.token());
} catch (FirebaseMessagingException e) {
log.error("메시지 전송 실패: {}, 토큰: {}", e.getMessage(), notification.token());
}
}
이를 해결하기 위해 병렬 처리(멀티스레딩) 또는 비동기 처리를 적용하여 성능을 개선할 수 있다.
병렬/배치 전송을 위한 최적화 방안
개별 send() 호출로 인한 성능 저하를 해결하기 위해 병렬 처리 또는 배치 크기를 조정하는 방법을 사용할 수 있다.
ExecutorService executorService = Executors.newFixedThreadPool(10);
List<Future<?>> futures = new ArrayList<>();
for (UserNotificationData notification : notifications) {
futures.add(executorService.submit(() -> {
try {
Message message = Message.builder()
.setToken(notification.token())
.putData("scheduleName", notification.scheduleName())
.build();
String response = FirebaseMessaging.getInstance().send(message);
log.info("메시지 전송 성공: {}", response);
} catch (FirebaseMessagingException e) {
log.error("메시지 전송 실패: {}", e.getMessage());
}
}));
}
// 모든 작업 완료 대기
for (Future<?> future : futures) {
try {
future.get();
} catch (Exception e) {
log.error("메시지 전송 작업 실패", e);
}
}
executorService.shutdown();
정리
- 각기 다른 내용을 전송 시
- sendMulticast() 사용 불가
- sendAll()도 최신 HTTP v1 API에서는 사용 불가
- 따라서 send() 개별 호출 필요
- 동일한 내용을 대량으로 전송 시
- sendMulticast()를 사용 (최대 500개 토큰)
- 최적화가 필요할 경우
- 병렬 전송(멀티스레드) 적용