
2014년 HTML5가 도입되기 전에는 서버➖클라이언트 간 실시간 통신을 구현하는 표준화된 양방향 통신방법(지금의 WebSocket)이 없었다.
HTTP 프로토콜 자체가 클라이언트에서 서버로의 단방향 통신을 위해 만들어진 것이기 때문
그 당시에는 주로 HTTP 위에서 마치 양방향인 것 처럼 동작하게 하는 기술들을 활용하였는데, 대표적으로 Short Polling, Long Polling, SSE(Sever Sent Events) 등이 있다.
이 기술들은 각각의 방식으로 서버와 클라이언트 간의 통신을 시도해서 실시간 데이터의 동기화나 푸시 기능을 흉내냈다.
polling 이란?
정보를 얻기위해 계속 요청하는 행위 정도로 이해하면 될 것 같다.
클라이언트가 정기적으로 서버에 요청을 보내 업데이트나 새 데이터를 확인하는 기술이다.
서버는 마지막 요청 이후에 현재 상태에 대한 응답이나 변경사항이 있으면 응답한다.

구현하기 쉽다는 장점이 있고, 대부분의 HTTP 인프라와 호환되지만 숏 폴링은 빈번한 네트워크 요청이 있기 때문에 지연시간이 증가할 가능성이 있다.
숏폴링은 실시간 요구사항이 덜 엄격하고 구현을 빠르게 해야할 때 사용된다.
@RestController
@RequestMapping("/api/polling")
public class PollingController {
private final AtomicLong lastUpdatedTime = new AtomicLong();
@GetMapping("/data")
public ResponseEntity<String> getData() {
long currentTime = System.currentTimeMillis();
// 특정 조건에서만 데이터 업데이트(예: 새로운 데이터가 있을 때)
if (currentTime - lastUpdatedTime.get() > 5000) { // 5초마다 업데이트
lastUpdatedTime.set(currentTime);
return ResponseEntity.ok("새로운 데이터가 있습니다!");
}
return ResponseEntity.ok("변경 사항이 없습니다.");
}
}
<template>
<div>
<h2>숏 폴링</h2>
<p>{{ message }}</p>
</div>
</template>
<script>
export default {
data() {
return {
message: "서버에서 데이터를 가져오는 중...",
pollingInterval: null, // pollingInterval 변수 선언
};
},
methods: {
fetchData() {
fetch("http://localhost:8080/api/polling/data")
.then((response) => response.text())
.then((data) => {
this.message = data;
})
.catch((error) => {
console.error("Error fetching data:", error);
});
},
},
mounted() {
this.fetchData(); // 초기 데이터 가져오기
this.pollingInterval = setInterval(this.fetchData, 5000); // 5초마다 요청
},
beforeDestroy() {
clearInterval(this.pollingInterval); // 컴포넌트가 제거될 때 인터벌 해제
},
};
</script>
롱 폴링은 클라이언트가 서버에 새 데이터를 폴링하는 기술이다. 서버에 클라이언트가 사용할 수 있는 데이터가 없는 경우 서버는 빈 응답을 보내는 대신에 요청을 보류하고 새 데이터가 있을 때 까지 지정된 시간동안 대기한다.
데이터가 없는 경우 서버는 두 가지로 응답할 수 있는데,
1. 지정된 시간동안 새 데이터가 사용 가능해지면 서버는 즉시 클라이언트에게 응답을 보내서 열린 요청을 완료한다.
2. 제한 시간동안 새 데이터가 사용이 불가능하면 서버는 그 사실을 알리는 응답을 보낸다. 그러면 클라이언트는 즉시 새로운 요청을 만들어내서 다시 새 요청-응답 주기를 만든다.

Long Polling 방식은 주기적으로 요청을 보내는 Short Polling방식에 비해 서버에 대한 네트워크의 부하를 줄일 수 있다는 장점이 있다.
@RestController
@RequestMapping("/api/long-polling")
public class LongPollingController {
private final AtomicLong lastUpdatedTime = new AtomicLong();
@GetMapping("/data")
public DeferredResult<ResponseEntity<String>> getData() {
DeferredResult<ResponseEntity<String>> deferredResult = new DeferredResult<>(10000L); // 최대 10초 대기
long currentTime = System.currentTimeMillis();
if (currentTime - lastUpdatedTime.get() > 5000) { // 새로운 데이터가 있을 때만 응답
lastUpdatedTime.set(currentTime);
deferredResult.setResult(ResponseEntity.ok("새로운 데이터가 있습니다!"));
} else {
deferredResult.onTimeout(() -> deferredResult.setResult(ResponseEntity.ok("변경 사항이 없습니다.")));
}
return deferredResult;
}
}
DeferredResult는 비동기 방식으로 응답을 보낼 수 있게 해준다.
DeferredResult란?
DeferredResult<T>는 단어 그대로 지연된 결과라는 뜻을 가진 비동기 처리 객체다.
Spring Controller 내에서 DeferredResult가 반환면 해당 객체에 결과가 세팅될 때 까지 응답을 보내지 않고 기다린다. 이 과정에서 요청 스레드를 점유하지 않기 때문에 서버의 리소스를 절약할 수 있으며, 이후 결과가 세팅되면 바로 클라이언트에게 응답을 전송한다.
DeferredResult 객체만 메모리에 유지되면 결과가 세팅되는 시점에 바로 응답을 보낼 수 있다.
🫱 즉 DeferredResult는 실시간성과 비동기성이 요구되는 상황에서 유용하게 처리할 수 있는 기술이다.
서버는 10초동안 새로운 데이터가 생길 때 까지 대기하며 만약 데이터가 생기면 즉시 클라이언트에 응답을 전송하고, 데이터가 생기지 않으면 타임아웃 후 '변경사항이 없음' 메세지를 응답으로 보낸다.
<template>
<div>
<h2>롱 폴링</h2>
<p>{{ message }}</p>
</div>
</template>
<script>
export default {
data() {
return {
message: "서버에서 데이터를 가져오는 중...",
};
},
methods: {
fetchData() {
fetch("http://localhost:8080/api/long-polling/data")
.then((response) => response.text())
.then((data) => {
this.message = data;
this.fetchData(); // 응답을 받은 후 다시 요청을 보냄
})
.catch((error) => {
console.error("Error fetching data:", error);
setTimeout(this.fetchData, 5000); // 오류 발생 시 5초 후 재시도
});
},
},
mounted() {
this.fetchData(); // 최초 요청
},
};
</script>
SSE는 이전에 작성한 포스팅이 있어서 SSE(Server Send Event) 정의, SpringBoot + Vue 구현 간단한 예제 를 첨부한다.
https://javascript.info/long-polling
https://medium.com/@anoopnayak1/exploring-real-time-communication-in-web-development-short-polling-vs-long-polling-ec571f5e8af8