개인 프로젝트에서 비동기로 시간이 걸리는 작업을 진행중일때,
진행 상황을 클라이언트에게 실시간으로 알려주는 기능이 필요했음.
초기에는 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를 선택한 이유 요약
- 요구사항 부합: 서버 → 클라이언트 단방향 통신만 필요
- 단순성: 복잡한 설정 없이 HTTP 엔드포인트처럼 구현
- 안정성: HTTP 기반으로 방화벽/프록시 이슈 최소화
- 자동 재연결: 브라우저 레벨에서 자동 처리
- 효율성: 필요한 기능만 사용, 리소스 절약
언제 WebSocket을 사용해야 하나?
- 실시간 채팅 (사용자 ↔ 사용자)
- 협업 도구 (동시 편집)
- 게임 (빠른 양방향 통신)
- 양방향 통신이 필수인 경우
'개발 고민' 카테고리의 다른 글
| [개발 고민]MySQL 조인 순서 최적화로 쿼리 속도 개선하기 (0) | 2026.01.16 |
|---|---|
| [개발일지_safeHome] 12. 등기부등본 분석 작업 관련 DB 설계 및 서비스 개발(임시) (0) | 2026.01.08 |
| [Java, Spring] Lombok @Data 쓰지 말아야 하는 이유 (0) | 2025.11.01 |
| 멀티테넌시 아키텍처 (0) | 2025.04.02 |
| SaaS와 멀티테넌시 (0) | 2025.04.02 |