사내에서 새로 추진되는 프로젝트에 플랫폼 엔지니어어로 참여하게 되었다. 프로젝트 아키텍처 다이어그램을 봤는데 처음 접하는 용어들이 몇몇 있었다. 그 중 중요도가 높은 SSE에 대해서 서술해보고자 한다.
클라이언트가 서버로 부터 데이터를 실시간으로 계속 받을 수 있는 기술이다. (단방향)
클라이언트측에서는 서버에서 보내주는 데이터를 받고 계속해서 업데이트해야되는 상황이 있다. 그럴 때마다 새로고침을 하거나, 서버로 request를 보내지않아도 된다는 장점을 가지고 있다.
HTTP 프로토콜을 기반으로 하여 동작하기 때문에, 기존의 웹 서버와 클라이언트 사이에서 사용되는 HTTP 통신을 이용하여 상대적으로 쉽게 구현할 수 있다.
SSE는 서버와 클라이언트 간의 실시간 통신을 위해 연결 상태를 유지하고 있다. 서버는 상황에 따라 여러 클라이언트와의 동시 연결을 관리하는데, 만약 서버 측에서 부하 및 장애가 발생하면 연결이 끊어질 수 있다. 이는 클라이언트가 서버로부터 데이터를 수신할 수 없는.. 유저에게 서비스가 올바르게 제공되지 못하는 심각한 상황에 이르게 된다.
SSE는 단방향 통신으로 클라이언트에서 서버로의 요청을 보낼 수 없어 클라이언트에서 발생한 문제나 에러를 서버로 전달하기 어렵다. 이는 클라이언트 측에서 발생한 장애가 서버로 전파되어 서버 측에서 처리하기 어려운 상황을 만들 수 있다. (이는 곧 서버의 부하증가로도 이어진다...)
이러한 이유로, SSE를 사용하는 시스템에서는 장애가 전파될 가능성을 고려하여 적절한 예외 처리와 오류 처리 메커니즘을 꼼꼼하게 구현해야한다. 이 말은.. 서비스가 추가될 때마다 항상 예외처리를 포함한 여러 코드가 추가되어 확장성에 한계를 가진다는 말이다..
import express from "express";
const app = express();
// SSE 연결을 위한 라우트 핸들러
app.get("/sse", (req, res) => {
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Connection", "keep-alive");
res.setHeader("Access-Control-Allow-Origin", "*");
// SSE 연결 유지를 위한 빈 데이터 전송
res.write(":\n\n");
// 일정 간격으로 데이터 전송
const interval = setInterval(() => {
const data = new Date().toISOString();
res.write(`data: ${data}\n\n`);
}, 1000);
// 클라이언트 연결 종료 시 SSE 연결 종료
req.on("close", () => {
clearInterval(interval);
res.end();
});
});
// 서버 실행
app.listen(3000, () => {
console.log("Server started on port 3000");
});
// SSE 연결을 수행하는 함수
function connectToSSE(): void {
const eventSource = new EventSource("/sse"); // SSE를 지원하는 엔드포인트 주소
// 이벤트 수신 시 처리할 로직
eventSource.addEventListener("message", (event: MessageEvent) => {
const data = event.data;
console.log("Received data:", data);
// 데이터를 받아 처리하는 로직을 작성합니다.
});
// SSE 연결 상태 처리
eventSource.onopen = () => {
console.log("SSE connection opened.");
};
eventSource.onerror = () => {
console.error("Error occurred in SSE connection.");
};
}
// SSE 연결 시작
connectToSSE();