사전 푸시 알림
개념

푸시 알림

gabury1고친 사람 github-actions[bot]

푸시 알림은 서버가 할 말이 생겼을 때 사용자의 기기에 먼저 소식을 보내는 일입니다. 앱을 열어 두지 않아도 잠금 화면에 알림이 뜹니다. 앱이 서버에 새 소식이 있는지 계속 물어보지 않아도 됩니다. 알림은 운영체제를 만든 회사의 중계 서버를 거쳐 기기에 닿습니다.

쉽고 빠른 이해

무슨 일을 하나 — 서버가 먼저 사용자의 기기에 소식을 보냅니다. 배달 앱을 닫아 두고 다른 일을 하고 있어도, 음식이 출발하면 잠금 화면에 「배달이 출발했습니다」가 뜹니다.

왜 필요한가 — 알림이 없으면 앱이 서버에 새 소식이 있는지 되풀이해 물어야 합니다. 대부분은 없다는 답이라 배터리만 닳습니다. 화면에서 내려간 앱은 휴대폰이 멈춰 두기 때문에 물어볼 수조차 없습니다.

어떻게 도나

  1. 앱이 중계 서버에 등록합니다. 이 기기의 이 앱을 가리키는 주소 값을 받습니다
  2. 앱이 그 주소 값을 우리 서버에 맡깁니다
  3. 알릴 일이 생기면 우리 서버가 주소 값과 내용을 중계 서버에 넘깁니다. 중계 서버가 기기로 전합니다

대가 — 도착을 약속받지 못합니다. 기기가 꺼져 있거나 알림이 쌓이면 늦거나 빠질 수 있습니다. 사용자가 알림을 끄면 그 기기에는 아무것도 안 뜹니다. 너무 자주 보내면 사용자가 알림을 끄거나 앱을 지웁니다.

상세

택배를 기다리는 사람은 현관문을 한 시간마다 열어 보지 않습니다. 기사가 물건을 문 앞에 두고 문자를 보내 줍니다. 기다리는 쪽은 다른 일을 하다가 문자가 오면 그때 나가 봅니다.

푸시 알림(push notification)은 서버가 먼저 사용자 기기로 보내는 짧은 메시지입니다. 메신저의 새 메시지 알림, 배달 앱의 출발 알림, 로그인할 때 휴대폰으로 오는 다중 인증 승인 요청이 그런 메시지입니다. 이 절은 알림 하나가 백엔드 서버에서 휴대폰 화면까지 가는 길을 백엔드 쪽에서 따라갑니다.

물어보지 않아도 오는 소식

푸시 알림이 없으면 앱이 서버에 「새 소식 있나」를 되풀이해 물어야 합니다. 이렇게 정해진 간격으로 묻는 방식을 폴링이라고 합니다. 새 소식은 가끔 생기므로 대부분의 물음은 「없다」로 끝납니다. 그 헛걸음마다 네트워크를 쓰고 배터리가 닳습니다.

더 큰 걸림돌은 앱이 멈춰 있다는 점입니다. 휴대폰 운영체제는 화면에서 내려간 앱을 얼마 뒤 멈춰 둡니다. 멈춘 앱은 코드가 돌지 않으니 서버에 물어볼 수도 없습니다.

그래서 방향을 뒤집습니다. 소식을 가진 서버가 먼저 보냅니다. 받는 쪽이 묻지 않고 보내는 쪽이 먼저 움직이는 방식을 푸시라고 부릅니다. 푸시 알림은 이 방식으로 사용자 기기에 소식을 전하는 일입니다.

운영체제 회사가 운영하는 중계 서버

우리 백엔드 서버는 사용자의 휴대폰에 바로 연결하지 못합니다. 휴대폰은 대개 통신사 망이나 공유기 뒤에 있습니다. 이런 장비는 안쪽 기기 여럿이 바깥 주소 하나를 나눠 쓰게 합니다. 이 일을 NAT(Network Address Translation, 네트워크 주소 변환)라고 합니다. NAT 뒤의 기기는 밖에서 먼저 여는 연결을 받지 못합니다.

