프론트엔드 통신 방식 (1): 전체 지도와 선택 축 — REST부터 gRPC까지
프론트엔드를 오래 하다 보면 결국 비슷한 질문 앞에 서게 됩니다.
- "이 기능은 REST로 충분할까, WebSocket을 써야 할까?"
- "실시간이라는데, 정말 양방향이 필요한가 아니면 서버에서 내려주기만 하면 되나?"
- "백엔드가 gRPC로 간다는데, 프론트엔드는 그대로 fetch 쓰면 되는 건가?"
- "GraphQL은 통신 방식인가, 아니면 그냥 쿼리 언어인가?"
이런 질문이 어려운 이유는 각 기술을 따로따로 배웠기 때문입니다. REST는 REST대로, WebSocket은 WebSocket대로 배우면, 막상 설계 단계에서 이것들을 같은 저울 위에 올려놓고 비교하는 감각이 잘 생기지 않습니다.
그래서 이 시리즈에서는 통신 방식을 하나씩 깊게 파고들기 전에, 먼저 전체 지도를 그리고 선택의 축을 세우는 것부터 시작하겠습니다. 이번 1편은 그 지도이자 시리즈의 목차 역할을 합니다.
이 글은 프론트엔드 통신 방식 시리즈의 1편입니다.
- 1편: 전체 지도와 선택 축 (현재 글)
- 2편: HTTP 기반 실시간 — Polling, Long Polling, SSE
- 3편: WebSocket 심화 — 양방향, 확장, 트레이드오프
- 4편: gRPC와 gRPC-Web, Protocol Buffers
- 5편: GraphQL과 통신 방식 최종 선택 가이드
미리 밝혀두면, 이 시리즈는 모든 방식을 대규모 운영에서 직접 다뤄본 경험담이 아닙니다. 공식 문서와 명세를 바탕으로 개념을 정리하되, 주장만 늘어놓기보다 가능한 부분은 바이트 수 계산이나 코드 예시로 그 자리에서 직접 따져보는 방식으로 풀어냅니다. 그래서 "해봤더니 이랬다"가 아니라, **"명세상·계산상 이렇게 되니 이런 트레이드오프가 생긴다"**를 보여주는 데 무게를 둡니다. 같은 이유로, 시니어 프론트엔드라면 무엇을 기준으로 따질지의 관점을 함께 담았습니다.
한눈에 보면
- 통신 방식은 외워야 할 목록이 아니라, 몇 개의 축으로 분류할 수 있는 스펙트럼입니다.
- 가장 중요한 축은 누가 먼저 말하는가(방향성), 연결을 얼마나 오래 유지하는가(연결 수명), 그리고 메시지를 어떤 형태로 표현하는가입니다.
- REST는 "요청-응답" 모델의 기준점이고, 나머지는 대부분 REST가 잘 못 하는 부분을 메우려는 시도입니다.
- 실시간이라는 단어는 보통 세 가지(서버 푸시, 양방향, 낮은 지연) 중 하나를 뜻하는데, 셋은 요구사항이 전혀 다릅니다.
- 시니어의 선택 기준은 "최신 기술인가"가 아니라 운영 비용, 인프라 호환성, 팀의 디버깅 능력, 실패 모드입니다.
- 대부분의 서비스는 단일 방식이 아니라 여러 방식의 조합으로 끝납니다.
통신 방식을 왜 "지도"로 봐야 할까
각 기술의 이름(REST, gRPC, WebSocket…)은 구현 기술의 이름입니다. 하지만 설계에서 우리가 실제로 고르는 것은 기술 이름이 아니라 통신의 성격입니다.
같은 "채팅" 기능이라도:
- 메시지를 보내는 행위는 단발성 요청에 가깝고
- 메시지를 받는 행위는 서버가 먼저 밀어주는 푸시에 가까우며
- 타이핑 인디케이터는 굳이 신뢰성이 필요 없는 가벼운 양방향 신호입니다
즉, 하나의 화면 안에서도 통신의 성격이 여러 개로 갈립니다. 이걸 "채팅이니까 WebSocket"이라고 한 단어로 뭉뚱그리면, 정작 중요한 트레이드오프를 놓치게 됩니다.
그래서 기술 이름을 외우기 전에, 성격을 가르는 축을 먼저 세우는 것이 훨씬 오래 남는 자산이 됩니다.
축 1: 누가 먼저 말하는가 (방향성)
가장 근본적인 축입니다. 통신을 시작하고 데이터를 흘려보내는 주체가 누구냐를 봅니다.
graph LR
subgraph "요청-응답 (Client Pull)"
C1[Client] -->|요청| S1[Server]
S1 -->|응답| C1
end
subgraph "서버 푸시 (Server Push)"
S2[Server] -->|이벤트| C2[Client]
end
subgraph "양방향 (Bidirectional)"
C3[Client] <-->|동시| S3[Server]
end- 요청-응답(Client Pull): 클라이언트가 물어봐야 서버가 답한다. REST, GraphQL 쿼리, gRPC 단항 호출이 여기에 속합니다. 웹의 기본값이자 가장 단순한 모델입니다.
- 서버 푸시(Server Push): 서버가 변화가 생겼을 때 클라이언트에게 먼저 알려준다. SSE(Server-Sent Events)가 대표적입니다. 알림, 실시간 대시보드, 진행률처럼 데이터가 한 방향으로만 흐를 때 적합합니다.
- 양방향(Bidirectional): 클라이언트와 서버가 언제든 서로 먼저 말할 수 있다. WebSocket, gRPC 양방향 스트리밍이 여기 속합니다. 채팅, 협업 편집, 게임처럼 양쪽이 대등하게 대화할 때 필요합니다.
여기서 시니어가 가장 자주 잡아내는 실수가 있습니다. **"서버 푸시면 충분한데 양방향을 도입하는 것"**입니다. 양방향은 거의 항상 더 비싸고(연결 관리, 상태, 확장), 디버깅이 어렵습니다. "사용자에게 보여주기만 하면 되는" 실시간이라면 SSE로 끝나는 경우가 많습니다.
축 2: 연결을 얼마나 오래 유지하는가 (연결 수명)
두 번째 축은 연결의 수명입니다.
- 단명 연결(short-lived): 요청할 때 연결하고 응답받으면 끝낸다. REST가 대표적입니다. 상태가 없어 확장이 쉽고, HTTP 캐시·CDN·로드밸런서 같은 웹 인프라를 그대로 활용합니다.
- 장기 연결(long-lived): 한 번 연결하면 계속 유지하며 그 위로 데이터를 흘려보낸다. WebSocket, SSE, gRPC 스트리밍이 여기 속합니다.
장기 연결은 매력적으로 보이지만, 운영 관점에서 새로운 부담을 가져옵니다.
- 서버는 동시에 열려 있는 연결 수만큼 메모리와 파일 디스크립터를 점유합니다.
- 무상태가 아니므로 수평 확장 시 어느 서버에 붙어 있는지가 문제가 됩니다(sticky session, pub/sub 필요).
- 프록시·로드밸런서·방화벽이 유휴 연결을 끊을 수 있어 하트비트와 재연결 로직이 필수가 됩니다.
이 시리즈 2·3편에서 이 부분을 깊게 다루지만, 핵심 직관은 지금 잡아두면 좋습니다. 장기 연결은 기능이 아니라 운영 비용을 빌리는 것입니다.
축 3: 메시지를 어떻게 표현하는가 (페이로드/계약)
세 번째 축은 데이터를 어떤 형식으로 주고받느냐입니다.
- 텍스트 기반(JSON): 사람이 읽을 수 있고 디버깅이 쉽다. REST, GraphQL, 대부분의 WebSocket 메시지가 JSON을 씁니다.
- 바이너리 기반(Protocol Buffers 등): 작고 빠르고 스키마가 강제된다. gRPC가 대표적입니다.
여기서 갈리는 건 단순히 속도가 아니라 계약(contract)을 어디서 강제하느냐입니다.
- REST는 보통 계약이 문서(혹은 OpenAPI)에 있고, 코드와 분리되어 있어 어긋날 수 있습니다.
- gRPC는
.proto파일이 곧 계약이고, 거기서 타입과 클라이언트 코드를 생성합니다. - GraphQL은 스키마가 계약이고, 클라이언트가 필요한 필드를 직접 고릅니다.
프론트엔드 입장에서 이 축은 타입 안정성과 자동 생성의 정도로 체감됩니다. 손으로 타입을 적느냐, 스키마에서 생성하느냐의 차이입니다.
REST를 기준점으로 삼는 이유
이 모든 축의 원점에 REST가 있습니다. REST는:
- 요청-응답이고 (방향성)
- 단명 연결이며 (연결 수명)
- 보통 JSON 텍스트입니다 (페이로드)
즉 세 축 모두에서 가장 단순하고 가장 웹 친화적인 쪽에 있습니다. 그래서 REST는 "기본값"이고, 나머지 기술들은 대부분 REST의 어떤 약점을 메우려는 시도로 이해할 수 있습니다.
| 기술 | REST가 못 하는 무엇을 메우는가 |
|---|---|
| Polling / Long Polling | 변화를 "거의 실시간"으로 받기 (REST 위에서) |
| SSE | 서버가 먼저 푸시하기 (단방향) |
| WebSocket | 진짜 양방향, 낮은 지연 |
| gRPC | 바이너리 효율, 강한 계약, 스트리밍 |
| GraphQL | 오버페칭/언더페칭, 클라이언트 주도 쿼리 |
이 관점이 중요한 이유는, "REST로 충분한가?"를 먼저 묻는 습관을 만들어 주기 때문입니다. 많은 경우 답은 "그렇다"이고, 그러면 가장 단순하고 운영이 쉬운 선택을 하게 됩니다. REST가 부족한 지점이 명확할 때만 다음 단계로 넘어가면 됩니다.
REST 자체의 개념이 흐릿하다면 먼저 REST API란 무엇인가와 REST API는 HTTP 의미를 어떻게 잘 활용하는가를 읽고 오면 이 시리즈가 훨씬 잘 붙습니다.
"실시간"이라는 단어를 분해하기
설계 회의에서 가장 위험한 단어가 "실시간"입니다. 같은 단어로 전혀 다른 세 가지를 말하기 때문입니다.
- 서버 푸시가 필요한가? — 사용자가 새로고침하지 않아도 화면이 갱신되어야 하는가. (알림, 대시보드)
- 양방향이 필요한가? — 클라이언트도 수시로, 그리고 즉시 서버에 보내야 하는가. (채팅, 협업)
- 낮은 지연이 필요한가? — 수십 ms 단위의 지연이 사용자 경험을 좌우하는가. (게임, 실시간 거래)
이 세 질문에 답하면 기술이 거의 결정됩니다.
graph TD
A[실시간이 필요하다] --> B{서버가 먼저 보내야 하나?}
B -->|아니오| C[REST + 적절한 캐시/재요청]
B -->|예| D{클라이언트도 자주 보내나?}
D -->|아니오| E[SSE]
D -->|예| F{초저지연/바이너리 중요?}
F -->|아니오| G[WebSocket]
F -->|예| H[WebSocket + 바이너리 / 전용 프로토콜]이 그림은 단순화한 것이지만, "실시간 → 무조건 WebSocket"이라는 반사신경을 끊는 것만으로도 설계 품질이 크게 올라갑니다.
시니어가 실제로 보는 기준
기술 비교표는 인터넷에 많습니다. 하지만 실무에서 결정을 좌우하는 것은 표에 잘 안 나오는 항목들입니다.
1. 운영 비용과 실패 모드
- 이 방식이 실패할 때 어떻게 실패하는가? REST는 요청 하나가 실패할 뿐이지만, WebSocket은 연결 전체가 끊기고 그 위의 모든 상태가 날아갑니다.
- 재연결, 재시도, 중복 메시지, 순서 보장을 누가 책임지는가? 장기 연결 기술은 이 책임을 대부분 애플리케이션 코드로 떠넘깁니다.
2. 인프라 호환성
- 기존 CDN, 캐시, API 게이트웨이, WAF, 로드밸런서가 이 방식을 그대로 지원하는가?
- gRPC-Web은 브라우저에서 그냥 안 되고 프록시(Envoy 등)가 필요합니다. SSE는 일부 프록시가 버퍼링해 버립니다. 이런 인프라 마찰이 종종 기술 선택을 뒤집습니다.
3. 디버깅과 관측 가능성
- 브라우저 DevTools에서 바로 들여다볼 수 있는가? REST·SSE·GraphQL은 쉽고, 바이너리 gRPC는 어렵습니다.
- 문제가 생겼을 때 팀이 추적할 수 있는 도구와 경험이 있는가?
4. 팀과 생태계
- 백엔드가 이미 무엇을 쓰는가? 코드 생성·타입 공유 파이프라인이 있는가?
- 새 팀원이 이해하는 데 드는 비용은?
이 기준들의 공통점은, "기술적으로 가능한가"가 아니라 "우리가 1년 동안 운영할 수 있는가"를 묻는다는 점입니다. 통신 방식 선택은 화려함의 문제가 아니라 지속 가능성의 문제에 가깝습니다.
비용을 숫자로 가늠하기
추상적인 "비싸다/싸다" 대신, 봉투 뒷면 계산(back-of-envelope)으로 감을 잡으면 결정이 단단해집니다. 정확한 수치가 아니라 자릿수를 보는 게 목적입니다.
폴링은 사용자 수 × 빈도로 비용이 곱해진다
동시 사용자 U명이 간격 I초로 폴링하면, 서버가 받는 요청은 초당 U / I건입니다.
- 사용자 1,000명, 30초 간격 → 초당 약 33건. 대부분의 서버가 가볍게 처리합니다.
- 사용자 50,000명, 3초 간격 → 초당 약 16,700건. 이 중 대다수가 "변화 없음"을 확인하는 헛요청입니다.
여기서 핵심 직관: 폴링 간격을 절반으로 줄이면 비용은 2배가 됩니다. "좀 더 실시간처럼" 간격을 줄이는 손잡이는 공짜가 아니라 곱셈입니다. 이 곡선이 임계점을 넘으면, 변화가 있을 때만 보내는 SSE/푸시가 오히려 싸집니다. 비용이 사용자 수에 비례(폴링)하느냐, 실제 이벤트 수에 비례(푸시)하느냐의 차이입니다.
장기 연결은 "할 일이 없어도" 자원을 점유한다
REST 요청은 처리되는 순간에만 자원을 씁니다. 반면 WebSocket/SSE 연결은 아무 데이터가 안 흘러도 열려 있는 내내 메모리·파일 디스크립터·(종종) 워커 슬롯을 점유합니다.
- 동시 연결 10만 개라면, 연결당 수십 KB만 잡아도 수 GB가 그냥 "연결 유지"에 들어갑니다.
- 거기에 재연결 폭주(배포 시), pub/sub 백플레인, presence 추적이 더해집니다(3편).
그래서 같은 "실시간"이라도 비용 구조가 다릅니다. 폴링은 요청당 비용, 장기 연결은 연결당 상주 비용입니다. 트래픽 패턴(사용자는 많지만 이벤트는 드문가? 반대인가?)에 따라 어느 쪽이 싼지가 뒤집힙니다.
페이로드 절감은 자릿수를 먼저 보라
"바이너리(protobuf)가 JSON보다 빠르다"는 맞지만, 얼마나 중요한지는 트래픽 성격에 달렸습니다. 큰 JSON 한 덩이를 가끔 받는 화면에서는 gzip된 JSON과 protobuf의 차이가 작습니다. 반면 작은 메시지를 초당 수천 번 보내는 경우엔 누적 차이가 큽니다. 절감액이 그걸 위해 치르는 복잡도(코드 생성, 디버깅 어려움)보다 큰가를 먼저 따져야 합니다(4편에서 상세히 다룹니다).
전체 비교 미리보기
각 편에서 깊게 다루겠지만, 한 장으로 미리 보면 다음과 같습니다. (자세한 트레이드오프는 5편에서 종합합니다.)
| 항목 | REST | Long Polling | SSE | WebSocket | gRPC(-Web) | GraphQL |
|---|---|---|---|---|---|---|
| 방향성 | 요청-응답 | 요청-응답 | 서버 푸시 | 양방향 | 단항/스트리밍 | 요청-응답(+구독) |
| 연결 | 단명 | 유사 장기 | 장기 | 장기 | 장기(스트림) | 단명(+WS 구독) |
| 페이로드 | JSON | JSON | 텍스트 | 자유 | 바이너리 | JSON |
| 브라우저 기본 지원 | ✅ | ✅ | ✅ | ✅ | ⚠️ 프록시 필요 | ✅(HTTP 위) |
| 캐시/CDN 친화 | ✅ 높음 | ⚠️ | ⚠️ | ❌ | ❌ | ⚠️ |
| 디버깅 난이도 | 쉬움 | 쉬움 | 쉬움 | 중간 | 어려움 | 중간 |
| 주된 강점 | 단순·표준 | 호환성 | 단방향 실시간 | 양방향 | 효율·계약 | 유연한 쿼리 |
표의 ⚠️와 ❌가 모여 있는 칸이 바로 운영 비용이 숨어 있는 자리입니다. 이 시리즈는 그 칸들을 하나씩 열어볼 예정입니다.
선택은 생각보다 되돌리기 어렵다
마지막으로, 시니어가 통신 방식 결정을 신중하게 다루는 진짜 이유를 짚겠습니다. 통신 방식은 한 번 고르면 바꾸기 어렵습니다. 그것이 클라이언트의 상태 관리, 에러 처리, 캐시, 인증 흐름까지 광범위하게 물들이기 때문입니다.
예를 들어 REST에서 WebSocket으로 옮긴다는 건 단순히 "전송 계층을 교체"하는 게 아닙니다.
- 요청-응답 기반의 에러 처리(
try/catch+ 상태 코드)가 연결 단위 이벤트 핸들링으로 바뀝니다. - HTTP 캐시·React Query 같은 요청 캐시 전략이 메시지 기반 상태 동기화로 바뀝니다.
- 무상태 인증(매 요청 토큰)이 연결 수명 동안의 인증 갱신으로 바뀝니다(3편).
- 백엔드는 무상태 확장에서 sticky session + pub/sub로 인프라가 바뀝니다.
즉 통신 방식 하나가 프론트·백·인프라에 걸친 아키텍처 결정입니다. 그래서 "일단 WebSocket으로 해두고 나중에 단순화하자"는 거의 일어나지 않습니다. 반대 방향(단순한 것에서 출발해 필요할 때 더하기)이 훨씬 안전한 이유입니다.
이 시리즈가 각 방식을 깊게 파는 것도 이 때문입니다. 되돌리기 어려운 결정일수록, 고르기 전에 트레이드오프를 충분히 알아야 합니다. 잘못된 선택의 비용은 버그 하나가 아니라 운영 기간 내내 따라오는 구조적 부담으로 나타납니다(각 편의 "미리 알아두면 좋은 함정" 절에서 더 구체적으로 다룹니다).
자주 하는 오해
1. 최신 기술일수록 더 좋은 선택이다
아닙니다. gRPC나 WebSocket이 REST보다 "발전된" 기술이라서 우월한 게 아니라, 다른 문제를 푸는 도구일 뿐입니다. 단순한 CRUD에 WebSocket을 쓰면 그냥 더 비싼 REST가 됩니다.
2. 실시간이면 무조건 WebSocket이다
아닙니다. 서버가 보여주기만 하면 되는 경우가 훨씬 많고, 그때는 SSE나 폴링이 더 단순하고 안정적입니다.
3. 통신 방식은 백엔드가 정하는 것이다
아닙니다. 통신 방식은 곧 프론트엔드의 상태 관리, 에러 처리, 재연결, 캐시 전략을 결정합니다. 프론트엔드가 트레이드오프를 이해하고 함께 정해야 합니다.
4. 하나를 고르면 끝이다
아닙니다. 실무 서비스는 대부분 REST(대다수 요청) + SSE 또는 WebSocket(일부 실시간) + 필요 시 GraphQL/gRPC의 조합으로 수렴합니다. 핵심은 "각 화면에 맞는 방식을 고르는 감각"입니다.
정리하면
통신 방식은 외워야 할 기술 목록이 아니라, 몇 개의 축으로 분류되는 스펙트럼입니다.
짧게 정리하면:
- 세 가지 축 — 방향성, 연결 수명, 페이로드 — 으로 모든 방식을 같은 저울에 올릴 수 있습니다.
- REST는 가장 단순한 기준점이고, 나머지는 REST의 약점을 메우는 도구입니다.
- "실시간"은 서버 푸시 / 양방향 / 저지연 셋으로 분해해야 올바른 기술이 보입니다.
- 시니어의 선택 기준은 최신성이 아니라 운영 비용, 인프라 호환성, 실패 모드, 팀 역량입니다.
다음 편부터는 이 지도 위의 각 지점을 깊게 파고듭니다. 2편에서는 가장 먼저, REST(HTTP) 위에서 실시간을 흉내 내는 방법 — 폴링과 SSE — 부터 시작하겠습니다.
