FCM 푸시를 보내는 기능의 로그에는 전부 성공으로 남는데 못 받는다는 문의가 있었다. 발송 코드는 sendMulticast() 가 예외를 안 던지면 성공으로 기록하고 있었다.
Table of contents
Open Table of contents
원인 — 호출 성공과 발송 성공
호출이 예외 없이 끝났다는 것과 그 알림이 실제로 발송됐다는 것은 다른 사실이다. 로그에는 앞의 것만 남고 있었으므로 뒤의 것은 어디에도 기록되지 않았다.
발송 건수라는 숫자 하나만 있었고 실제로 받은 사람이 몇인지는 알 수 없었다. 성공이라는 말이 무엇의 성공인지가 정의돼 있지 않은 상태였다.
검증 — 응답 안에 있던 실패
반환된 BatchResponse 를 열어 보니 successCount 와 failureCount 와 항목별 responses 가 담겨 있었다. 전체의 상당 부분이 실패로 표시돼 있었는데 응답을 안 봐서 몰랐다.
응답을 버리는 코드는 실패를 성공으로 기록하고 그 기록은 아무도 의심하지 않는다. 응답을 파싱해 실패 건수와 사유를 함께 남기게 고쳤다.
조치 — 실패 사유별 대응
사유를 보니 UNREGISTERED 가 대부분이었고 INVALID_ARGUMENT 와 SENDER_ID_MISMATCH 가 뒤를 이었다. FCM 공식 문서대로 UNREGISTERED 는 앱 인스턴스가 등록 해제돼 그 토큰이 더는 유효하지 않다는 뜻이라 재시도해도 계속 실패하는 종류였다.
두 가지 방법이 있었는데 실패 토큰을 그대로 두고 매번 보내는 것과 device_tokens.is_active 를 꺼서 다음부터 안 보내는 것이다. 뒤쪽을 골랐다. 이유는 같은 토큰에 계속 보내면 실패 건수만 쌓여 진짜 실패가 묻혀서다. 대신 앱을 다시 설치한 사용자는 새 토큰이 등록될 때까지 못 받는 것을 감수했다. 며칠 만에 실패 건수가 한 자릿수 비율로 내려갔다.
주의 — 발송과 도착과 열람
발송 성공도 도착을 뜻하지 않아서 기기가 꺼져 있으면 나중에 도착한다. 앱이 onMessageReceived 에서 수신을 알려 주게 해서 발송과 도착과 열람을 각각 세게 했다.
도착이 발송보다 적은 것은 정상이고 그 차이가 기기 사정을 뜻한다. 어디까지가 우리 책임이고 어디부터가 기기 사정인지가 이 세 숫자로 갈렸다.
배경 — 다른 발송 경로와 도착 비율
같은 방식으로 메일과 문자도 보니 각각 단계가 있었고 메일은 bounce 를 안 보고 있었다. 없는 주소로 계속 보내면 발송 평판이 나빠져 정상 주소에도 안 가게 된다.
도착 비율을 매일 보게 하니 갑자기 떨어진 날을 잡을 수 있었다. 한 번은 외부 알림 서비스 쪽 장애였고 우리가 먼저 알고 문의하니 그쪽도 인지하고 있었다.
정리
- 호출이 성공한 것과 발송이 성공한 것은 다르다
- 응답을 버리면 실패가 성공으로 기록된다
- 그 기록은 아무도 의심하지 않는다
- 실패 사유를 보고 재시도해도 안 되는 대상은 끈다
- 발송과 도착과 열람을 나눠 세면 책임 범위가 갈린다
- 메일과 문자도 각각 단계가 있고 반송을 봐야 한다
- 반송을 안 보면 발송 평판이 나빠진다
- 도착 비율을 매일 보면 떨어지는 날을 잡는다