gRPC · GraphQL · WebSocket 정리

자주 비교되는 세 가지 통신 방식의 개념, 동작 방식, 장단점을 정리했습니다.

본문

본문 용어 정리 (펼쳐 보기)

이 글 본문에 나온 용어만 한 줄씩 정리했습니다.

  • TCP: 연결을 맺은 뒤 순서 확인과 재전송으로 데이터를 빠짐없이 전달하는 신뢰성 있는 전송 프로토콜입니다.
  • HTTP: 웹에서 요청과 응답을 주고받는 프로토콜입니다.
  • 방화벽(Firewall): 정해진 규칙에 따라 들어오고 나가는 트래픽을 허용하거나 차단하는 보안 장치입니다.
  • 프록시(Proxy): 클라이언트와 서버 사이에서 요청을 대신 전달하는 중계 서버로, 캐싱, 필터링, 주소 숨김에 쓰입니다.
  • 로드밸런서(Load Balancer): 들어오는 요청을 여러 서버에 나눠 보내서 부하를 분산하고 장애를 줄이는 장치입니다.
  • 핸드셰이크(Handshake): 본격적인 통신을 시작하기 전에 양쪽이 연결 조건을 확인하고 합의하는 절차입니다.
  • 양방향(전이중, Full-duplex): 양쪽이 동시에 자유롭게 주고받을 수 있는 방식입니다(예: 전화).
  • 요청/응답(Request/Response): 클라이언트가 요청하면 서버가 응답하는, 항상 클라이언트가 먼저 시작하는 방식입니다.
  • 푸시(Push): 서버가 요청을 받지 않고도 먼저 클라이언트에게 데이터를 보내는 것입니다.
  • 엔드포인트(Endpoint): API가 요청을 받는 주소(URL)입니다.
  • 오버헤드(Overhead): 실제 데이터 전달 외에 추가로 드는 비용(헤더, 연결 설정 등)입니다.
  • RFC: 인터넷 프로토콜의 표준을 정리한 공식 문서입니다(WebSocket은 RFC 6455).
  • 캐시(Cache): 한 번 받은 결과를 저장해 두었다가 다시 요청이 오면 재사용해서 속도를 높이는 것입니다.
  • HTTP 캐싱: 브라우저나 프록시가 Cache-Control 같은 헤더에 따라 응답을 저장하고 재사용하는 방식입니다.
  • curl: 터미널에서 URL로 HTTP 요청을 보내고 응답을 확인하는 명령줄 도구입니다.
  • L4 로드밸런서: IP와 포트 같은 전송 계층 정보만 보고 서버에 트래픽을 나누는 로드밸런서입니다.
  • L7 프록시: HTTP 경로나 헤더 같은 응용 계층 내용을 보고 요청 단위로 나눠 전달하는 프록시(로드밸런서)입니다.
  • 마이크로서비스: 하나의 큰 앱을 작고 독립적인 여러 서비스로 쪼개서 만드는 구조입니다.
  • 디버깅: 프로그램의 오류 원인을 찾아 고치는 작업입니다.
  • 직렬화(Serialization): 메모리 안의 데이터를 전송하거나 저장할 수 있는 바이트나 문자열로 바꾸는 것입니다(반대는 역직렬화).
  • JSON: 사람이 읽을 수 있는 키-값 형태의 텍스트 데이터 형식입니다.
  • 바이너리(Binary) 포맷: 데이터를 사람이 읽을 수 없는 바이트 그대로 인코딩해 작고 빠르지만 눈으로 확인하기는 어려운 형식입니다.
  • Protocol Buffers(Protobuf): Google이 만든 바이너리 직렬화 형식으로, .proto 파일에 구조를 정의합니다.
  • .proto 파일: gRPC의 서비스와 메시지 타입을 정의하는 파일이자 클라이언트와 서버의 약속(계약)입니다.
  • RPC(Remote Procedure Call): 다른 서버에 있는 함수를 내 로컬 함수처럼 호출하는 방식입니다.
  • stub: 원격 함수를 로컬 함수처럼 부를 수 있게 자동 생성되는 클라이언트 쪽 코드입니다.
  • 코드 생성(codegen): 정의 파일(.proto)에서 클라이언트와 서버 코드를 자동으로 만들어 내는 것입니다.
  • HTTP/2: HTTP/1.1을 개선해 한 연결에서 여러 요청을 동시에 처리하고 헤더를 압축하는 프로토콜입니다.
  • 멀티플렉싱(Multiplexing): 하나의 연결로 여러 요청과 응답을 섞어서 동시에 주고받는 기술입니다.
  • 스트리밍(Streaming): 데이터를 한 번에 다 보내지 않고 계속 이어서 흘려보내는 방식입니다.
  • Unary: 요청 하나에 응답 하나를 주고받는 가장 기본적인 gRPC 호출 방식입니다.
  • Deadline: 호출이 이 시간 안에 끝나야 한다는 제한 시간으로, 넘기면 호출이 실패합니다.
  • gRPC-Web: 브라우저에서 gRPC 서버를 호출할 수 있게 해 주는 변형 프로토콜로, 프록시가 필요합니다.
  • 쿼리 언어: 원하는 데이터를 요청하기 위한 전용 문법입니다.
  • Schema / SDL: GraphQL의 타입과 필드를 정의하는 계약이고, SDL(Schema Definition Language)은 이를 쓰는 문법입니다.
  • Query: 데이터를 조회하는 GraphQL 연산입니다.
  • Mutation: 데이터를 생성, 수정, 삭제하는 GraphQL 연산입니다.
  • Subscription: 서버의 변경을 실시간으로 받아 보는 GraphQL 연산으로, 보통 WebSocket 위에서 동작합니다.
  • Resolver: 스키마의 각 필드 값을 실제로 가져오는 서버 쪽 함수입니다.
  • Introspection: 클라이언트가 서버에 스키마 자체를 물어볼 수 있게 하는 기능으로, 도구의 자동 완성에 쓰입니다.
  • Over-fetching: 필요하지 않은 필드까지 받아 오는 문제입니다.
  • Under-fetching: 필요한 데이터가 한 번에 오지 않아 요청을 여러 번 보내야 하는 문제입니다.
  • N+1 문제: 목록을 1번 조회한 뒤 항목마다 추가 쿼리를 N번 날려서 총 N+1번의 조회가 일어나는 비효율입니다.
  • DataLoader: 같은 종류의 조회를 모아 한 번에 처리하고 캐시해서 N+1 문제를 줄이는 도구입니다.
  • depth/complexity 제한: 쿼리의 깊이나 계산 비용에 상한을 두어 지나치게 무거운 쿼리를 막는 방어 장치입니다.
  • Upgrade 핸드셰이크: HTTP 요청으로 시작해서 “프로토콜을 WebSocket으로 바꾸자”고 합의하는 절차입니다.
  • 101 Switching Protocols: 서버가 프로토콜 전환을 승인한다는 HTTP 응답 코드입니다.
  • ws:// / wss://: WebSocket 주소 형식이고, wss는 TLS로 암호화된 연결입니다.
  • stateful: 서버가 이전 연결이나 요청의 상태를 기억하고 있는 방식입니다(반대는 stateless).
  • sticky session: 같은 클라이언트의 요청을 계속 같은 서버로 보내도록 고정하는 설정입니다.
  • 메시지 브로커: 서버들 사이에서 메시지를 중계해 주는 시스템입니다.
  • Redis: 메모리 기반 데이터 저장소로, 발행/구독(pub/sub) 기능을 메시지 브로커처럼 써서 서버들 사이에 메시지를 전달할 수 있습니다.
  • 하트비트(ping/pong): 연결이 아직 살아 있는지 확인하려고 주기적으로 주고받는 신호입니다.

