실시간 통신 방식 비교: 폴링·SSE·WebSocket·모바일 푸시 선택 기준

반응형

실시간 기능을 만들 때 가장 먼저 나눌 것은 앱이 열려 있는 동안 데이터를 갱신할 것인지, 앱이 꺼진 사용자에게 다시 방문할 이유를 알릴 것인지다. 전자는 폴링·SSE·WebSocket 같은 연결 방식의 문제이고, 후자는 APNs·FCM을 통한 모바일 푸시 알림의 문제다.

이 둘을 하나의 프로토콜 비교표로 놓으면 요구사항이 섞인다. 푸시 알림은 앱의 최신 상태를 보관하는 데이터 채널이 아니며, WebSocket은 종료된 모바일 앱을 깨우는 알림 채널이 아니다.

먼저 네 가지 질문을 정한다

  1. 데이터는 서버에서 클라이언트로만 흐르는가, 양방향인가
  2. 새 이벤트를 몇 초 늦게 받아도 되는가
  3. 브라우저 탭이나 앱이 닫힌 뒤에도 알려야 하는가
  4. 연결이 끊겼을 때 어느 지점부터 복구해야 하는가

이 답으로 후보를 상당히 줄일 수 있다.

요구사항 우선 검토할 방식
수십 초 늦어도 되는 상태 갱신 주기적 폴링
기존 HTTP 인프라에서 이벤트를 빨리 받고 싶음 롱폴링
서버 → 브라우저 단방향 이벤트 스트림 SSE
채팅·협업처럼 지속적인 양방향 메시지 WebSocket
앱이 닫혀도 OS 알림이 필요 APNs·FCM

폴링은 단순함이 장점이다

주기적 폴링은 클라이언트가 일정 간격으로 최신 상태를 요청한다.

GET /jobs/42
→ RUNNING

10초 뒤 GET /jobs/42
→ SUCCEEDED

실시간성이 수초에서 수분 단위여도 되고 사용자 수가 크지 않다면 가장 운영하기 쉬운 선택일 수 있다. 일반 HTTP 인증·캐시·관측 도구를 그대로 사용하고, 연결 상태를 별도로 관리하지 않는다.

단점은 변경이 없어도 요청이 발생한다는 점이다. 간격을 짧게 할수록 지연은 줄지만 서버·네트워크 부하는 늘어난다. 화면이 숨겨졌을 때 중지하고, exponential backoff와 ETag·If-None-Match 같은 조건부 요청을 적용할 수 있다.

롱폴링은 응답 뒤 다시 요청한다

롱폴링은 서버가 이벤트나 timeout이 생길 때까지 요청을 열어 둔다. 응답을 받은 클라이언트는 즉시 다음 요청을 보낸다.

client ── request ──> server
client <─ event  ─── server
client ── next request ──> server

일반 HTTP 요청·응답 모델을 유지하므로 기존 인프라와 호환해야 할 때 쓸 수 있다. 그러나 이벤트마다 완전한 HTTP 요청과 응답이 반복되고, proxy·load balancer timeout과 동시 연결 제한을 고려해야 한다.

원문의 “클라이언트가 주기적으로 요청한다”는 표현은 짧은 폴링과 혼동될 수 있다. 롱폴링은 보통 응답을 받거나 timeout이 난 직후 다음 요청을 이어 항상 대기 중인 요청에 가깝게 만든다.

SSE는 서버에서 브라우저로 보내는 스트림이다

Server-Sent Events(SSE)는 HTTP 응답을 text/event-stream으로 유지하며 서버가 브라우저에 이벤트를 보낸다. 브라우저의 EventSource는 연결이 끊기면 재연결하고, 이벤트에 id가 있으면 Last-Event-ID를 이용해 이어받을 단서를 제공한다.

id: 1042
event: job-status
data: {"jobId":42,"status":"SUCCEEDED"}
const events = new EventSource('/events/jobs');

events.addEventListener('job-status', (event) => {
  const payload = JSON.parse(event.data);
  console.log(payload);
});

대시보드, 빌드 로그, 알림 목록처럼 서버에서 클라이언트로만 이벤트가 흐를 때 단순하다. 클라이언트가 서버로 보내는 일반 명령은 별도 HTTP 요청을 사용하면 된다.

운영에서는 다음을 확인한다.

  • proxy buffering이 스트림을 모아 두지 않는가
  • idle timeout보다 짧은 heartbeat가 필요한가
  • 이벤트 ID와 replay 저장소가 있는가
  • 브라우저의 EventSource 인증 방식이 서비스 정책과 맞는가
  • 느린 클라이언트에 대한 backpressure 정책이 있는가

