모아위키
Published on

SSE란? WebSocket·폴링 차이와 EventSource 사용법

SSE(Server-Sent Events)는 서버가 HTTP 연결을 통해 브라우저로 텍스트 이벤트를 계속 보내는 방식입니다. 작업 진행률이나 알림처럼 서버에서 오는 업데이트를 받는 데 사용할 수 있습니다. 브라우저에서는 EventSource로 수신하며, 클라이언트가 작업을 요청하는 API는 별도로 둡니다.

SSE·WebSocket·폴링, 언제 선택할까?

구분데이터 전달고려할 상황설계할 비용
주기적 폴링클라이언트가 조회하고 서버가 응답갱신 빈도가 낮고 몇 초의 지연을 허용할 때조회 주기·반복 요청 수
SSE열린 HTTP 응답으로 서버가 이벤트 전송작업 진행률·알림 등 서버 측 갱신 수신연결 수·재연결·프록시 설정
WebSocket연결 후 양방향 메시지 교환빈번한 양방향 메시지가 필요한 경우연결 상태·재접속·메시지 처리

SSE가 항상 더 적은 자원을 쓰는 것은 아닙니다. 폴링 요청은 줄일 수 있지만 연결을 유지하는 비용이 있습니다. 동시 접속자 수, 이벤트 빈도, 배포 환경의 연결 제한을 함께 고려하세요.

이벤트 형식과 EventSource 예제

서버는 Content-Type: text/event-stream으로 UTF-8 텍스트를 보내고, 이벤트 끝에 빈 줄을 넣습니다. 아래는 작업 완료 이벤트 한 건의 응답 본문 예시입니다. 서버 구현 코드나 실제 작업 실행 결과는 아닙니다.

id: job-42-v3
event: job-status
data: {"jobId":"42","status":"completed"}

실제로 보낼 문자열의 마지막은 \n\n입니다. event를 지정했으므로 브라우저에서는 같은 이름의 이벤트를 구독합니다. 이름을 지정하지 않은 이벤트는 onmessage로 받을 수 있습니다.

// 같은 출처의 /events/stream이 SSE 응답을 제공한다고 가정합니다.
const stream = new EventSource('/events/stream');

stream.addEventListener('job-status', (event) => {
  try {
    const update = JSON.parse(event.data);
    if (update.jobId !== '42') return;
    console.log('작업 상태:', update.status);
    if (['completed', 'failed'].includes(update.status)) {
      stream.close(); // 이 예제는 작업 하나의 종료 후 구독을 끝냅니다.
    }
  } catch {
    console.error('이벤트 데이터 형식을 확인하세요.');
  }
});

stream.onerror = () => {
  console.warn('연결 오류: 서버 응답과 네트워크를 확인하세요.');
};

이 코드는 수신 측 예시이며 /events/stream 엔드포인트가 별도로 필요합니다. 운영 환경에서는 로그 대신 화면 상태를 갱신하고, 화면을 떠날 때도 close()로 구독을 정리하세요. 형식과 API 동작은 WHATWG HTML 표준과 MDN SSE 사용 안내를 참고했습니다.

재연결되면 놓친 이벤트도 돌아오나요?

자동 재연결과 누락 이벤트 복구는 다릅니다. 브라우저는 재연결 가능한 연결 종료 상황에서 다시 접속합니다. id를 받은 연결은 재연결 요청의 Last-Event-ID로 마지막 ID를 전달할 수 있지만, 서버가 이벤트를 보관하고 이어 보내는 기능은 직접 구현해야 합니다. close()를 호출하면 해당 구독의 재접속을 중단합니다.

작업 상태를 다룬다면 다음을 함께 설계하세요.

  • 상태 조회 API를 기준으로 최신 상태를 다시 확인하는 복구 경로
  • 이벤트 ID 또는 상태 버전으로 중복 이벤트를 구분하는 처리
  • 재연결·최초 구독 사이에 작업이 끝나도 놓치지 않도록 최신 상태를 제공하는 절차

연결은 됐는데 이벤트가 늦게 도착한다면

  1. 응답의 Content-Type과 이벤트 뒤 빈 줄을 확인합니다.
  2. 서버에서 스트림을 즉시 내보내는지, 프록시·CDN이 응답을 모아서 보내는지 확인합니다.
  3. 배포 환경의 최대 요청 시간과 유휴 연결 제한을 확인합니다.
  4. 필요하면 : keep-alive\n\n 같은 주석 메시지를 주기적으로 보내 유휴 연결을 관리합니다. 이것만으로 모든 시간 제한이 해결되지는 않습니다.

