REST + JSON 은 "사람이 읽기 편하다" 가 최대 강점. 그러나 내부 microservice 통신 — 사람이 읽을 일 없고 latency · throughput 이 중요한 환경 — 에서는 JSON parse · text format · HTTP/1.1 의 head-of-line blocking 이 비용. gRPC 는 Google 이 2015 년에 공개한 RPC 프레임워크로, protobuf 의 binary wire format + HTTP/2 의 multiplexed stream + 코드 생성으로 이 비용을 없앤다. 이 가이드는 gRPC 가 실제로 동작하는 방식, 4 가지 RPC 모드, REST 대비 강점·약점, 그리고 브라우저에서 직접 못 쓰는 이유를 정리한다.
전체 그림
.proto 파일 (스키마)
│
│ protoc + plugin
▼
client stub server skeleton
(Java/Go/Py/…) (Java/Go/Py/…)
│ ▲
│ method call │ method impl
▼ │
┌─────────────────────────────┐
│ gRPC runtime │
│ ┌─────────────────────────┐ │
│ │ protobuf encode/decode │ │
│ ├─────────────────────────┤ │
│ │ HTTP/2 frames │ │ ← multiplexed streams
│ ├─────────────────────────┤ │
│ │ TCP + TLS │ │
│ └─────────────────────────┘ │
└─────────────────────────────┘
핵심: 개발자는 .proto 하나 정의 → 양쪽 언어로 stub 생성 → 그 stub 의
메서드 호출은 "그냥 함수처럼" 보이는 RPC..proto 와 코드 생성
// user.proto
syntax = "proto3";
package myapp;
service UserService {
rpc GetUser (GetUserRequest) returns (User);
rpc ListUsers (ListUsersRequest) returns (stream User); // server stream
rpc UpdateProfile (stream ProfilePatch) returns (User); // client stream
rpc Chat (stream Message) returns (stream Message); // bidi
}
message GetUserRequest { int64 id = 1; }
message User { int64 id = 1; string name = 2; string email = 3; }
// 컴파일
protoc --go_out=. --go-grpc_out=. user.proto
protoc --python_out=. --grpc_python_out=. user.proto
// Go 의 client 사용
client := pb.NewUserServiceClient(conn)
user, err := client.GetUser(ctx, &pb.GetUserRequest{Id: 42})
// → 일반 함수처럼 보이지만 내부에선 protobuf 인코딩 + HTTP/2 호출Protobuf — 왜 JSON 보다 빠른가
JSON:
{"id":42,"name":"jade","email":"x@y.com"}
→ 38 bytes, parse 비용 큼 (string → number, key matching)
Protobuf (wire format):
08 2a 12 04 6a 61 64 65 1a 07 78 40 79 2e 63 6f 6d
│ │ │ │ jade │ x@y.com
│ │ │ │ field 3, length-delimited (7 bytes)
│ │ field 2, length-delimited (4 bytes)
│ varint(42) = id 값
field 1, varint (tag = 1<<3 | 0)
총 17 bytes. 거의 절반.
장점:
- 작음 (네트워크 비용 ↓)
- 빠름 (field number 로 직접 매핑, key 매칭 X)
- schema 강제 (런타임 typo 0)
단점:
- 사람이 못 읽음 — 디버깅 시 grpcurl / proto reflection 필요
- schema 없이는 의미 없음 — 옛 binary log 파싱 hardHTTP/2 — gRPC 의 두 번째 바닥
HTTP/1.1:
한 TCP 연결 = 한 번에 한 요청 (head-of-line blocking)
요청 100 개 = 연결 100 개 또는 직렬 100 회
HTTP/2:
한 TCP 연결에 multiplexed stream — 100 요청 동시 가능
binary framing — 텍스트 parse 없음
header compression (HPACK) — 같은 헤더 반복 비용 ↓
server push (사용 빈도 낮음)
gRPC 매핑:
1 RPC = 1 HTTP/2 stream
request / response = HEADERS frame + DATA frame들 + 끝맺음 trailer
→ 한 연결로 수천 RPC 동시 처리. 연결 setup 비용 (TLS handshake) 도
한 번만.
cf. REST + HTTP/1.1: 매 요청마다 RTT, keep-alive 로 완화하지만
multiplexing 은 안 됨. HTTP/2 도입한 REST 도 이점 일부 흡수.4 가지 RPC 모드
# 1. Unary — 가장 흔함, REST 의 한 호출과 동등
rpc GetUser (Req) returns (Resp);
client → 1 request, server → 1 response.
# 2. Server Streaming
rpc ListUsers (Req) returns (stream User);
client → 1 request, server → N response (한 stream).
사례: 큰 결과 셋, 진행률 갱신, server-side push.
# 3. Client Streaming
rpc Upload (stream Chunk) returns (UploadResult);
client → N request (한 stream), server → 1 response (끝에).
사례: 파일 업로드, sensor data 수집.
# 4. Bidirectional Streaming
rpc Chat (stream Msg) returns (stream Msg);
client ↔ server 양방향 독립 stream.
사례: 채팅, 실시간 게임, collaborative 편집.
→ REST + HTTP 만으로 streaming 하려면 SSE / WebSocket / long-poll 등
별도 메커니즘 필요. gRPC 는 4 모드 모두 같은 framework 안.Deadline · Cancellation · Metadata
# Deadline (timeout)
ctx, cancel := context.WithTimeout(ctx, 200*time.Millisecond)
client.GetUser(ctx, ...)
→ 200ms 안에 응답 못 받으면 자동 cancel.
→ 전파 (propagation): 그 서버가 또 다른 gRPC 호출하면 deadline
상속 → cascade timeout 자연 처리.
# Cancellation
client 가 cancel → 서버 stream 도 cancel 신호 받음.
→ 불필요 작업 즉시 중단 (장시간 query, big response).
# Metadata (헤더에 해당)
md := metadata.Pairs("authorization", "Bearer …", "trace-id", "abc")
ctx := metadata.NewOutgoingContext(ctx, md)
→ REST 의 헤더와 동일 역할, key/value 쌍.
# 상태 코드
gRPC 자체 status code (12 + 1) — HTTP status 와 다름.
OK / CANCELLED / DEADLINE_EXCEEDED / NOT_FOUND / PERMISSION_DENIED /
RESOURCE_EXHAUSTED / UNAVAILABLE / INTERNAL …REST 대비 — 언제 무엇
| 축 | REST + JSON | gRPC + protobuf |
|---|---|---|
| payload 크기 | 크다 (text) | 작다 (binary) |
| parse 비용 | 크다 | 작다 |
| schema 강제 | OpenAPI 별도 | .proto 자체가 schema |
| streaming | SSE / WS 별도 | 네이티브 4 모드 |
| multiplexing | HTTP/2 필요 | HTTP/2 기본 |
| 사람 가독성 | 높다 (curl 가능) | 낮다 (grpcurl 등 필요) |
| browser 지원 | 네이티브 fetch | X (gRPC-Web 필요) |
| cache 친화 | HTTP cache 활용 | cache X (POST 전제) |
| 외부 노출 | 표준 (모두 익숙) | 드뭄 (보통 gateway 변환) |
브라우저의 한계 — gRPC-Web
브라우저 fetch / XHR 는 HTTP/2 의 일부 기능 (trailer header, raw
frame 제어) 에 접근 불가. 그래서 순수 gRPC 를 직접 호출 불가.
해결: gRPC-Web
- 브라우저 ↔ proxy (Envoy / grpc-web-proxy) 사이는 HTTP/1.1 또는
HTTP/2 의 제한 subset
- proxy ↔ backend 사이는 진짜 gRPC
- streaming 의 일부 (client / bidi) 는 미지원 또는 트릭
→ 그래서 public-facing API 는 보통 REST 또는 GraphQL,
내부 service-to-service 만 gRPC 가 흔한 패턴.
→ Connect-RPC / Twirp 같은 변형은 브라우저에서 직접 호출 가능하게
설계 (HTTP/1.1 + JSON 도 지원).흔한 함정
- schema breaking change — protobuf field number 는 영구. 절대 재사용 금지. 삭제는 reserved 로 보호. type 변경도 호환성 깨짐 (int32 → int64 OK, int32 → string X).
- HTTP status 와 gRPC status 혼동 — gRPC 는 전송 성공이면 HTTP 200, 실제 응답의 OK / NOT_FOUND 는 gRPC trailer 의 status code. monitoring 시 둘 다 봐야.
- load balancer 호환 — HTTP/2 multiplexed connection 은 L4 load balancer 와 안 맞음 (한 connection 이 한 서버 고정 → 부하 unbalance). L7 (Envoy / nginx 1.13+) 필요. 또는 client-side load balancing (xDS).
- deadline 미설정 — 클라이언트가 deadline 안 박으면 hang 가능. 모든 RPC 에 deadline 강제하는 게 표준 패턴.
- error 모델 단순함 — gRPC status code 13 개 만으로는 도메인 에러 표현 부족. google.rpc.Status 의 details (Any) 로 구조화 에러 첨부.
- 외부 노출 시 인증 — gRPC 는 metadata 로 Bearer token 가능. 그러나 IAM / OAuth2 통합은 직접 코드 필요.
oauth2-explained가이드 참조. - generated code 의 build 부담 — .proto 변경 시 모든 언어 stub 재생성. monorepo + buf 같은 도구로 관리.
마무리
gRPC 의 강점은 한 줄 — 강한 schema + binary wire + HTTP/2 multiplex + streaming + 다언어 코드 생성. 이 4 가지가 같이 쌓이는 환경 (수많은 내부 서비스, 다언어, 높은 throughput) 에서 REST 대비 압도적.
반대로 외부 public API, 브라우저 직접 호출, curl 로 디버깅 많은 환경에서는 REST + JSON 이 여전히 유리. 실전 패턴 — 내부는 gRPC, edge gateway 에서 REST/GraphQL 로 변환해 외부 노출. Cache·rate-limit 등 HTTP 기반 부가 기능 (cors-explained, rate-limiting-strategies) 은 그 gateway 에서 처리.