"SSE 와 WebSocket 전 server-push update 받는 유일 방법이 반복 묻기. Naive polling 이 낭비; long-polling 이 낭비성 fix; 둘 다 더 고기술 옵션 안 맞는 경우 위해 2026 production 에 여전히 ship."
Naive Polling — 반복 묻기, 대부분 답 버리기
Client 가 N초 마다 server 한테 "update 있어?" 물음. 변경 없으면 server 가 빈 결과 돌려줌; client 가 기다리고 또 물음. 구현 단순; 어느 HTTP server 와 동작; 세상 모든 proxy 생존.
비용:
지연. 평균 전달 시간 = N/2. 5s 마다 polling 면 user-인지 지연 0과 5s 사이, 평균 2.5s.
Bandwidth. 빈 response 도 HTTP roundtrip 비용 (request line + header + 200 OK + 빈 body = 양쪽 ~500 byte). 10,000 client 간 5s interval 이면 1 MB/s 의 아무것도 아님.
Server 부하. Idle client 도 endpoint 끊임없이 침.
괜찮은 때: 저-stake 상태 체크 (10s 마다 build 진행), background worker, 지연 tolerance 높은 곳.
Long-Polling — Server 가 request 보유
같은 패턴, 더 똑똑한 실행: client 가 물음; 가용 없으면 server 가 즉시 응답 안 함. 대신 server 가 event 기다리며 연결 열어둠. 뭔가 일어나면 (혹은 server 쪽 timeout 발동, ~30s), server 가 event (혹은 빈 + 'timeout' marker) 로 응답. Client 가 즉시 새 long-poll 시작.
Naive polling 대비 승리:
거의 zero 지연. 데이터 가용 순간 server push. Client 가 한 RTT 안에 받음.
낮은 request rate. Event 당 request 하나 (혹은 ~30s timeout 당) 5s 당 request 하나 대신.
비용:
Pending client 당 영속 연결 하나. Server 가 N 대기 client 위해 N file descriptor 보유. Async/await framework (FastAPI, Node, Go) 가 이거 싸게 처리; thread-per-request server (옛 PHP, Apache prefork) 가 안 함.
Proxy/timeout 간섭. 일부 intermediary 가 ~60s 침묵 후 연결 닫음. Server 쪽 timeout 이 어느 intermediary 보다 먼저여야.
Async 비용 반전
동기 (thread-per-request) server 에서 10,000 long-poll 연결 보유가 10,000 thread 필요 — 보통 비현실적. Async server (Python asyncio, Node.js, Go goroutine) 에서 10,000 pending 연결이 거의 비용 영 — 각각 stalled coroutine, thread 아님. Framework concurrency 모델이 scale 에서 long-polling 실용성 결정.
Long-polling 이 HTTP 동작하는 어디서나 동작하는 가장 단순한 'real-time' 패턴. Protocol upgrade 불필요, 특별 middleware 불필요, client-쪽 라이브러리 불필요. SSE 나 WebSocket 못 쓸 때 (회사 방화벽 차단, 고대 client) long-polling 이 그냥 동작하는 fallback.
Long-polling 이 SSE / WebSocket 이기는 때
Long-polling 이 SSE 이김:
Client 가 장기 연결 떨어뜨리는 방화벽 뒤 (일부 회사 환경) — 근데 SSE 도 같은 문제.
각 event 에 request/response semantics 필요 (client 가 각 전달 ack 원함). SSE 가 one-way push 만.
Long-polling 이 SSE/WebSocket 한테 짐:
높은 event 빈도 기대. 각 event 가 long-poll 재오픈 — SSE 안 가진 per-event HTTP overhead.
진짜 스트리밍 원함 (server 가 작은 event 의 연속 흐름 push). SSE 가 이를 위해 설계.
양 방향 트래픽 필요. 양 방향이 real-time 흘러야 → WebSocket.
cwkPippa 의 streaming 스택
cwkPippa 가 polling 도 long-polling 도 안 씀 — AI token stream 위해 SSE ship (canonical 사용 사례: WebUI 한테 server-push 높은 빈도 event). 스트리밍 안 하는 endpoint 의 fallback 이 일반 request/response. Cinder Sidekick 채널 (진짜 양방향) 위해 WebSocket 존재. Long-polling 절대 필요 없었음 — ship 한 모든 client 가 SSE native 지원. 패턴이 2026 에 여전히 ship — Pushpin, Pusher, 많은 SaaS 알림 service 가 SSE/WebSocket 실패 시 long-polling 을 fallback 으로 씀.
Code
Naive polling — N초 마다 묻고, 대부분 응답 버림·python
# Naive polling — client 쪽
import httpx, time
while True:
resp = httpx.get('https://api.example.com/inbox/poll?since_id=last_seen')
events = resp.json()['events']
for event in events:
handle(event)
time.sleep(5) # 5초 마다 poll
# 낭비: 대부분 poll 이 빈 응답.
Long-polling server (FastAPI asyncio) — 데이터나 timeout 까지 request 보유·python
# Long-polling — server 쪽 (FastAPI + asyncio)
import asyncio
from fastapi import FastAPI, Request
app = FastAPI()
_event_queues: dict[str, asyncio.Queue] = {}
@app.get('/inbox/poll')
async def long_poll(client_id: str, request: Request):
queue = _event_queues.setdefault(client_id, asyncio.Queue())
# 최대 30s 동안 event 기다림 (server-쪽 timeout)
try:
event = await asyncio.wait_for(queue.get(), timeout=30.0)
return {'event': event}
except asyncio.TimeoutError:
# 30s 내 event 없음 — client 가 즉시 재 poll 하도록 빈 거 돌려줌
return {'event': None}
# 뭔가 일어나면 queue 에 push (어느 consumer 든):
async def publish_to(client_id: str, event: dict):
queue = _event_queues.get(client_id)
if queue:
await queue.put(event)
# Client 쪽 — naive polling 과 같은 shape, sleep 불필요:
# while True:
# resp = httpx.get('https://api.example.com/inbox/poll', params={'client_id': 'me'})
# event = resp.json()['event']
# if event: handle(event)
# # 즉시 loop — server 가 다음 request 보유
// Long-polling — client 쪽 (브라우저 fetch loop)
async function longPoll(clientId) {
while (true) {
try {
const resp = await fetch(`/inbox/poll?client_id=${clientId}`);
const data = await resp.json();
if (data.event) {
handle(data.event);
}
// sleep 없음 — 즉시 재-poll. Server 가 다음 event 나 timeout 까지 보유.
} catch (e) {
// 네트워크 에러 — flapping server 두드림 피하려 잠시 back off
await new Promise(r => setTimeout(r, 1000));
}
}
}
longPoll('me');
새 event 도착이나 30초 경과까지 GET /events?since_id=X 보유하는 FastAPI long-poll endpoint 만들어. client_id 당 asyncio.Queue 써. 그 다음 작은 publisher (매칭 queue 에 event 넣는 POST /publish) 쓰고 long-polling client 가 publish 의 ~10ms 안에 event 받는 거 검증 — 5s interval naive polling 대비, 최대 5s 기다림. timestamp 로 둘 다 수치 측정.
Hint
Asyncio 패턴: client_id → asyncio.Queue 매핑 dict, asyncio.wait_for timeout 으로 await queue.get(). Publishing 은 queue.put_nowait(event). Client: response 에 즉시 재-fetch 가진 fetch + tight loop. 수치 비교: t=0 에 publish, client 가 t=~RTT (long-poll) vs t=~2.5s 평균 (naive 5s polling) 에 받음. 차이가 느낄 만큼 극적.
Progress
Progress is local-only — sign in to sync across devices.