그래서 운영체제를 만든 회사가 중계 서버를 운영합니다. 휴대폰은 켜져 있는 동안 이 중계 서버와 연결 하나를 계속 열어 둡니다. 휴대폰 쪽에서 먼저 연 연결이라 NAT 뒤에서도 살아 있습니다. 서버의 알림은 이 연결을 타고 거꾸로 내려옵니다.

모든 앱의 알림이 이 연결 하나로 들어옵니다. 앱마다 제 서버와 연결을 따로 붙잡고 있으면 연결 수만큼 배터리가 닳기 때문입니다.

중계 서버는 플랫폼마다 따로 있습니다. 아래 표는 백엔드 개발자가 흔히 만나는 셋입니다.

받는 기기 중계 서버 운영하는 쪽
iOS 기기 APNs(Apple Push Notification service) Apple
Android 기기 Firebase Cloud Messaging(FCM) Google
웹 브라우저 브라우저마다 정해 둔 푸시 서비스 브라우저를 만든 회사

백엔드는 사용자가 쓰는 플랫폼에 맞는 중계 서버로 보내야 합니다. iOS 사용자와 Android 사용자를 다 받는 서비스는 두 곳에 따로 보냅니다. 브라우저로 보내는 알림은 웹 푸시라고 따로 부릅니다.

디바이스 토큰

중계 서버는 알림을 어느 기기의 어느 앱에 줄지 알아야 합니다. 그 주소 노릇을 하는 값이 디바이스 토큰입니다. 「이 휴대폰에 깔린 이 앱」 하나를 가리키는 긴 문자열입니다. 앱이 중계 서버에 등록할 때 받습니다. 플랫폼에 따라 등록 토큰이라고도 부릅니다.

백엔드는 이 토큰을 사용자 계정에 묶어 저장합니다. 한 사람이 휴대폰과 태블릿을 같이 쓰면 토큰이 둘입니다. 그래서 사용자 한 명 밑에 토큰 여러 개를 두는 테이블 모양이 흔합니다.

토큰은 바뀝니다. 앱을 지웠다 다시 깔거나 기기를 바꾸면 새 토큰이 나옵니다. 그래서 앱은 켜질 때마다 토큰을 새로 받아 백엔드에 다시 보냅니다. 중계 서버가 「이 토큰은 이제 없다」고 답하면 백엔드는 그 토큰을 지웁니다.

알림 하나가 가는 차례

이 소절은 등록부터 화면에 뜨기까지를 한 줄로 잇습니다. 참여자는 셋입니다. 사용자 기기의 앱, 운영체제 회사의 중계 서버, 우리 백엔드입니다.

sequenceDiagram
    participant 앱
    participant R as 중계 서버
    participant 백엔드
    앱->>R: 알림을 받겠다고 등록
    R-->>앱: 디바이스 토큰
    앱->>백엔드: 디바이스 토큰 저장
    Note over 백엔드: 알릴 일이 생긴다
    백엔드->>R: 토큰과 알림 내용
    R-->>앱: 알림 전달
    Note over 앱: 운영체제가 화면에 띄운다

위의 세 화살표는 앱이 깔리거나 켜질 때 일어납니다. 아래 두 화살표는 알릴 일이 생길 때마다 일어납니다. 백엔드 입장에서 푸시 알림은 중계 서버에 요청 하나를 보내는 일입니다.

그 요청은 보통 HTTP(HyperText Transfer Protocol) 요청입니다. 요청 본문에는 받을 토큰, 화면에 띄울 제목과 내용, 앱에 넘길 데이터를 싣습니다. 이렇게 요청에 실어 보내는 알맹이를 페이로드라고 합니다.

중계 서버는 아무 서버의 요청이나 받지 않습니다. 남이 우리 앱 사용자에게 알림을 보내면 안 되기 때문입니다. 그래서 백엔드는 이 앱을 만든 쪽이라는 증명을 요청에 함께 싣습니다. 개발사가 미리 발급받은 키나 인증서가 그 증명입니다.

화면에 뜨는 알림과 조용한 알림

푸시 알림에는 두 종류가 있습니다. 하나는 제목과 내용을 화면에 띄우는 알림입니다. 앱이 멈춰 있어도 운영체제가 대신 띄워 줍니다. 사용자가 알림을 누르면 그때 앱이 열립니다.

