# 기본으로 돌아가기: 웹소켓 대신 롱폴링을 선택한 이유

> Clean Markdown view of GeekNews topic #18649. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=18649](https://news.hada.io/topic?id=18649)
- GeekNews Markdown: [https://news.hada.io/topic/18649.md](https://news.hada.io/topic/18649.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2025-01-09T20:20:44+09:00
- Updated: 2025-01-09T20:20:44+09:00
- Original source: [inferable.ai](https://www.inferable.ai/blog/posts/postgres-nodejs-longpolling.mdx)
- Points: 14
- Comments: 5

## Summary

Node.js/TypeScript 기반 백엔드에서 대규모 실시간 업데이트를 처리하기 위해 웹소켓 대신 HTTP 롱 폴링을 선택한 이유는, 롱 폴링이 인프라 호환성, 인증 단순성, 운영의 용이성 등에서 더 많은 장점을 제공하기 때문입니다. 롱 폴링은 서버가 데이터가 생길 때까지 기다렸다가 응답을 반환하는 방식으로, 기존 HTTP 기반 로깅 및 모니터링 스택을 그대로 활용할 수 있고, 외부 의존성을 최소화하여 제품 제어를 강화할 수 있습니다. 또한, 롱 폴링은 Postgres 데이터베이스와의 최적화를 통해 효율적인 데이터 조회를 가능하게 하며, 클라이언트 측에서는 표준 HTTP 요청-응답 구조에 재시도 로직만 추가하면 쉽게 구현할 수 있습니다.

## Topic Body

- Node.js/TypeScript 기반 백엔드에서 대규모 실시간 업데이트를 처리해야 하는 상황이었음  
- PostgreSQL을 백엔드로 사용하여 수백 개의 워커 노드가 새로운 작업을 지속적으로 확인하고, 에이전트가 실행 및 채팅 상태 업데이트를 받아야 함  
- 웹소켓에 대한 탐구로 시작했지만, 놀랍도록 효과적인 '구식' 솔루션으로 귀결  
  → "Postgres를 사용한 HTTP Long Polling"  
  
### 문제 상황: 대규모 실시간 업데이트   
- **워커 노드 업데이트** :   
  - Node.js/Golang/C# SDK를 실행하는 수백 개의 워커 노드가 있음  
  - 새로운 작업이 제공되는 즉시 이를 알아야 했기 때문에 Postgres 데이터베이스를 다운시키지 않는 쿼리 전략이 필요  
- **에이전트 상태 동기화** :   
  - 에이전트는 실행 및 채팅 상태에 대한 실시간 업데이트가 필요했고, 이를 효율적으로 스트리밍해야 함   
  
### 롱 폴링과 WebSocket 비교  
- 숏 폴링은 시간표에 따라 엄격하게 출발하는 기차와 같아서 승객이 있는지 여부에 관계없이 정해진 간격으로 출발함   
- 롱 폴링은 서버가 응답을 기다리다 데이터가 생기면 바로 반환하고, 일정 시간이 지나면 타임아웃으로 응답을 돌려줌  
  - 즉, “기다리다가 데이터가 생기면 출발”하는 기차와 같음. 특정 시간(TTL)내에 승객이 나타나지 않을때만 비어 있는 상태로 출발   
  - 데이터(승객)가 있을 때는 즉시 출발하고 없을 때는 리소스를 효율적으로 사용할 수 있는 두 가지 장점을 모두 제공  
- WebSocket은 연결을 상시 유지해 양방향으로 데이터를 주고받는 방식임  
  - 조직 환경, 인프라, 파이어월 문제 등으로 WebSocket 구성보다 롱 폴링이 더 단순하고 호환성 높음  
  
### 롱 폴링 구현 세부 내용  
- `getJobStatusSync` 함수가 중요한 역할을 담당함  
  - `jobId`, `owner`, `ttl` 등의 파라미터를 받아 특정 작업 상태를 특정 시간 동안 반복 조회함  
- 다음 조건 중 하나가 충족될 때까지 반복 조회를 수행함  
  - 작업 상태가 `success` 또는 `failure`가 됨  
  - `ttl`(타임아웃) 경과  
- 500ms 간격으로 데이터베이스를 조회하고, 결과가 확정되지 않았으면 기다렸다가 다시 조회함  
- 타임아웃 초과 시 에러를 던지고, 성공 시 결과를 반환함  
  
### 데이터베이스 최적화  
- Postgres에 적절한 인덱스를 두어 조회 비용을 최소화함  
- 예: `CREATE INDEX idx_jobs_status ON jobs(id, cluster_id);`  
  
### 롱 폴링의 이점  
- **모니터링 유지 용이성** : 기존 HTTP 기반 로깅, 모니터링 스택을 그대로 활용 가능함  
- **인증 단순성** : 새 인증 방식을 구현할 필요 없이 기존 HTTP 인증을 그대로 사용 가능함  
- **인프라 호환성** : 파이어월이나 로드 밸런서에 별도 설정이 필요 없고, 일반 HTTP 트래픽으로 취급됨  
- **운영 단순성** : 서버 재시작 시에도 연결 상태를 별도로 처리할 필요가 없고, 디버깅이 용이함  
- **클라이언트 구현 간편성**  :  표준 HTTP 요청-응답 구조에 재시도 로직만 추가하면 동작 가능함  
  
### ElectricSQL과의 비교  
- ElectricSQL은 Postgres 데이터를 프론트엔드와 동기화하는 솔루션임  
- WebSocket 대신 HTTP를 쓰면서도 실시간성을 보장해주는 구조를 갖추고 있음  
- 실제로 실시간 업데이트를 처리하기 위해 극단적인 제어나 낮은 수준의 구조가 필요하지 않은 경우 ElectricSQL을 권장  
  
### 우리가 Raw Long Polling을 선택한 이유  
- 메시지 전달 메커니즘은 단순한 구현 세부사항이 아니라 **제품의 핵심** 요소  
- 핵심 기능을 타사 라이브러리에 의존할 수 없음 (아무리 우수한 라이브러리라도)  
- 요구사항  
  - **핵심 제품 제어** : 메시지 전달 메커니즘을 완전히 제어해야 함. 인프라 수준이 아니라 제품 자체임   
  - **외부 의존성 제거** : 셀프 호스팅을 단순화하기 위해 외부 의존성을 최소화  
  - **저수준 제어** : 폴링 메커니즘 및 연결 관리를 직접 제어  
  - **최대 제어 가능성** : 동적 폴링 간격 구현 등 세부사항을 세밀하게 조정할 수 있어야 함   
  - **코드 단순성**  : 사용자들이 코드베이스를 쉽게 이해하고 수정할 수 있도록 간단하게 설계  
- 결론적으로 간단한 HTTP Long Polling 구현을 선택함으로써 **직접 제어**와 **단순성**을 확보  
  
### 롱 폴링 구현 시 주의사항  
- **TTL 설정** : 서버 쪽에서 반드시 최대 TTL을 강제하고, 클라이언트가 요청한 TTL이 이를 넘지 않도록 처리함  
- **인프라 타임아웃 고려**  :  로드 밸런서, 엣지 서버, 프록시 등의 타임아웃 설정보다 충분히 짧은 TTL이어야 함  
- **DB 폴링 간격**  :  500ms 정도로 딜레이를 주어 DB 부하를 줄임  
- **백오프 전략(옵션)**  : 점진적으로 폴링 간격을 늘리는 방식으로 시스템 자원을 더 효율적으로 사용 가능함  
  
### WebSocket을 고려해야 할 상황  
- WebSocket 자체가 잘못된 것은 아니며, 다른 측면에서는 유용함  
  - 상태가 많은 연결을 모니터링하고, 복잡한 이벤트를 상시 주고받아야 하는 경우  
  - 인증, 인프라, 관측 문제를 해결할 리소스와 시간이 충분한 경우  
- 운영 및 로깅, 재연결 처리, 인증 메커니즘 등을 직접 구축해야 하는 복잡성이 존재함  
  
### WebSockets: 또 다른 선택지에 대한 이야기  
- Long Polling이 우리의 요구에 적합했지만, WebSockets도 충분히 고려할 가치가 있음  
- WebSockets 자체가 나쁜 것은 아니며, 많은 **주의와 관리**가 필요할 뿐  
- WebSockets의 주요 과제와 해결 방향  
  - **가시성** : WebSockets는 상태 기반이므로, 지속적인 연결에 대한 로깅과 모니터링 추가 필요  
  - **인증** : WebSocket 연결을 위한 새로운 인증 메커니즘 구현 필요  
  - **인프라** : WebSocket을 지원하기 위해 로드 밸런서, 방화벽 등의 인프라를 적절히 구성해야 함  
  - **운영 관리** : WebSocket 연결 및 재연결 관리. 연결 타임아웃 및 오류 처리  
  - **클라이언트 구현** : 클라이언트 측 WebSocket 라이브러리 구현. 재연결 및 상태 관리 기능 포함

## Comments



### Comment 33246

- Author: jhj0517
- Created: 2025-01-10T14:06:03+09:00
- Points: 1

ML 모델 서빙에 여기서 말하는 "숏폴링" 구조를 사용하고 있는데 뭐가 효율적일지 고민이 많네요. 나름대로 여기 저기 알아본 바로는 웹소켓이나 SSE 등의 재연결 처리에 대한 큰 비용때문에 숏폴링이 일반적으로 더 안전하다는 이야기가 있어서 숏폴링을 선택하긴 했는데.. 😭

### Comment 33229

- Author: bbulbum
- Created: 2025-01-10T10:51:39+09:00
- Points: 2

Long polling 은 좀 hacky 하게 느껴져서 꺼려지는 것 같네요. 브라우저에선 아마 계속 요청이 완료되지 않은 것으로 뜰 것같고요. 종종 로딩이 끝나지 않는 사이트들이 있던데 저는 컨텐츠가 전부 로드되지 못한건가? 싶어서 별로더라구요.  
어플리케이션에서도 결국 어느부분에 hang 을 걸고 응답을 대기하는 상태가 될텐데,, 좀 어색하게 보이네요.

### Comment 33201

- Author: joyfui
- Created: 2025-01-09T23:26:46+09:00
- Points: 2

"에이전트가 실행 및 채팅 상태 업데이트를 받아야 함"  
이거보고 바로 sse를 떠올렸는데 역시 해커뉴스 의견에서 sse 언급이 많군요.

### Comment 33198

- Author: neo
- Created: 2025-01-09T20:20:44+09:00
- Points: 1

###### [Hacker News 의견](https://news.ycombinator.com/item?id=42600276) 
- Long polling은 자체적인 문제를 가지고 있음
  - Second Life는 클라이언트와 서버 간에 HTTPS long polling 채널을 사용함
  - 클라이언트 측에서는 libcurl을 사용하며, 타임아웃이 발생할 수 있음
  - 서버가 타임아웃과 다음 요청 사이에 메시지를 보내려 하면 경합 조건이 발생하여 메시지가 손실될 수 있음
  - Apache 서버가 앞단에 위치하여 불필요한 요청을 차단하지만, 타임아웃이 발생할 수 있음
  - 중간 박스와 프록시 서버가 long polling을 싫어할 수 있음
  - HTTP 연결을 오래 유지하는 것을 싫어하는 요소들이 많음
  - 결과적으로 신뢰할 수 없는 메시지 채널이 되어 중복을 감지하기 위해 시퀀스 번호가 필요하고 메시지를 잃을 수 있음
  - 원래 기사에서 "loop"로 표시된 차트 섹션은 타임아웃 처리를 언급하지 않음
  - long polling을 사용할 경우 몇 초마다 데이터를 보내 연결을 유지해야 함

- Phoenix와 LiveView를 매일 사용하는 것이 기쁨
  - WebSockets를 사용하여 신경 쓸 필요가 없음

- 서버 전송 이벤트(SSE)를 사용하는 것보다 기술적인 이점이 있는지 궁금함
  - 둘 다 HTTP 연결을 열어두고 간단한 HTTP라는 장점이 있음
  - SSE는 업데이트나 결과를 스트리밍할 수 있는 경우에 더 적합해 보임
  - 적합한 사용 사례는 특정 클라이언트를 대신하여 모든 작업 ID를 모니터링하는 경우일 수 있음

- 이 기사는 "Websocket"과 "Long-polling"을 독립적인 결정으로 연결하고 있음
  - long-polling 서버는 약간의 추가 작업으로 websocket 클라이언트를 처리할 수 있음
  - 기존 아키텍처가 websocket인 경우 long-polling 클라이언트를 지원하려면 두 개의 서버 계층이 필요함

- Node.js에서 setTimeout을 사용하는 더 쉬운 방법
  - `import { setTimeout } from "node:timers/promises"; await setTimeout(500);` 사용

- long polling을 좋아함, 이해하기 쉽고 클라이언트 관점에서 매우 느린 연결처럼 작동함
  - 재시도와 클라이언트 측 취소된 연결을 추적해야 함
  - 코드 예제에서 반복적으로 데이터를 쿼리하는 루프가 어색해 보임

- 서버 전송 이벤트나 WebSockets가 long polling의 모든 사용 사례를 대체하지 못함
  - SSE의 연결 제한이 자주 문제로 등장함
  - WebSockets는 대부분의 환경에서 신뢰할 수 없음
  - 백엔드에서 변경 사항을 감지하고 적절한 클라이언트로 전파하는 문제는 여전히 해결되지 않음

- Postgres의 비동기 알림 기능을 사용하는 것이 좋음
  - 서버가 채널을 LISTEN하고 데이터 변경 시 PG가 TRIGGER 및 NOTIFY할 수 있음

- 짧은 타임아웃과 우아하게 종료된 요청을 가진 long polling의 의미가 여전히 있는지 모르겠음
  - HTTP/2나 QUIC이 사용되지 않는 경우 이 트릭이 여전히 의미가 있을 수 있음

- WebSockets의 상대적으로 간단한 대안을 상기시키는 것이 상쾌함
  - WebSockets를 선택한 스타트업에서 일했었고, 호텔과 레스토랑 와이파이에서 테스트가 어려웠음

### Comment 33227

- Author: luminance
- Created: 2025-01-10T10:42:52+09:00
- Points: 1
- Parent comment: 33198
- Depth: 1

Elixir, Phoenix framework, LiveView를 통해서 WebSockets를 써보고 싶네요.
