일반적으로 데이터를 주고받는 상황에서 Socket 은 클라이언트와 서버가 한번 연결해놓고 데이터를 전달받기 때문에 대규모 트래픽이 발생하는 상황에서 Polling 방식보다 유리하다고 알고 있습니다. 하지만 이 명제가 항상 옳지는 않습니다. 대규모 트래픽에 대해서는 Socket 보다는 Polling 방식이 더 안정적일 수 있는데, 그 이유에 대해 먼저 알아봅시다.
🎯 Socket의 내재된 위험성
짧은 Polling 주기에 대해 가장 많이 걱정하는 부분이 바로 서버에 대한 과부하 입니다. 물론 맞는말입니다. Polling 주기를 짧게 설정해두고 실시간 데이터를 받는것보다는 SSE 또는 소켓으로 연결하는게 더 효율적이죠.
하지만, 100만명의 사용자가 있는 서비스는 100만명 모두의 소켓 연결을 유지해야 합니다. Socket 은 연결을 유지하기 위해 다음과 같은 비용이 발생합니다.
- 100만명이 접속 -> 100만개의 소켓 객체가 메모리에 보관됨
- ELB(로드밸런서) L4/L7 의 Max Connection 한계 도달
- 연결만 해놨는데 리소스가 계속 발생
하지만, 위 비용보다 더 치명적인 장애가 발생할 수 있는데 네트워크가 잠깐이라도 끊어진 뒤 재접속할 경우 100만명이 재접속을 시도하게 됩니다. 결국 서버는 디도스 공격과 같은 상황에 직면에 서버는 복구할 수 없는 장애가 발생할 수 있습니다.
🎯 Stateless 한 Polling
Polling 방식은 Socket 과 달리 Stateless한 상태를 유지합니다. 즉 연결 정보(Session)가 없으니 어떤 서버로 요청해도 상관 없습니다. Scale-out 에 유리하죠. 반대로 소켓은 서버 상태를 유지해야 하기 때문에 높은 트래픽이 발생해 서버를 증설한다해도 연결 상태를 유지하기 매우 까다롭습니다.
또한 Polling 방식은 요청을 처리하는 자원만 사용하고 바로 반납하게 됩니다. 한마디로 100만명의 사용자가 있는 서비스에서 트래픽이 부하가 온다하면 그냥 서버만 증설하면 해결되는 문제입니다.
하지만 Polling 방식은 아무래도 Socket 방식보다 무거운 요청일 수 밖에 없습니다. 매번 새로운 요청을 보내기 위해 연결을 하고 연결을 끊어야 하기 때문이죠. 그래서 Polling 방식을 사용할때 보통 DB 를 거치지 않는 경량 요청을 많이 사용합니다. 저같은 경우 Redis 를 이용해 필요한 데이터를 적재하고 조회합니다. 한마디로 요청수가 많아져도 개당 처리비용이 매우 낮아 서버가 충분히 버틸 수 있습니다.
🎯 프로젝트에 적용해보자
여기서 부터는 진행하고 있는 프로젝트에 대해 개인적으로 정리한 내용입니다.
현재 프로젝트에서 구현해야 하는 기능은 스마트 워치에 수집한 생체 데이터를 서버로 보내고, 사용자가 참여한 경기에 대해 자신의 순위를 조회해야 하는 기능 입니다.

이때 생체 데이터는 스마트 워치에서 5초 단위로 수집해 서버로 전송해야 합니다. 처음에는 소켓을 사용해 데이터를 주고받으려고 했지만, 실제로는 두개의 단방향 통신이라 생각했습니다. 왜냐하면 양방향 통신은 언제 데이터가 들어올지 모를때, 소켓 연결을 열어두고 통신하는 방식입니다.
하지만, 생체 데이터 전송 주기와 조회 주기를 정하면 이 통신은 두개의 단방향 통신이라 이해했습니다.
1. 생체 데이터(BioSample) 을 5초 주기로 전송
2. 자신의 경기 순위를 5초 주기 조회
수집한 생체 데이터를 5초마다 서버로 보내기에는 부담스러울 수 있어 한번에 모아 전송하는 배치(Batch) 방식을 적용하려 합니다. 또한 자신의 순위에 대한 정보는 Redis 에 저장하고 조회해 Polling 에 대한 요청을 가볍게 설계했습니다. 최종 아키텍처는 아래와 같습니다.