아래 두 게시물에서 다룬 주제 중 gRPC, GraphQL, WebSocket 세 가지만 골라 정리했습니다.

먼저 구분하기

세 가지는 같은 종류가 아닙니다. 그래서 서로 대체재라기보다 문제에 따라 고르는 도구에 가깝습니다.

정체
gRPC RPC 프레임워크 (HTTP/2 + Protocol Buffers)
GraphQL API 쿼리 언어 + 런타임 (전송은 보통 HTTP)
WebSocket 양방향 전송 프로토콜 (HTTP로 시작해 업그레이드)

1. gRPC

Google이 만든 RPC 프레임워크입니다. 원격 서버의 함수를 로컬 함수처럼 호출합니다.

gRPC 구조: .proto에서 클라이언트/서버 코드를 생성하고 HTTP/2 위에서 Protobuf로 통신

동작 방식

  1. .proto 파일에 서비스와 메시지 타입을 정의합니다.
  2. 이 파일에서 클라이언트(stub)와 서버 코드가 자동 생성됩니다.
  3. 호출은 HTTP/2 위에서 Protocol Buffers(바이너리) 로 직렬화되어 오갑니다.
service UserService {
  rpc GetUser (GetUserRequest) returns (User);
}

message GetUserRequest { int64 id = 1; }
message User { int64 id = 1; string name = 2; }

통신 방식 4가지

  • Unary: 요청 1개, 응답 1개
  • Server streaming: 요청 1개, 응답 여러 개
  • Client streaming: 요청 여러 개, 응답 1개
  • Bidirectional streaming: 양쪽이 동시에 계속 주고받기

장점

  • 바이너리 포맷이라 JSON보다 작고 빠릅니다.
  • .proto가 곧 계약서라 강한 타입과 다언어 코드 생성이 됩니다.
  • HTTP/2 멀티플렉싱으로 한 연결에서 여러 요청을 처리합니다.
  • Deadline, 취소, 인증 같은 기능이 기본으로 있습니다.

단점

  • 브라우저에서 직접 호출할 수 없어 gRPC-Web과 프록시가 필요합니다.
  • 바이너리라 눈으로 확인하며 디버깅하기 어렵습니다.
  • HTTP 캐시, curl 같은 일반 도구와 궁합이 좋지 않습니다.
  • HTTP/2 연결을 오래 유지해서, L4 로드밸런서에서는 요청이 한 서버로 쏠릴 수 있습니다. L7 프록시나 클라이언트 측 로드밸런싱이 필요합니다.

