디이프는 데이터 기반으로 건강한 삶에 기여하기 위해 다양한 서비스를 개발 및 제공하고 있습니다. 이 과정에서, 최근에는 SSE(Server-Sent Events) 기술을 많이 활용하고 있는데요, SSE가 어떻게 활용되게 되었는지와 SSE에 대한 내용을 간단히 공유드리겠습니다.
최근 디이프는 AI 식단 생성 모델을 기반으로 사용자 맞춤형 식단을 제공하는 서비스 개발기간이 있었습니다. 개발 내용 중에, 데이터의 생성 및 상태변경을 사용자가 알 수 있도록 아이콘을 변경해야 하는 동작이 있었는데, 이 부분을 구현하는 과정에서 시행착오가 있었습니다.
처음에는 최대한 간단하게 구현하고자, 페이지 새로고침 방식에 의존하여 상태 값을 응답으로 전송하는 방안을 선택했습니다. 그러나 사용자들로부터 지속적으로 새로고침을 해야만 상태 변화를 확인할 수 있다는 불편함을 피드백으로 받게 되었고, 이를 해결하기 위한 방법이 필요했습니다. 서비스 페이지에서 실시간으로 상태 변화를 전달해야 하는 상황에서, 웹소켓을 사용해 볼까 하는 고민도 했지만, 이 경우에는 추가적인 연결 설정과 복잡한 처리로 인해 부담이 되었습니다. 그래서 간단하게 데이터를 전달할 수 있는 방법을 찾던 중, SSE가 적합하다는 결론에 도달했습니다.
SSE(Server-Sent Events)는 HTTP를 기반으로 동작하는 실시간 통신 기술입니다.
우리가 평소 http://www.naver.com와 같은 주소를 접근할 때, www.naver.com이라는 주소가 가진 데이터 정보의 교환은 HTTP라는 통신 규약대로 처리를 하는데, 이렇게 이미 오랜 시간 익숙하게 사용해 온 HTTP는 많은 환경 호환성과 안정성, 확정성을 보장하고 있습니다. 따라서 이에 기반한 SSE는 별도의 복잡한 설정 없이 동작이 가능한 특징을 가졌습니다.
그럼 이러한 SSE는 어떻게 동작할까요?
일반적으로 HTTP 방식에서는 클라이언트가 요청을 보내고 서버가 응답을 보내면 연결이 끊어집니다. 즉, 처음 새로고침에 의지한 방식은 클라이언트의 GET 요청에 따라 서버로부터 응답을 받고 연결을 끊는 기본 요청/응답 형식이었죠. 그러나 SSE는 HTTP와 동일한 방식으로 서버에 연결하되, 이를 유지합니다. 따라서 클라이언트는 한 번의 연결 이후 대기 상태를 유지하며, 서버는 이 연결을 통해 클라이언트의 요청 없이도 데이터를 언제든지 전송할 수 있는 구조가 되는 것이죠.
이와 같은 SSE 연결은 기존 HTTP 헤더에 일부 설정을 추가함으로써 간단히 구현 가능합니다.
클라이언트 > 서버
- Accept: text/event-stream
- 서버에게 이벤트 스트림 형식으로 데이터를 지속적으로 수신할 준비가 되었음을 알림
서버 > 클라이언트
- Content-Type: text/event-stream
- 클라이언트에게 이벤트 스트림 형식으로 데이터를 전송함을 의미
- Connection: keep-alive
- 연결을 끊지 않고 계속 유지하도록 요청
- Cache-Control: no-cache
- 데이터를 캐시하지 않고 실시간으로 새로운 데이터를 전송하도록 지시
위와 같은 헤더 설정을 통해 초기 연결을 설정하면, 이후에는 반복적인 GET 요청/응답을 처리할 필요가 없어 네트워크 부담이 줄어듭니다. 또한, 연결 문제 발생 시 클라이언트에서 자동으로 재연결을 시도하는 기능이 내장되어 있어, 별도의 복잡한 처리 없이도 연결이 복구됩니다. 이로 인해 SSE는 실시간 단방향 통신을 간편하게 구현할 수 있는 장점이 있습니다.
하지만 SSE는 모든 상황에 적합한 해결책이 아닙니다.
1. 브로드캐스트 방식으로 불필요한 데이터 전송
SSE는 단방향 통신 구조로, 서버에서 모든 클라이언트에게 동일한 데이터를 전송하는 방식으로 동작합니다. 이로 인해 클라이언트가 필요로 하지 않는 데이터를 수신하게 되는 경우가 발생하며, 결과적으로 네트워크 대역폭이 불필요하게 소모될 수 있습니다. 따라서 클라이언트별 데이터 전송이 요구되는 서비스에서는 별도의 처리가 있어야 하며, 비효율적일 수 있습니다.
2. 다중 사용자 서비스에서의 제약
SSE는 HTTP 기반의 지속적인 연결을 유지하며 데이터를 전송하기 때문에 클라이언트 수가 증가함에 따라 서버가 관리해야 하는 자원(예: 동시 연결 수, 파일 디스크립터, 메모리 등)의 소모가 기하급수적으로 늘어납니다. 이로 인해 서버 자원이 고갈될 위험이 있으며, 자원 부족 상황에서는 서버의 성능 저하나 서비스 불안정이 발생할 수 있습니다. 따라서 추가적인 채널 관리(예: 사용자 그룹별 데이터 분리)나 로드밸런싱 구성이 없다면 단일 서버로는 높은 동시 접속 트래픽을 효과적으로 처리하기 어렵기 때문에 서비스의 확장성과 안정성이 제한될 수 있습니다.
지속적인 연결을 지원하는 방법으로 WebSocket을 사용할 수도 있습니다. Websocket은 클라이언트가 HTTP 요청에 Upgrade 헤더를 포함하여 서버에게 WebSocket연결을 요청하고, 서버가 이를 수락하면 HTTP에서 웹소켓 프로토콜로(ws://, wss://) 변경되어 메시지(데이터)를 주고받게 됩니다.
서버가 클라이언트에게 데이터를 전송할 때, 특정 클라이언트의 소켓을 대상으로 데이터를 보내므로, 단일 클라이언트와의 통신이 기본입니다. 물론 서버 설정과 서비스 동작 단의 추가적인 구현을 통해 모든 클라이언트에게도 동일한 메시지를 전송할 수 있습니다. 그리고 텍스트 데이터 중심으로만 전송되는 SSE와는 다르게, 하나의 연결에서 여러 데이터 스트림을 다룰 수 있는 멀티플렉싱을 지원해서, 텍스트, 이미지, 오디오, 파일 데이터 등을 효율적으로 전송할 수 있습니다.
하지만 서버가 많은 클라이언트를 동시에 관리해야 할 때, 클라이언트별 고유 소켓을 유지해야 하므로 SSE보다 자원 소모가 더 크며, 별도의 프로토콜애서 동작하다 보니 상태관리, 오류처리 및 재연결 로직 등을 추가로 구현해야 한다는 특징이 있습니다.
이렇게 sse와 websocket에 대해 간단히 알아보았을 때, 실시간 통신을 지원한다는 점과 한 번의 연결로 데이터 통신이 이루어지는 방식은 동일하나 Websocket은 독립적인 프로토콜을 사용하여 데이터를 주고받는 차이점이 크게 다가왔습니다. 추가적인 설정이 필요할 수 있다는 점이 빠르게 동작 구현을 해야 하는 상황에 알맞지 않음을 느꼈고, 무엇보다 사용자의 로컬에 셋팅되어 일부 사용자만 접근하는 서비스였고, 간단한 텍스트 데이터를 클라이언트가 얻기만 하면 되었기에, SSE를 적용하는 것이 더 적합하다고 판단이 되었습니다.
SSE를 적용한 이후, 비슷한 서비스 동작을 처리하는 데 필요한 개발 부담이 크게 줄어들었고, 특히 화면 기획이 유연해져서, 서비스 개선과 변경이 보다 용이해졌습니다.
이 경험을 통해 기술 선택이 얼마나 중요한지, 그리고 그 선택이 이후 개발 과정에 얼마나 큰 영향을 미치는지 실감하게 되었습니다. 비록 새로운 방식이나 기술을 도입하는 데에 시간이 부족하고 여유가 없을 수 있지만, 이후 느낄 수 있는 편리함과 효율이 있음을 알았기에, 앞으로도 꾸준히 새로운 기술을 시도하고 적용해 보며, 더 나은 서비스와 경험을 제공할 수 있도록 노력할 것입니다.
https://matt1235.tistory.com/79
https://velog.io/@yuna706/SSE-%ED%86%B5%EC%8B%A0webSocket%EA%B3%BC-%EB%B9%84%EA%B5%90
https://bunny.net/academy/http/what-is-sse-server-sent-events-and-how-do-they-work/
https://velog.io/@black_han26/SSE-Server-Sent-Events
https://yeo-computerclass.tistory.com/480
https://yeo-computerclass.tistory.com/480