푸시가 두 번 가던 이유 — 더블클릭과 크론이 만든 경합

관리자에서 푸시를 보내면 일부 사용자에게 알림이 두 번 도착한다는 제보가 있었다. 발송 수 통계와 실제 도달 수가 어긋나 원인 파악부터 했다.

재현으로 찾은 두 경로

경로 1 — 동시 요청. 발송 버튼을 빠르게 두 번 누르거나 탭 두 개에서 같은 화면을 열어두면, 같은 발송 단위가 두 번 처리됐다. 서버가 "발송 중" 상태로 바꾸기 전에 두 번째 요청이 들어와 둘 다 조건을 통과했다.

경로 2 — 재시도. 외부 발송 API 호출이 타임아웃으로 끊겼을 때 재시도를 걸어뒀는데, 실제로는 발송이 성공하고 응답만 늦게 온 경우가 있었다. 재시도가 두 번째 발송을 만들었다.

막은 방식

  • 발송 단위에 잠금을 걸어 동시에 하나만 진입하게 했다. 두 번째 요청은 대기가 아니라 즉시 "이미 처리 중"으로 끝난다. 대기시키면 결국 순서대로 두 번 나간다.
  • 외부 호출에 타임아웃과 멱등 키를 붙였다. 같은 키로 다시 요청하면 재발송되지 않는다.
  • 발송 결과를 사유별로 집계해 화면에 표시했다. 유효하지 않은 토큰, 수신 거부, 실패를 나눠 보니 "발송 수는 늘었는데 도달이 그대로"인 상황을 바로 알 수 있게 됐다.

통계가 안 맞던 진짜 이유

중복 발송을 막고 나서도 숫자가 조금 어긋났다. 확인해보니 발송 기록을 쓰는 시점과 세션 처리가 겹쳐 일부 기록이 늦게 반영되고 있었다. 기록 쓰기를 먼저 커밋하고 외부 호출을 나중에 하도록 순서를 바꿔 정리했다.

남는 이야기

중복 발송은 사용자 입장에서 사소해 보여도 알림 차단으로 직결된다. 한 번 차단당하면 되돌리기 어렵다. 그래서 막는 쪽을 보수적으로 잡았다. 애매하면 안 보내는 편이 낫다는 기준으로 조건을 정리했다.

푸시백엔드동시성운영
푸시가 두 번 가던 이유 — 더블클릭과 크론이 만든 경합 · peppercode 개발 노트