채팅이나 대시보드 같이 웹에서 서버와 클라이언트가 실시간으로 데이터를 주고받아야 하는 서비스를 구현할 때, 어떤 기술을 선택하면 좋을지 정리해보고자 한다!
클라이언트가 일정 주기로 서버에 요청을 보내 데이터를 가져오는 방식
클라이언트 → 서버: 새 데이터 있나요?
서버 → 클라이언트: 없음
(3초 후)
클라이언트 → 서버: 새 데이터 있나요?
서버 → 클라이언트: 없음
(3초 후)
클라이언트 → 서버: 새 데이터 있나요?
서버 → 클라이언트: 있음!
구현 코드 예시
setInterval 같은 함수를 사용해 클라이언트가 정해진 간격으로 서버에 HTTP 요청을 반복해서 보낸다.
setInterval(async () => {
const res = await fetch('/api/status');
const data = await res.json();
setState(data);
}, 5000); // 5초마다 요청
Polling의 단점을 보완한 방식
클라이언트가 요청을 보내면 서버는 변경 사항이 생길 때까지 응답을 보류하다가(연결은 유지), 변경이 발생하면 그때 응답한다
클라이언트 → 서버: 새 데이터 있나요?
서버: 기다리는 중...
서버 → 클라이언트: 있음!
클라이언트 → 서버: 새 데이터 있나요?
서버: 기다리는 중...
구현 코드 예시
async function longPoll() {
const response = await fetch('/api/updates?timeout=30000'); // 서버가 변경 시까지 응답 대기
const data = await response.json();
handleUpdates(data);
longPoll(); // 응답 받으면 즉시 다음 요청
}
Server Sent Events
서버에서 클라이언트로만 데이터를 전송하는 단방향 통신 방식
브라우저에 내장된 EventSource API를 통해 클라이언트가 서버의 이벤트를 구독하면, 서버는 연결을 유지한 채 필요한 시점에 이벤트를 지속적으로 전송해준다
브라우저에 내장된 EventSource을 이용하여 클라이언트가 서버의 이벤트를 구독한다는 요청을 보냄
클라이언트 → 서버: 연결 요청 (Accept: text/event-stream)
서버 → 클라이언트: 연결 유지... (Content-Type: text/event-stream)
서버 → 클라이언트: data: {"message": "새 알림"}
서버 → 클라이언트: data: {"message": "또 다른 알림"}
(연결 유지 중...)
구현 코드 예시
const es = new EventSource('/api/stream');
es.onmessage = (e) => {
const data = JSON.parse(e.data);
setState(data);
};
// 커스텀 이벤트 타입 구분
es.addEventListener('alert', (e) => {
triggerAlarm(JSON.parse(e.data));
});
es.addEventListener('status', (e) => {
updateStatus(JSON.parse(e.data));
});
SSE 프로토콜 형식
event: notification // 이벤트 타입
data: {"title": "새 메시지", "body": "안녕하세요"} // 데이터
id: 12345 // 이벤트 ID
retry: 3000 // 재연결 대기 시간(ms)
최초 HTTP 핸드셰이크 이후 지속 연결을 유지하며 서버와 클라이언트가 양방향으로 데이터를 주고받는 방식
클라이언트 → 서버: HTTP 업그레이드 요청
서버 → 클라이언트: 101 Switching Protocols
(이제부터 WebSocket 프로토콜)
클라이언트 ↔ 서버: 양방향 메시지 교환
101 Switching Protocol
클라이언트가 보낸 Upgrade 요청 헤더에 대한 응답이 들어가며 서버에서 프로토콜을 변경할 것임을 알려주는 상태 코드
해당 코드는 Websocket 프로토콜 전환 시에 사용된다
구현 코드 예시
const ws = new WebSocket('wss://example.com/ws');
// 수신
ws.onmessage = (e) => {
const data = JSON.parse(e.data);
setState(data);
};
// 송신
ws.send(JSON.stringify({ type: 'subscribe', id: 'room_01' }));
🔎 Socket.IO
WebSocket을 기반으로 한 라이브러리
WebSocket의 복잡한 구현을 쉽게 도와주는 라이브러리라고 보면 된다
일단 WebSocket 연결을 시도하고 불가능한 브라우저 환경이라면 자동으로 Long Polling 방식으로 연결한다
자동 재연결, 이벤트 기반 API 등의 편의 기능을 제공한다
구현이 편리하지만 라이브러리 의존성이 생기고 순수 WebSocket보다 약간 느리다.
const socket = io('https://example.com');
// 수신 (on)
socket.on('message', (data) => {
updateChat(data);
});
// 송신 (emit)
socket.emit('send', { text: '안녕하세요' });

단방향이면 SSE, 양방향이 필요하면 WebSocket이 기본 기준
실시간 서비스에서는 이벤트가 매우 빠르게 연속으로 발생하게 되는데, 이를 그대로 처리하면 불필요한 API 호출과 렌더링이 과도하게 늘어날 수도 있다
따라서 이를 제어하고 성능을 높이기 위해 Debounce나 Throttle를 고려해볼 수 있음
이벤트가 발생할 때마다 기존 타이머를 초기화하며, 호출이 멈춘 후 지정한 시간이 지나면 마지막 이벤트를 기준으로 함수를 실행
즉, 순차적 호출을 하나의 그룹으로 묶어서 처리하는 느낌
불필요한 중간 요청을 제거할 수 있음
단, 실행이 지연되므로 연속 스트림에는 부적합
스로틀링은 출력을 조절한다는 의미로 이벤트를 일정 시간 간격으로 최대 한 번만 실행되도록 제한
예를 들어 소켓 설정하면서 Throttle 의 설정시간으로 100ms 를 주게되면
소켓 메시지가 10ms마다 와도 실제 차트 업데이트는 100ms에 한 번만 발생
따라서, 실시간 데이터에서 렌더링 빈도를 제어하여 성능을 높일 수 있다
단 중간 데이터는 무시될 수도 있음
계속 오는 데이터의 처리빈도를 제한하여 성능을 올릴 때 적합 (실시간 차트, 소켓 메시지, 스크롤 이벤트 등)
이벤트 발생: ─▲──▲─▲──▲▲▲──▲──▲─▲─▲──▲
Debounce: ────────────────────────────▲
(마지막 이벤트 후 delay 지나면 실행)
Throttle: ─▲────────▲────────▲────────▲
(일정 간격으로 한 번씩만 실행)
[Network] 실시간 통신 기술 (폴링, 롱폴링, 웹소켓, SSE)
실시간 통신 기술 비교 (Polling, Webhook, SSE, WebSocket)
백엔드 통신 패턴 총정리 — Short Polling, Long Polling, SSE, Pub/Sub
SSE, Polling, WebSocket 비교 학습 노트
실시간 통신 기술 비교하기 / SSE 알림 기능 구현하기
Debounce 와 throttle 은 뭐고 각각 언제 사용할까?
디바운스(Debounce)와 스로틀(Throttle ) 그리고 차이점
Debounce와 Throttle: 직접 실행하며 알아보기