요청에 바로 답하지 않고 사건이 생길 때까지 붙잡아 두는 방식.
-
짧은 폴링 — 클라이언트: 새 거 있어? → 서버: 없어 (1초 뒤 반복)
- 대부분의 요청이 헛수고다. 지연은 폴링 주기만큼 생긴다
-
**롱 폴링 — 클라이언트: 새 거 있어?**
- 서버: (답을 미룬다 …… 사건 발생) → 응답
- 클라이언트: 받자마자 다시 요청
-
헛된 왕복이 사라지고 지연이 사건 발생 시점까지 줄어든다
왜 완전한 해법이 아닌가
- 사건 하나에 요청·응답 한 쌍이 든다 (헤더·쿠키 오버헤드가 매번 붙는다)
- 응답과 다음 요청 사이의 짧은 공백에 생긴 사건은 놓칠 수 있다
- 서버가 커서(마지막 이벤트 id)를 받아 그 이후를 돌려주는 설계가 필요하다
- 대기 중인 요청이 연결·스레드를 점유한다
- 블로킹 서버 모델에서는 동시 사용자 수만큼 스레드가 묶인다
- (비동기 서블릿·이벤트 루프·가상 스레드로 완화한다)
- 블로킹 서버 모델에서는 동시 사용자 수만큼 스레드가 묶인다
- 프록시·LB 의 타임아웃보다 오래 붙잡으면 502 가 난다
- 보통 25~50초에서 스스로 끊고 빈 응답을 준 뒤 다시 받는다
지금도 남아 있는 자리
-
WebSocket·SSE 가 막히는 환경의 폴백
- (오래된 기업 프록시가 Upgrade 를 끊거나 스트리밍을 버퍼링하는 경우)
-
장기 실행 작업의 완료 대기 — 결제 승인 · 리포트 생성
-
서버 간 통신에서 큐 소비 (예: 메시지 대기 API)
-
SockJS 같은 폴백 계층이 WebSocket → SSE → 롱 폴링 순으로 내려간다
사건 유실을 막는 커서 설계
GET /events?since=42
- 43번 이후가 이미 있으면 즉시 응답
- 없으면 생길 때까지 대기
'대기' 와 '밀린 것 전달' 을 같은 엔드포인트가 처리해야 재연결 공백에서 사건이 새지 않는다
이 커서 개념은 SSE 의 Last-Event-ID 와 정확히 같은 문제를 푼다
세 방식의 비용 비교
갱신이 초당 1회 · 접속자 1만 명 기준 (헤더 500B 가정)
-
짧은 폴링(1초) — 1만 요청/초 × 500B ≈ 5MB/s — 대부분 빈 응답
-
롱 폴링 사건 수만큼 요청. 사건이 잦으면 짧은 폴링과 비슷해진다
-
WebSocket — 프레임 헤더 2~14B. 같은 조건에서 두 자릿수 배 차이
-
사건이 드물면 롱 폴링도 충분하다. 잦아질수록 격차가 벌어진다
면접 함정
- ❌ "롱 폴링은 실시간이 아니다" → 지연은 충분히 낮다. 문제는 연결·헤더 비용이다.
- ❌ "WebSocket이 있으니 이제 안 쓴다" → 폴백과 장기 작업 대기에서는 지금도 현역이다.