목록으로

pushkey가 없으면 알림함도 안 보인다고?

6

pushkey가 없으면 알림함도 안 보인다고?

2026-02-06 ~ 2026-03-05

문제 상황

푸시 알림 서비스에는 두 가지 기능이 있다.

  1. FCM 푸시 발송 — 사용자 폰에 알림이 뜬다
  2. 알림함 저장 — 앱 내 알림센터에 기록이 남는다

이 두 가지는 별개의 기능이다. 앱을 삭제했거나 알림 권한을 꺼둔 사용자는 푸시를 받을 수 없지만, 앱에 다시 들어오면 알림함에서 기록을 볼 수 있어야 한다.

그런데 어느 날, 특정 사용자들의 알림함이 비어 있다는 제보가 들어왔다. 확인해보니 pushkey(FCM 토큰)가 없는 사용자의 알림이 아예 저장되지 않고 있었다.


원인 분석

Kafka Consumer에서 메시지를 받아 처리하는 흐름이 이랬다.

메시지 수신 → pushkey 확인 → pushkey 없으면 return → 알림함 저장도 안 됨

문제 코드의 구조:

public void handleMessage(PushData message) {
    if (message.getToken() == null || message.getToken().isEmpty()) {
        log.debug("pushkey 없음, 스킵");
        return;  // ← 여기서 알림함 저장도 같이 스킵됨!
    }

    saveNotification(message);     // 알림함 저장
    sendFcm(message);              // FCM 발송
}

pushkey 체크가 알림함 저장보다 앞에 있어서, pushkey가 없으면 알림함에도 기록이 안 남았다.


해결: 순서를 바꾸고 관심사를 분리

핵심은 알림함 저장과 FCM 발송은 별개의 관심사라는 것이다.

public void handleMessage(PushData message) {
    // 1. 알림함 저장 — pushkey와 무관하게 항상 실행
    saveNotification(message);

    // 2. FCM 발송 — pushkey가 유효할 때만
    if (isValidToken(message.getToken())) {
        sendFcm(message);
    }
}

수정 후 흐름:

메시지 수신 → 알림함 저장 (항상) → pushkey 확인 → 유효하면 FCM 발송

발송 측에서도 처리

문제가 Consumer 쪽만은 아니었다. 메시지를 보내는 쪽(Producer)에서도 pushkey가 없으면 아예 Kafka에 메시지를 안 보내고 있었다. → Kafka에서 Producer는 메시지를 보내는 쪽, Consumer는 메시지를 받아서 처리하는 쪽이다. 이 글에서는 알림을 발행하는 서비스가 Producer, 실제로 FCM을 쏘는 서비스가 Consumer 역할이다.

// 수정 전: pushkey 없으면 메시지 자체를 안 보냄
if (pushkey != null) {
    kafkaProducer.send(message)
}

// 수정 후: pushkey 없어도 "수신 불가" 표시를 달아서 보냄
val token = if (pushkey.isNullOrBlank()) "UnableToReceive" else pushkey
kafkaProducer.send(message.copy(token = token))

"UnableToReceive"라는 특수값을 넣어서 보내면, Consumer에서는:

  • 알림함 저장은 정상적으로 하고
  • FCM 발송만 스킵한다

이게 왜 중요한가

사용자 입장에서 생각해보면:

상황수정 전수정 후
송금 받았는데 pushkey 없음알림함에 아무것도 안 보임알림함에 "입금 완료" 기록 있음
앱 재설치 후 알림 확인과거 알림 없음과거 알림 정상 표시
알림 권한 꺼둔 사용자거래 기록 누락앱 내에서 확인 가능

거래 내역은 사용자의 자산 기록이다. 푸시를 못 받는 건 괜찮지만, 기록 자체가 없어지면 안 된다.


배운 것

  • "푸시를 못 보내면 아무것도 안 한다"는 잘못된 가정이다. 알림함 저장과 FCM 발송은 별개의 관심사로 분리해야 한다. (관심사 분리란 서로 다른 역할을 하는 로직을 독립적으로 관리하는 설계 원칙이다. 하나가 실패해도 다른 하나에 영향을 주지 않는다)
  • 코드의 실행 순서가 비즈니스 로직을 결정한다. 체크 로직이 저장 로직보다 앞에 있으면, 의도치 않게 저장까지 스킵된다.
  • Producer와 Consumer 양쪽 모두 수정해야 완전한 해결이 된다. 한쪽만 고치면 다른 쪽에서 다시 누락된다.