큰 흐름의 아키텍처는 위와 같고, 설계방법을 좀 더 자세히 알아봅시다.
무식한 Polling 은 지양하자
상황에 따라 주기를 바꾸는 Adaptive Polling 을 만들려 합니다. 경기가 점차 진행됨에 따라 폴링 주기를 짧게 만들어 실시간 정보에 대해 응답할 수 있게 만들었습니다. 또한 특정 주기에 트래픽이 몰리는걸 방지하기 위해 지터(Jitter)방식을 이용해 부하를 분산했습니다.
가변 주기 : 서버가 응답헤더(TTL)로 다음 요청 시간을 지정해줌
지터(Jitter) : 3초 + 랜덤 시간을 섞어 요청 분산
사용자가 조회하는 데이터
해당 프로젝트에서 Polling 방식으로 조회해야 하는 데이터는 순위(rank)뿐만 아니라 생체 데이터도 조회해야 합니다. 즉, 하나의 객체를 조회해야 하는거죠.
응답 예시
{
"rank": 3,
"currentDistance": 4500,
"targetDistance": 5000,
"pollInterval": 1, // 폴링 주기
"done": false
}
Polling 응답 필드에 pollInterval 은 폴링할 주기를 초(s) 단위로 응답을 내려주고, 클라이언트는 전달받은 폴링 주기를 이용해 동적으로 데이터를 조회합니다.
(경기 초반에는 5초, 중반은 3초, 마지막은 1초 정도로 할당)
두개의 key 사용
현재 만들고 있는 Polling 방식에서 가장 중요한점은 실시간 조회가 가능하게 하는겁니다. 사용자는 실시간으로 자신이 경기에서 몇등인지를 알 수 있어야하고 생체데이터 또한 조회할 수 있어야 합니다. 하지만 Socket 만큼의 실시간성은 보장되지 못합니다. (지정한 주기를 기준으로 조회되기 때문)
그래서 실시간 조회에 가까운 성능을 보장해야 합니다. Redis 를 사용해 빠른 인메모리 조회가 가능하고, 메모리 안에서도 시간 복잡도가 O(Log N) 을 보장하도록 설계했습니다.
캐시 key 는 두개를 만들었고, 각각 관리하는 데이터는 다음과 같습니다.
ZSet
┌─ game:rank:100 (ZSet) ──────────┐
│
│ 용도: 순위 조회/정렬
│
│ userId │ score (= rank)
│ ───────┼────────────────
│ "5" │ 1 ← 1위
│ "3" │ 2
│ "7" │ 3
│ ... │ ...
│
│ 1위 조회: ZRANGE 0 0 → O(1)
│ 내 순위: ZRANK → O(log N)
└─────────────────────────────────┘
Hash
┌─ game:data:100 (Hash) ──────────┐
│
│ 용도: 상세 데이터 조회
│
│ userId │ data (JSON)
│ ───────┼────────────────
│ "5" │ {rank:1,
│ │ distance:4500,
│ │ bpm:152, ...}
│ "3" │ {...}
│ "7" │ {...}
│
│ 상세 조회: HGET → O(1)
└─────────────────────────────────┘
ZSet 을 이용해 순위를 정렬하고, 해당 순위의 사용자 상세 정보는 해시 처리된 값을 관리합니다. 즉 사용자의 순위를 조회하는 시간복잡도는 O(Log N), 상세 정보를 조회하는 시간복잡도는 O(1) 로 보장됩니다.
정리해보면, Polling 을 설계할때 지정한 주기마다 클라이언트가 서버로 요청을 보낼 수 있지만, 이런 경우 특정 시간에 많은 트래픽이 몰려 서버에 많은 부하가 발생할 수 있습니다. 때문에 가변 주기와 지터를 사용해 요청을 분산시키는 방법을 적용해 Polling 방식을 더 효율적으로 설계할 수 있습니다.
테스트 결과
테스트 환경: Ec2 t3.medium
수집 메트릭
- System : CPU 사용률(user, system, iowait), 메모리 사용률(heap, non-heap), 네트워크 I/O (bytes in/out)
- Application : JVM Heap 사용량, GC 횟수 및 시간 (G1 Young/Old)
- HTTP : RPS, P99
테스트결과 (Peak 시점 기준 : 10000VU)
|
지표
|
WebSocket
|
Polling
|
개선율
|
|
메모리 사용량
|
5.2 GB
|
1.8 GB
|
약 65% 개선
|
|
CPU 사용률
|
47.8 %
|
34.6 %
|
약 27% 개선
|
|
p99 Latency
|
12ms
|
85ms
|
트레이드오프
|
지금까지 Socket 과 Polling 방식의 차이점과 대용량 트래픽에 대응하기 위해 적합한 기술을 알아봤는데요, Polling 을 이용해 데이터를 주고받을때 특정 주기로만 데이터를 주고 받는 방식보단 동적으로 주기를 할당하고 트래픽을 분산시키는 방법은 서비스 품질을 높이고 안정적인 운영에 도움이 될 것 같습니다.
'Trouble Shooting && 성능 개선' 카테고리의 다른 글
| [ Trouble Shooting ] 수만개의 요청을 어떻게 처리하고 테스트할까? (0) | 2025.07.09 |
|---|---|
| [Trouble Shooting] 대용량 트래픽에 적합한 부하 테스트 환경 (티켓팅 서비스) (0) | 2025.07.06 |
| [Trouble Shooting] 로그 조회 성능 개선하기 (0) | 2025.06.11 |
| [Trouble Shooting] 쿼리 로그 저장 기능 개선하기 (2) (2) | 2025.06.11 |
| [Trouble Shooting] 쿼리 로그 저장 기능 개선하기 (1) (0) | 2025.05.24 |