다른 하나는 화면에 아무것도 안 띄우는 조용한 알림입니다. 사용자 몰래 앱을 잠깐 깨워 데이터를 건넵니다. 앱은 그 틈에 서버에서 새 데이터를 받아 둡니다. 다음에 사용자가 앱을 열면 기다리지 않고 새 내용을 봅니다.

조용한 알림은 운영체제가 더 엄하게 다룹니다. 사용자가 보지 않는 알림이 배터리를 쓰기 때문입니다. 너무 자주 오면 운영체제가 늦추거나 건너뛰기도 합니다.

도착을 약속하지 않는 전달

중계 서버는 알림을 기기로 전하려고 애쓰지만 도착을 약속하지는 않습니다. 기기가 꺼져 있으면 알림을 잠시 들고 있다가 켜지면 전합니다. 들고 있는 동안 같은 앱의 알림이 여럿 쌓이면 마지막 것만 남기기도 합니다.

그래서 알림을 데이터의 원본으로 쓰지 않습니다. 알림은 「새 소식이 있다」는 신호로만 씁니다. 내용은 앱이 열릴 때 백엔드에서 다시 받아 옵니다. 메신저 알림 하나를 놓쳐도 앱을 열면 메시지가 전부 보이는 까닭이 이것입니다.

알림을 받을지는 사용자가 정합니다. 앱은 알림을 보내기 전에 사용자의 허락을 받아야 하는 경우가 많습니다. 사용자는 설정에서 언제든 알림을 끌 수 있습니다. 끈 기기에는 백엔드가 아무리 보내도 알림이 뜨지 않습니다.

푸시 알림을 고르지 않는 때

푸시 알림은 멈춰 있는 앱에 소식을 전하는 길입니다. 받는 쪽의 사정에 따라 따로 쓰는 길이 있습니다. 갈림은 두 번입니다.

flowchart TD
    A{"받는 쪽이 사용자 기기인가"} -->|아니오 · 다른 서버| B["웹훅"]
    A -->|예| C{"앱이 지금 화면에 떠 있나"}
    C -->|예| D["웹소켓 · Server-Sent Events"]
    C -->|아니오| E["푸시 알림"]

받는 쪽이 다른 회사의 서버라면 그 서버 주소로 HTTP 요청을 바로 보냅니다. 이 방식을 웹훅이라고 합니다. 서버는 늘 켜져 있고 주소가 정해져 있어서 중계 서버가 필요 없습니다.

앱이 화면에 떠 있는 동안에는 중계 서버를 거치지 않습니다. 앱이 백엔드와 직접 연결을 열어 두고 주고받습니다. 채팅방 화면 안에서 오가는 메시지가 그렇습니다. 연결 하나로 양쪽이 주고받는 웹소켓이나, 서버가 한쪽으로 계속 흘려보내는 Server-Sent Events를 씁니다.

알림을 자주 보내는 데도 값이 따릅니다. 알림이 너무 많아 사용자가 무뎌지는 상태를 알림 피로라고 합니다. 이 상태의 사용자는 알림을 끄거나 앱을 지웁니다. 한 번 꺼진 알림은 백엔드가 다시 켤 방법이 없습니다.

관련 항목

푸시 알림을 기기까지 나르는 중계 서비스

APNs · Firebase Cloud Messaging · 웹 푸시 · Push API · 서비스 워커 · Amazon SNS

푸시 알림을 받아 띄우는 플랫폼

Android · iOS · 브라우저 · 운영체제 · 모바일 개발

푸시 알림 한 건을 이루는 구성 요소

디바이스 토큰 · 페이로드 · 딥 링크 · 인증서 · JWT · HTTP

푸시 알림 대신 쓰는 전달 방식

폴링 · 롱 폴링 · 웹소켓 · Server-Sent Events · 웹훅 · SMS · 이메일

푸시 알림이 기대는 네트워크 원리

푸시 · 발행-구독 · 지속 연결 · 킵얼라이브 · NAT · 메시지 큐

푸시 알림을 운영할 때 부딪히는 문제

알림 피로 · 토큰 만료 · 속도 제한 · 재시도 · 멱등성

푸시 알림으로 사용자에게 전하는 기능

다중 인증 · 백그라운드 동기화 · 배달 추적 · 채팅

다른 이름: push notification · 푸시 노티피케이션 · 푸시 메시지