WebSocket은 양쪽이 독립적으로 보낼 때 선택한다

WebSocket은 opening handshake 뒤 지속 연결에서 양방향 메시지를 주고받는다.

const socket = new WebSocket('wss://example.com/ws');

socket.addEventListener('open', () => {
  socket.send(JSON.stringify({ type: 'subscribe', roomId: '42' }));
});

socket.addEventListener('message', (event) => {
  console.log(JSON.parse(event.data));
});

채팅, 공동 편집과 게임처럼 클라이언트와 서버가 서로 독립적으로 낮은 지연의 이벤트를 계속 보낼 때 적합하다. 하지만 연결만 열었다고 신뢰성 있는 메시징이 완성되는 것은 아니다.

애플리케이션 프로토콜에 다음 항목을 정해야 한다.

  • 메시지 type과 schema
  • 인증 갱신과 권한 변경 반영
  • ping/pong과 연결 유효성 판단
  • 재연결 시 마지막 처리 위치
  • 중복·순서 뒤바뀜과 idempotency
  • 느린 소비자와 송신 buffer 제한
  • 서버 배포 시 drain과 reconnect 전략

양방향이라는 이유만으로 WebSocket을 기본값으로 두지 않는다. 서버 → 클라이언트 스트림만 필요하면 SSE가 구현·관측 면에서 더 단순할 수 있다.

모바일 푸시는 최신 데이터의 원본이 아니다

iOS는 APNs, Android와 여러 플랫폼에서는 FCM을 통해 원격 알림을 전달한다. 앱이 foreground 연결을 유지하지 않아도 OS가 알림을 표시하거나 제한된 background 처리를 시작할 수 있다는 점이 SSE·WebSocket과 다르다.

FCM의 notification message와 data message는 처리 주체가 다르다. background에서 notification message는 SDK가 표시하고, data message는 앱 코드가 처리한다. 두 메시지 유형의 payload 최대 크기는 4096바이트지만 콘솔 전송은 별도 제한이 있다.

APNs는 best-effort 서비스다. 알림이 지연·재정렬·throttle되거나 전달되지 않을 수 있다. 특히 iOS background notification은 낮은 우선순위이며 전달이 보장되지 않는다.

따라서 푸시 payload 하나만으로 주문·결제 같은 상태를 확정하지 않는다.

푸시: “주문 상태가 바뀌었습니다.”
   ↓ 사용자가 앱을 열거나 background refresh 허용
API: 현재 주문 상태를 다시 조회

민감정보도 payload에 그대로 넣지 않는다. FCM 문서는 전송 연결이 암호화되지만 end-to-end encryption은 아니라고 명시한다. 최소 식별자와 이벤트 힌트만 보내고 권한이 적용된 API에서 최신 상태를 읽는 편이 안전하다.

어떤 방식도 재연결 설계를 대신하지 않는다

실시간 기능의 품질은 정상 연결보다 끊어진 뒤 드러난다. 공통으로 다음 필드를 두면 복구가 쉬워진다.

{
  "eventId": "1042",
  "type": "job.status.changed",
  "occurredAt": "2026-08-02T01:20:00Z",
  "resourceId": "job-42",
  "version": 7
}
  • eventId: 중복 제거와 이어받기
  • occurredAt: 관측과 지연 분석
  • resourceId: 최신 상태 재조회
  • version: 오래된 이벤트의 덮어쓰기 방지

메시지 전달과 데이터 정합성을 분리하는 것이 핵심이다. 이벤트를 놓칠 수 있다는 전제로 API 재조회나 replay 경로를 마련한다.

최종 선택 순서

  1. 앱이 닫힌 사용자에게 알려야 하면 APNs·FCM을 별도 채널로 둔다.
  2. 앱이 열린 동안 지연 허용치가 크면 폴링부터 검토한다.
  3. 서버 → 브라우저 단방향이면 SSE를 검토한다.
  4. 진짜 양방향 이벤트가 계속 필요하면 WebSocket을 검토한다.
  5. 어떤 선택이든 timeout, 재연결, 누락·중복과 관측 지표를 먼저 설계한다.

REST·GraphQL·gRPC 같은 API 계약 방식과의 관계는 API 통신 방식 고르는 기준, TCP 연결 자체의 혼잡과 윈도우는 TCP 혼잡 제어 동작 순서에서 이어서 볼 수 있다.

참고 자료

반응형
KEEP READING
카테고리 전체 보기 →

댓글