폴링
2026.10.01 · 9분
1. 폴링이란?
폴링(Polling)은 클라이언트가 서버에 "새로운 거 있어요?"라고 정해진 간격마다 반복해서 물어보는 방식입니다.
클라이언트 ── 새 소식 있나요? ──> 서버
클라이언트 <─ 없습니다 ────────── 서버
(3초 대기)
클라이언트 ── 새 소식 있나요? ──> 서버
클라이언트 <─ 없습니다 ────────── 서버
(3초 대기)
클라이언트 ── 새 소식 있나요? ──> 서버
클라이언트 <─ 1건 있습니다 ─────── 서버택배 조회 페이지를 몇 분마다 새로고침하는 것과 같습니다. 택배사가 먼저 알려주지 않으니 내가 계속 확인하는 것입니다.
대화는 항상 클라이언트가 시작합니다. 일반 HTTP는 요청이 있어야 응답이 있으므로, 서버는 새 데이터가 생겨도 먼저 알려줄 수 없습니다. 그래서 클라이언트가 주기적으로 물어보는 방법으로 "거의 실시간"을 흉내 냅니다.
2. 폴링의 종류
2-1. 숏 폴링 (Short Polling)
보통 "폴링"이라고 하면 이것을 말합니다. 요청을 보내면 서버는 새 데이터가 있든 없든 바로 응답합니다.
요청 → 즉시 응답(없음) → 대기 → 요청 → 즉시 응답(없음) → 대기 → 요청 → 즉시 응답(있음)- 구현이 가장 쉽습니다.
- 대부분의 응답이 "변경 없음"이라 요청이 낭비됩니다.
- 새 데이터가 생긴 시점과 클라이언트가 알게 되는 시점 사이에 최대 "폴링 간격"만큼 지연이 생깁니다.
2-2. 롱 폴링 (Long Polling)
요청을 받은 서버가 새 데이터가 생길 때까지 응답을 미루고 기다립니다. 데이터가 생기면 그때 응답하고, 클라이언트는 응답을 받자마자 다시 요청을 보냅니다.
클라이언트 ── 새 소식 있나요? ──> 서버
(데이터가 생길 때까지 붙잡고 대기...)
클라이언트 <─ 1건 있습니다 ─────── 서버
클라이언트 ── 새 소식 있나요? ──> 서버 ← 받자마자 바로 다시 요청
(대기...)너무 오래 기다리면 중간 장치가 연결을 끊을 수 있으므로, 서버는 보통 일정 시간(예: 30초)이 지나면 "없음"으로 응답하고 클라이언트는 다시 요청합니다.
- 새 데이터가 생기면 거의 바로 전달되어 지연이 짧습니다.
- 빈 응답이 크게 줄어듭니다.
- 서버가 대기 중인 요청을 붙잡고 있어야 하므로 동시 연결 관리가 필요합니다.
2-3. 한눈에 보기
| 항목 | 숏 폴링 | 롱 폴링 |
|---|---|---|
| 서버 응답 시점 | 즉시 | 데이터가 생기거나 타임아웃될 때 |
| 빈 응답 | 많음 | 적음 |
| 전달 지연 |
3. 코드로 보기
숏 폴링
async function poll() {
const res = await fetch('/api/notifications');
const data = await res.json();
render(data);
}
// 5초마다 확인
setInterval(poll, 5000);setInterval은 이전 요청이 끝나지 않아도 다음 요청을 보냅니다. 응답이 느리면 요청이 겹칠 수 있으므로, 응답을 받은 뒤 다음 요청을 예약하는 방식이 더 안전합니다.
async function poll() {
try {
const res = await fetch('/api/notifications');
render(await res.json());
} finally {
setTimeout(poll, 5000); // 끝난 뒤에 다음 요청 예약
}
}
poll();롱 폴링
async function longPoll(lastId = 0) {
try {
// 서버는 lastId 이후 새 데이터가 생길 때까지 응답을 미룬다
const res = await fetch(`/api/messages?after=${lastId}`);
if (res.status === 200) {
const messages = await res.json();
render(messages);
lastId = messages.at(-1)?.id ?? lastId;
}
// 204 등: 타임아웃으로 빈 응답 → 그냥 다시 요청
longPoll(lastId); // 바로 다시 요청
} catch {
setTimeout(() => longPoll(lastId), 3000); // 오류면 잠시 쉬고 재시도
}
}
longPoll();lastId 처럼 어디까지 받았는지를 함께 보내면, 응답과 다음 요청 사이에 생긴 데이터를 놓치지 않습니다.
4. 요청 낭비 줄이기
조건부 요청 (304 Not Modified)
데이터가 바뀌지 않았다면 본문 없이 "변경 없음"만 응답하게 할 수 있습니다.
GET /api/status
If-None-Match: "v42"
HTTP/1.1 304 Not Modified서버가 처음 응답할 때 ETag: "v42" 를 주고, 클라이언트가 다음 요청에 그 값을 If-None-Match 로 보냅니다. 바뀐 게 없으면 본문 없이 304 만 오므로 전송량이 줄어듭니다. 요청 횟수 자체는 줄지 않는다는 점은 기억해 둡니다.
간격 조절
- 백오프(Backoff): 오류가 나거나 변화가 없으면 간격을 점점 늘립니다. (2초 → 4초 → 8초 …)
- 화면이 안 보일 때 멈추기: 탭이 백그라운드에 있으면 폴링을 멈추거나 느리게 합니다.
document.addEventListener('visibilitychange', () => {
if (document.hidden) stopPolling();
else startPolling();
});- 끝나면 멈추기: 작업 완료처럼 더 확인할 필요가 없는 상태가 되면 폴링을 중단합니다.
5. 어디에 적합한가?
- 파일 변환·결제·배포처럼 오래 걸리는 작업의 상태 확인
- 몇 초~몇 분 늦어도 괜찮은 대시보드 수치 갱신
- 외부 API처럼 서버가 먼저 알려주는 기능이 없는 대상을 확인할 때
- 실시간 기능을 빠르게 만들어 봐야 하는 초기 단계
자주 쓰는 패턴은 "작업 생성 → 상태 폴링"입니다.
POST /jobs → 202 Accepted, { "id": 7 }
GET /jobs/7 → { "status": "processing" }
GET /jobs/7 → { "status": "processing" }
GET /jobs/7 → { "status": "done", "url": "..." } ← 여기서 폴링 종료예시 1: 결제 결과 확인
외부 PG(결제대행사)로 결제한 뒤 우리 페이지로 돌아왔을 때 씁니다. 이때 폴링하는 대상은 PG가 아니라 우리 서버입니다.
1. 사용자 → PG 결제창에서 결제
2. PG → 우리 결과 페이지로 리다이렉트
3. PG → 우리 서버로 결과 통보(웹훅) 또는 우리 서버 → PG 승인 API 호출
4. 결과 페이지 → 우리 서버에 1~2초마다 확인 ← 폴링
GET /orders/7 → { "status": "pending" }
GET /orders/7 → { "status": "paid" } ← 여기서 종료2번과 3번의 순서는 보장되지 않습니다. 사용자가 결과 페이지에 먼저 도착했는데 서버는 아직 결과를 못 받았을 수 있습니다. 그래서 결과 페이지는 "결제 확인 중"을 보여 주면서 짧게 반복해서 묻습니다.
- 리다이렉트 URL의 파라미터만 보고 성공 처리하지 않습니다. 사용자가 조작할 수 있으므로 서버가 PG와 직접 확인한 값을 기준으로 합니다.
- 일정 시간(예: 30초) 안에 결과가 나오지 않으면 폴링을 멈추고 "주문 내역에서 확인해 주세요" 같은 안내로 넘깁니다.
- 가상계좌처럼 입금이 몇 시간 뒤에 되는 결제는 폴링으로 기다리지 않습니다. 서버가 웹훅을 받아 처리합니다.
예시 2: 파일 업로드 후 처리 상태
진행 상황은 그 작업을 하고 있는 쪽만 압니다. 그래서 누가 작업하느냐에 따라 폴링이 필요한 구간이 나뉩니다.
[클라이언트 → 서버 업로드] 진행률: 클라이언트가 앎 → 폴링 불필요
[서버: 변환·리사이징·검사] 진행률: 서버만 앎 → 폴링
[서버 → S3 저장] 진행률: 서버만 앎 → 폴링업로드 진행률은 보내는 쪽인 브라우저가 이미 알고 있으므로 직접 계산합니다.
const xhr = new XMLHttpRequest();
xhr.upload.onprogress = (e) => {
if (e.lengthComputable) setProgress(Math.round((e.loaded / e.total) * 100));
};
xhr.open('POST', '/api/upload');
xhr.send(formData);는 업로드 진행률 이벤트를 제공하지 않아서 (또는 이를 감싼 axios의 )를 씁니다.
6. 장점과 한계
장점
- 일반 HTTP 요청이라 이해하기 쉽고 구현이 단순합니다.
- 별도 프로토콜이나 서버 설정이 거의 필요 없습니다.
- 프록시·방화벽·로드밸런서에서 막힐 일이 적습니다.
- 요청마다 독립적이라 서버 재시작이나 네트워크 끊김에도 자연스럽게 복구됩니다.
- 기존 인증 방식(쿠키, Authorization 헤더)을 그대로 쓸 수 있습니다.
한계
- 변화가 없어도 요청을 보내므로 네트워크와 서버 자원이 낭비됩니다.
- 숏 폴링은 간격만큼 반영이 늦습니다. 간격을 줄이면 요청이 더 늘어납니다.
- 사용자가 많아지면 "사용자 수 × 초당 요청 수"만큼 서버 부하가 커집니다.
- 모바일에서는 반복 요청이 배터리와 데이터를 소모합니다.
7. 간격은 어떻게 정할까?
"얼마나 늦어도 괜찮은가"에서 출발합니다.
| 상황 | 예시 간격 |
|---|---|
| 결제 결과 확인 | 1~2초 |
| 작업 진행 상태 | 2~5초 |
| 알림 개수 | 30초~1분 |
| 통계 대시보드 | 1~5분 |
부하를 대략 계산해 볼 수도 있습니다. 사용자 1,000명이 5초마다 요청하면 초당 200건입니다. 간격을 1초로 줄이면 초당 1,000건이 됩니다.
8. 핵심 정리
폴링은 클라이언트가 일정 간격으로 서버에 변경 사항을 반복해서 물어보는 방식이다. 숏 폴링은 서버가 즉시 응답하고, 롱 폴링은 새 데이터가 생길 때까지 서버가 응답을 미룬다. 단순하고 어디서나 동작하지만, 빈 요청과 지연이라는 비용이 있다.