실시간 모니터링 대시보드를 폴링 대신 SSE로 만들었다
Server-Sent Events로 서버 메트릭 실시간 전송
#SSE #실시간 #React #백엔드
서버 상태(CPU, 메모리, 디스크) 보는 모니터링 대시보드를 만들었다. 실시간이니까 데이터가 계속 갱신돼야 하는데, 처음엔 그냥 2초마다 API를 다시 부르는 폴링으로 했다. 되긴 됐는데, 네트워크 탭을 열어보니 같은 요청이 2초마다 끝없이 왕복하고 있었다. 좀 찜찜했다. 그래서 SSE(Server-Sent Events) 로 바꿨다. 이 글에선 왜 폴링을 버렸고 SSE로 어떻게 옮겼는지 정리해본다. 폴링의 아쉬운 점 폴링은 클라이언트가 2초마다 "데이터 줘", "데이터 줘" 하고 계속 물어보는 방식이다. 단순하지만 단점이 있다. 1. 데이터가 안 바뀌었어도 일단 요청을 보낸다 (낭비) 2. 요청 간격만큼 항상 지연이 있다 (실시간이라기엔 살짝 늦음) 3. 매번 새 HTTP 요청이라 헤더 같은 오버헤드가 반복된다 서버 상태 보는 화면이라 한 번 켜두면 계속 떠 있는데, 그동안 쉼 없이 요청이 왕복하는 게 마음에 안 들었다. SSE는 서버가 먼저 보낸다 SSE는 방향이 반대다. 클라이언트가 한 번 연결을 열어두면, 서버가 갱신될 때마다 알아서 데이터를 밀어 넣는다 . 클라이언트는 계속 물어볼 필요 없이 가만히 받기만 하면 된다. 웹소켓보다 가볍고, 서버→클라이언트 단방향이면 SSE로 충분하다. 백엔드는 응답 헤더를 text/event-stream으로 열어두고, 연결을 끊지 않은 채로 주기적으로 데이터를 write한다. router.get('/stream', (req, res) = { // 토큰 검증 (SSE는 헤더를 못 실어서 쿼리로 토큰을 받음) try { jwt.verify(req.query.token, JWT_SECRET); } catch { return res.status(401).end(); } res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', 'X-Accel-Buffering': 'no', }); res.flushHeaders(); async function send() { const data = await collectMetrics(); res.write(`data: ${JSON.stringify(data)}\n\n`); } send(); // 연결되자마자 한 번 const interval = setInterval(send, 2000); // 이후 2초마다 req.on('close', () = clearInterval(interval)); // 끊기면 정리 }); 여기서 꼭 챙겨야 하는 게 req.on('close') 다. 클라이언트가 탭을 닫으면 setInterval을 멈춰줘야 한다. 안 그러면 끊긴 연결에 계속 데이터를 쏘는 좀비 타이머가 쌓인다. 이거 빼먹으면 서버 메모리가 슬금슬금 샌다. 프론트는 EventSource 하나면 끝 브라우저엔 SSE 받는 EventSource 객체가 내장돼 있다. 따로 라이브러리도 필요 없다. 연결하고, onmessage로 받고, 언마운트될 때 close. 이게 전부다. export function useSSE(path, onData) { useEffect(() = { const token = localStorage.getItem('monitor_token'); const es = new EventSource(`${path}?token=${token}`); es.onmessage = (e) = onData(JSON.parse(e.data)); es.onerror = () = { /* 끊김 처리 */ }; return () = es.close(); // 정리 }, [path]); } 이렇게 훅으로 감싸두니까, 대시보드 컴포넌트에선 useSSE만 부르면 데이터가 알아서 흘러들어온다. 폴링 코드보다 오히려 깔끔해졌다. 정리하면 실시간이라고 무조건 웹소켓을 떠올릴 필요는 없다. 서버가 일방적으로 밀어주기만 하면 되는 상황(모니터링, 알림, 진행률)이면 SSE가 더 간단하고 가볍다. 브라우저 기본 기능만으로 되고, 그냥 HTTP라서 프록시랑도 잘 맞는다. 폴링에서 SSE로 바꾸고 나니 네트워크 탭에서 그 끝없던 요청들이 사라지고, 화면 갱신도 더 즉각적이 됐다. 도구를 고를 때 "통신이 어느 방향이냐"만 먼저 따져봐도 적절한 선택이 보인다는 걸 배운 작업이었다.