어울리는 곳: 서비스 간 내부 통신(마이크로서비스), 지연 시간이 중요한 호출, 스트리밍

2. GraphQL

Facebook이 만든 API 쿼리 언어입니다. 보통 단일 엔드포인트(/graphql) 하나로, 클라이언트가 필요한 필드만 골라 요청합니다.

GraphQL 구조: 클라이언트가 쿼리를 보내면 서버가 resolver로 데이터를 모아 요청한 필드만 JSON으로 응답

해결하려는 문제

  • Over-fetching: 쓰지 않는 필드까지 받아오는 문제
  • Under-fetching: 한 화면을 그리려고 요청을 여러 번 보내는 문제
query {
  user(id: 1) {
    name
    posts { title }
  }
}

요청한 name과 posts.title만 JSON으로 돌아옵니다.

핵심 개념

  • Schema: 타입과 필드를 정의하는 강타입 계약 (SDL)
  • Query / Mutation / Subscription: 조회 / 변경 / 실시간 구독
  • Resolver: 각 필드의 값을 가져오는 함수

장점

  • 요청 한 번에 연관 데이터를 가져옵니다.
  • 스키마가 곧 문서이고, introspection으로 도구가 자동 완성을 지원합니다.
  • 서버를 고치지 않고도 프론트엔드가 응답 모양을 바꿀 수 있습니다.

단점

  • N+1 문제: resolver가 항목마다 쿼리를 날리기 쉽습니다. DataLoader로 배치 처리합니다.
  • 보통 POST 하나로 통신해서 HTTP 캐싱이 어렵습니다.
  • 너무 깊거나 큰 쿼리를 막으려면 depth/complexity 제한이 필요합니다.
  • 에러가 HTTP 200으로 오는 경우가 많아 모니터링에 주의해야 합니다.

어울리는 곳: 웹/모바일 등 클라이언트가 여러 종류이고 데이터 관계가 복잡한 서비스

3. WebSocket

HTTP로 시작해서 Upgrade 핸드셰이크를 거친 뒤, 하나의 TCP 연결 위에서 양방향(full-duplex) 으로 통신합니다. (ws://, wss://, RFC 6455)

WebSocket 구조: HTTP Upgrade 요청과 101 응답 이후 하나의 연결에서 양방향 메시지를 주고받음

동작 방식

  1. 클라이언트가 Upgrade: websocket 헤더로 HTTP 요청을 보냅니다.
  2. 서버가 101 Switching Protocols로 응답합니다.
  3. 이후 연결이 열린 채로 유지되며, 양쪽이 언제든 메시지를 보냅니다.
const ws = new WebSocket('wss://example.com/chat');
ws.onopen = () => ws.send('hello');
ws.onmessage = (e) => console.log(e.data);

장점

  • 서버가 요청 없이도 먼저 데이터를 푸시할 수 있습니다.
  • 연결 이후에는 메시지마다 HTTP 헤더를 반복해서 보내지 않아 오버헤드가 작고 지연이 낮습니다.
  • 브라우저가 기본 지원합니다.

단점

  • 연결이 상태를 가져서(stateful) 서버를 여러 대로 늘리기 어렵습니다. sticky session이나 Redis 같은 메시지 브로커로 서버 간 이벤트를 전달해야 합니다.
  • 재연결과 하트비트(ping/pong) 를 직접 구현해야 합니다.
  • 일부 프록시와 방화벽에서 문제가 생길 수 있습니다.
  • 연결 수만큼 서버 자원을 계속 점유합니다.

어울리는 곳: 채팅, 실시간 협업, 온라인 게임, 시세와 알림

비교

gRPC GraphQL WebSocket
전송 HTTP/2 주로 HTTP (구독은 WebSocket 위) TCP (HTTP 업그레이드)
데이터 형식 Protobuf (바이너리) JSON 자유 (텍스트/바이너리)
통신 방향 요청/응답 + 스트리밍 4종 요청/응답 (+구독) 양방향
브라우저 프록시 필요 바로 가능 바로 가능
연결 요청 단위 요청 단위 영속 연결
강점 속도, 타입 계약 유연한 조회 실시간
약점 디버깅, 브라우저 캐싱, N+1 확장성, 상태 관리

어떻게 고를까

  • 서버끼리 빠르고 타입이 명확하게 통신해야 한다 → gRPC
  • 클라이언트마다 필요한 데이터가 다르고 관계가 복잡하다 → GraphQL
  • 서버가 즉시 푸시하고 양쪽이 계속 주고받아야 한다 → WebSocket
  1. Luci 모니터링 구조
  2. OSI 7계층 실습: HTTP 요청 하나 따라가기
  3. HDF5로 sim 중간 상태 저장·복원
  4. Luci 하네스 통신 구조
  5. gRPC · GraphQL · WebSocket 정리지금 읽는 글
  6. 김근언