MDN 안내에도 주석 메시지와 연결 제한이 설명되어 있습니다. 아래에서는 이러한 특성을 비동기 작업 완료 알림 구조에 적용합니다.

비동기 작업 상태 전달 구조

1. 왜 SSE를 도입하려고 했는가?

백엔드에서 시간이 오래 걸리는 비동기 작업을 처리할 때, 클라이언트가 해당 작업의 완료 시점을 알아야 하는 요구사항이 있었다.

일반적으로 비동기 작업의 흐름은 다음과 같다.

  1. 클라이언트가 작업 요청
  2. 서버에서 비동기 작업 수행
  3. 작업 완료 시점에 결과 상태 변경
  4. 클라이언트는 완료 여부를 확인해야 함

이때 가장 먼저 떠올릴 수 있는 방법은 클라이언트가 일정 주기로 상태 조회 API를 호출하는 폴링(Polling) 방식이다.

하지만 이 방식은 작업이 완료되지 않았더라도 불필요한 요청이 반복적으로 발생한다는 단점이 있다.

이 문제를 해결하기 위해 서버가 작업 완료 시점에 직접 이벤트를 전달하는 구조를 고민하게 되었고, 그 대안으로 SSE(Server-Sent Events)를 설계하게 되었다.


2. SSE(Server-Sent Events)란 무엇인가?

SSE(Server-Sent Events)는 서버에서 클라이언트로 지속적으로 이벤트를 스트리밍 방식으로 전달하는 기술이다.

클라이언트는 한 번 연결을 맺으면 서버에서 이벤트가 발생할 때마다 데이터를 실시간으로 수신할 수 있다.

즉, 다음과 같은 특징을 가진다.

  • HTTP 기반 스트리밍 통신
  • 서버 → 클라이언트 단방향 이벤트 전달
  • 연결을 유지하면서 이벤트를 순차적으로 수신
  • 작업 상태 변경 알림에 적합

특히 "비동기 작업 완료 알림"과 같은 시나리오에서 매우 유용하다.


3. 전체 구조 개요

이번에 설계한 구조의 핵심 흐름은 다음과 같다.

Client → Backend → 비동기 작업 수행
   ↓
SSE 연결 유지
   ↓
작업 완료 이벤트 발생 → Client에 전달

즉,

  • 클라이언트는 서버와 SSE 연결을 유지하고
  • 서버는 비동기 작업 상태를 관리하며
  • 작업 완료 시점에 이벤트를 즉시 전달하는 구조다.

4. 폴링(Polling) 대신 SSE를 선택한 이유

4.1 폴링 방식의 구조

가장 단순한 구조는 다음과 같다.

Client → 일정 주기로 상태 조회 API 호출
Server → 현재 작업 상태 반환

이 방식은 구현이 쉽지만 다음과 같은 문제가 있다.

  • 작업이 완료되지 않아도 계속 요청 발생
  • 서버 부하 증가
  • 완료 시점에 즉시 반영되지 않을 수 있음

특히 작업 시간이 일정하지 않은 경우, 폴링 주기를 짧게 하면 부하가 커지고 길게 하면 반영이 늦어지는 문제가 생긴다.

4.2 SSE 방식의 구조

SSE를 사용하면 흐름이 단순해진다.

Client → SSE 연결 요청
Server → 작업 완료 시점에 이벤트 전송

즉, 필요할 때만 이벤트가 전달되므로 불필요한 반복 요청을 줄일 수 있다.


5. SSE 기반 상태 전달 흐름

5.1 클라이언트 SSE 연결

클라이언트는 특정 리소스에 대해 SSE 연결을 맺는다.

Client → /events/stream 연결
Server → 스트림 연결 유지

이 연결은 작업 상태가 변경될 때까지 유지된다.

5.2 비동기 작업 수행

클라이언트가 작업을 요청하면 서버는 해당 작업을 비동기로 수행한다.

Client → 작업 요청
Backend → 비동기 작업 시작

이때 작업 수행 자체는 일반적인 API 호출과 동일하게 처리된다.

5.3 작업 완료 이벤트 전달

비동기 작업이 완료되면 서버는 연결되어 있는 SSE 스트림으로 이벤트를 전송한다.

