개발 고민

[개발 고민] webSocket, SSE(Server Sent Event) 중 뭐가 좋을까?

woopii 2026. 1. 18. 20:00

개인 프로젝트에서 비동기로 시간이 걸리는 작업을 진행중일때, 

진행 상황을 클라이언트에게 실시간으로 알려주는 기능이 필요했음.
초기에는 WebSocket을 고려했으나, SSE(Server-Sent Events)로 방향을 전환하게 됨

 

1. WebSocket이란?

간단 설명

양방향 실시간 통신 프로토콜.

클라이언트와 서버가 서로 자유롭게 메시지를 주고받을 수 있음.

동작 방식

1. 클라이언트 → 서버: WebSocket 연결 요청 (HTTP Handshake)
2. 서버 → 클라이언트: 연결 승인 (프로토콜 업그레이드)
3. ↔ 양방향 통신: 클라이언트 ↔ 서버 자유롭게 메시지 전송
4. 연결 종료: 어느 쪽에서든 종료 가능

특징

  • 양방향 통신: 서버 → 클라이언트, 클라이언트 → 서버 모두 가능
  • 프로토콜: ws:// 또는 wss:// (별도 프로토콜)
  • 연결 유지: 지속적인 TCP 연결 유지
  • 복잡도: 설정 및 구현이 상대적으로 복잡

적합한 사용 케이스

  • 채팅 애플리케이션
  • 실시간 게임
  • 협업 도구 (Google Docs 등)
  • 양방향 통신이 필요한 모든 경우

 

2. SSE(Server-Sent Events)란?

간단 설명

서버에서 클라이언트로 단방향 실시간 데이터 전송을 위한 기술.

HTTP 기반으로 동작.

 

동작 방식

1. 클라이언트 → 서버: HTTP 요청
2. 서버: HTTP 응답 시작 (연결 유지)
3. 서버 → 클라이언트: 이벤트 발생 시마다 데이터 전송
4. 연결 종료 또는 타임아웃 시 종료
5. (선택) 자동 재연결

특징

  • 단방향 통신: 서버 → 클라이언트만 가능
  • 프로토콜: HTTP/HTTPS (기존 인프라 활용)
  • 자동 재연결: 브라우저가 자동으로 재연결 시도
  • 간단함: 일반 HTTP 엔드포인트처럼 구현

적합한 사용 케이스

  • 진행 상황 알림
  • 실시간 피드/뉴스
  • 주식/환율 실시간 업데이트
  • 서버 모니터링 대시보드
  • 서버 → 클라이언트 단방향 알림

 

3. 프로젝트에서 기능 선택

필요 기능

필요한 통신 흐름:

서버 → 클라이언트: "PDF 파싱 중..."
서버 → 클라이언트: "AI 분석 중..."
서버 → 클라이언트: "후처리 중..."
서버 → 클라이언트: "완료!"

클라이언트 → 서버로 보낼 데이터가 없음.

 

WebSocket을 선택하지 않은 이유

1. 과도한 기능

// WebSocket은 양방향인데, 우리는 한 방향만 사용
messagingTemplate.convertAndSend("/topic/analysis/$jobId", payload)  // 서버 → 클라이언트만 사용
// 클라이언트 → 서버는 전혀 사용하지 않음

 

2. 복잡한 설정

@Configuration
@EnableWebSocketMessageBroker
class WebSocketConfig : WebSocketMessageBrokerConfigurer {
    override fun configureMessageBroker(registry: MessageBrokerRegistry) {
        registry.enableSimpleBroker("/topic")
        registry.setApplicationDestinationPrefixes("/app")
    }
    
    override fun registerStompEndpoints(registry: StompEndpointRegistry) {
        registry.addEndpoint("/ws").setAllowedOrigins("*")
    }
}

 

3. 별도 프로토콜

  • ws:// 프로토콜 사용
  • 방화벽/프록시 이슈 가능성
  • CORS 설정 추가 필요

SSE를 선택한 이유

1. 요구사항에 딱 맞음

// 단방향 진행 상황 알림만 필요
override fun notifyStep(jobId: String, status: JobStatus, step: AnalysisStep?, message: String) {
    emitters[jobId]?.send(SseEmitter.event().data(payload))
}

 

2. 간단한 구현

@GetMapping("/stream", produces = [MediaType.TEXT_EVENT_STREAM_VALUE])
fun streamProgress(@PathVariable jobId: String): SseEmitter {
    val emitter = SseEmitter(300_000L)
    emitters[jobId] = emitter
    return emitter
}

별도 설정 파일이나 복잡한 구성 불필요

 

3. HTTP 기반

  • 기존 HTTP 인프라 활용
  • HTTPS로 자동 암호화
  • 별도 포트나 프로토콜 불필요

4. 자동 재연결

javascript
// 브라우저가 자동으로 재연결 시도
const eventSource = new EventSource('/api/sample/analysis/${jobId}/stream');
// 연결 끊기면 자동으로 재시도

4. 성능 및 리소스

WebSocket

  • 지속적인 TCP 연결 유지
  • 양방향 채널 유지 (사용하지 않아도)
  • 메모리 사용량: 중간~높음

SSE

  • HTTP 연결 유지 (단방향)
  • 필요한 만큼만 리소스 사용
  • 메모리 사용량: 낮음~중간

 

5. 최종 결정

SSE를 선택한 이유 요약

  1. 요구사항 부합: 서버 → 클라이언트 단방향 통신만 필요
  2. 단순성: 복잡한 설정 없이 HTTP 엔드포인트처럼 구현
  3. 안정성: HTTP 기반으로 방화벽/프록시 이슈 최소화
  4. 자동 재연결: 브라우저 레벨에서 자동 처리
  5. 효율성: 필요한 기능만 사용, 리소스 절약

언제 WebSocket을 사용해야 하나?

  • 실시간 채팅 (사용자 ↔ 사용자)
  • 협업 도구 (동시 편집)
  • 게임 (빠른 양방향 통신)
  • 양방향 통신이 필수인 경우