비동기 작업 완료
→ 서버에서 이벤트 생성
→ SSE 스트림으로 Client에 전달

이 구조를 통해 클라이언트는 별도의 새로고침 없이 즉시 상태 변경을 반영할 수 있다.


6. 장기 연결에서의 인증 고려 사항

SSE는 연결이 장시간 유지되는 특성이 있기 때문에 인증 토큰 만료에 대한 설계가 중요하다.

특히 다음과 같은 상황을 고려해야 한다.

  • SSE 연결 중 Access Token 만료
  • 토큰 재발급 이후 연결 유지 전략
  • 쿠키 기반 인증 vs 헤더 기반 인증 선택

따라서 SSE 설계는 단순한 스트리밍 통신 문제가 아니라, 인증 구조와도 밀접하게 연결된 설계 요소라고 볼 수 있다.

기본 EventSource 생성자는 임의의 Authorization 헤더를 지정하는 옵션을 제공하지 않습니다. 헤더 인증이 필요하다면 스트리밍 fetch 방식이나 검토한 라이브러리 등 별도 구현이 필요합니다. 쿠키 인증의 다른 출처 요청은 withCredentials와 서버의 CORS 설정을 함께 검토하세요. 토큰을 URL에 넣으면 로그 등에 남을 수 있으므로 단순한 우회 방법으로 사용하지 마세요.


7. WebSocket 대신 SSE를 선택한 이유

실시간 통신이라고 해서 항상 WebSocket이 필요한 것은 아니다. 이번 구조에서는 서버에서 클라이언트로 상태 변경 알림만 전달하면 충분했다.

즉, 양방향 통신이 필수적인 상황은 아니었다.

이 경우 SSE의 장점은 다음과 같다.

  • HTTP 기반이라 인프라 구성이 단순함
  • 단방향 이벤트 전달에 적합
  • 구현 난이도가 낮음
  • HTTP 기반 인프라를 활용할 수 있으나 프록시 버퍼링·타임아웃 설정 확인 필요

따라서 단순 상태 알림 목적이라면 WebSocket보다 SSE가 더 적합한 선택이 될 수 있다.


8. SSE 설계 시 고려해야 할 핵심 포인트

이번 구조를 설계하면서 다음과 같은 요소를 중점적으로 고려했다.

  • 비동기 작업 완료 시점 이벤트 전달 방식
  • 서버 내부 작업 상태와 SSE 스트림 연동 방법
  • 장기 연결 환경에서의 인증 토큰 만료 처리
  • 폴링 방식 대비 서버 부하 감소 효과

특히 "언제 이벤트를 발생시킬 것인가"가 중요한 설계 포인트였다. 작업 완료 시점에 정확하게 이벤트를 전달해야 클라이언트가 불필요한 재요청 없이 상태를 갱신할 수 있기 때문이다.


9. 정리

SSE(Server-Sent Events)는 단순한 실시간 통신 기술이 아니라, 백엔드에서 비동기 작업 상태를 효율적으로 전달하기 위한 설계 도구라고 볼 수 있다.

SSE를 도입함으로써 다음과 같은 장점을 얻을 수 있다.

  • 불필요한 폴링 요청 감소
  • 작업 완료 시점 즉시 반영
  • 사용자 경험 개선
  • 갱신 빈도와 연결 비용에 맞춘 자원 사용 설계

특히 비동기 작업 완료 알림과 같은 시나리오에서는 SSE가 WebSocket보다 단순하고 적합한 선택이 될 수 있다.


10. 결론

비동기 작업의 완료 시점을 클라이언트에 전달해야 하는 상황에서는 주기적으로 상태를 조회하는 구조보다 서버가 이벤트를 직접 전달하는 구조를 검토할 수 있다. 실제 효율은 이벤트 빈도와 연결 유지 비용에 따라 달라진다.

이러한 요구사항을 충족하기 위해 SSE(Server-Sent Events)를 활용한 백엔드 중심의 상태 전달 구조를 설계할 수 있다.

이 구조는 인증 토큰 재발급 흐름과 결합하여 장기 연결 환경에서도 안정적으로 확장할 수 있다.

참고 문서

공식 문서 확인 및 예제 보완: 2026년 9월 26일. 예제는 통신 형식과 수신 흐름 설명용이며, 인증·이벤트 보관·운영 환경은 별도로 구현해야 합니다.