REST, gRPC, GraphQL - 선택 기준과 각자가 내는 비용
세 가지를 “REST는 무난하고, gRPC는 빠르고, GraphQL은 유연하다”로 요약하면 선택에 쓸 수 없다. 셋은 같은 문제의 다른 답이 아니라 다른 문제를 풀고, 각자 비용을 다른 곳에 놓는다. 그 비용이 어디에 놓이는지가 선택 기준이다.
세 가지가 실제로 다른 점
| REST | gRPC | GraphQL | |
|---|---|---|---|
| 계약 | 느슨함(OpenAPI는 선택) | .proto가 강제 | 스키마가 강제 |
| 전송 | HTTP/1.1 또는 2, JSON | HTTP/2, protobuf | 주로 HTTP/1.1, JSON |
| 호출 모양 | 리소스 중심 | 메서드 호출 | 질의 |
| 응답 형태 | 서버가 정함 | 서버가 정함 | 클라이언트가 정함 |
| 스트리밍 | SSE·WebSocket 별도 | 양방향 내장 | subscription |
| 브라우저 | 그대로 | 프록시 필요(gRPC-Web) | 그대로 |
| 캐싱 | HTTP 캐시 그대로 | 직접 구현 | 어려움(POST 단일 엔드포인트) |
핵심 차이를 한 줄로 줄이면 이렇다. REST는 HTTP의 의미를 쓰고, gRPC는 타입과 성능을 얻는 대신 HTTP의 의미를 버리고, GraphQL은 응답 모양의 결정권을 클라이언트에 넘긴다.
REST가 공짜로 얻는 것
HTTP를 그대로 쓰기 때문에 주변 생태계가 전부 동작한다.
- 브라우저,
curl, 프록시, CDN, API 게이트웨이가 별도 작업 없이 붙는다. - 상태 코드와 메서드 의미가 표준이다. 멱등성과 재시도 규약이 여기서 나온다(멱등한 API 설계).
Cache-Control,ETag로 캐싱이 무료다.- 관측 도구가 URL과 상태 코드 기준으로 이미 만들어져 있다.
대가는 계약이 느슨하다는 것이다. OpenAPI를 쓰지 않으면 필드 하나가 바뀐 것을 컴파일러가 잡아 주지 않는다. 그리고 리소스 모델에 안 맞는 동작(예: “정산 마감을 실행한다”)을 억지로 명사로 만드는 문제가 늘 남는다.
gRPC가 가져오는 것과 잃는 것
.proto 하나에서 서버와 클라이언트 코드가 생성된다. 필드를 지우면 빌드가 깨진다. 서비스가 많고 팀이 여럿일 때 이 강제력이 가장 큰 이점이다.
HTTP/2 위에서 동작하므로 연결 하나에 스트림이 여럿 흐르고, 헤더가 압축되며, 양방향 스트리밍이 기본이다. 직렬화가 바이너리라 payload가 작고 파싱이 빠르다.
잃는 것이 분명하다.
- 브라우저에서 직접 못 쓴다. gRPC-Web과 프록시가 필요하다.
- HTTP 캐싱을 못 쓴다. 캐시가 필요하면 직접 만든다.
- 디버깅이 불편하다. 바이너리라
curl로 못 보고grpcurl같은 도구가 필요하다. - HTTP/2 커넥션 하나에 스트림이 몰려 L4 로드 밸런서가 부하를 못 나눈다. 연결 단위로 분산하는 LB 뒤에서는 한 서버로 쏠린다. L7 프록시나 클라이언트 측 분산이 필요하다.
GraphQL이 옮기는 비용
클라이언트가 필요한 필드만 질의하므로 over-fetching이 사라지고, 화면 하나를 그리기 위한 왕복(under-fetching)도 준다. 화면 요구가 자주 바뀌는 프런트엔드에 유리하다.
비용은 서버로 옮겨간다.
- N+1 질의. 중첩 필드마다 리졸버가 돌면서 DB를 친다. DataLoader 류의 배치·캐시 계층이 사실상 필수다.
- 질의 비용의 상한이 없다. 깊게 중첩된 질의 하나가 서버를 마비시킬 수 있다. 깊이 제한, 복잡도 점수, 영속 질의(persisted query)가 방어 장치다.
- 캐싱이 어렵다. 단일 엔드포인트에 POST라 HTTP 캐시가 안 붙는다.
- 관측이 어렵다. 모든 요청이
POST /graphql이라 URL 기준 지표가 무의미하다. 오퍼레이션 이름을 지표 라벨로 써야 하고, 그러면 카디널리티를 신경 써야 한다.
고르는 기준
질문 순서로 정리하면 이렇다.
- 클라이언트가 브라우저인가? 그렇다면 gRPC는 프록시 비용을 먼저 계산한다.
- 호출자가 내부 서비스인가 외부 개발자인가? 외부 공개 API는 REST가 진입 장벽이 가장 낮다.
- 계약 위반을 빌드 타임에 잡아야 하는가? 서비스 수가 많고 변경이 잦으면 gRPC나 스키마 강제가 값을 한다.
- 화면마다 필요한 데이터가 크게 다른가? 그렇다면 GraphQL의 이점이 실제로 있다. 화면이 몇 개 안 되면 REST 엔드포인트 몇 개가 더 싸다.
- 스트리밍이 필요한가? 양방향이면 gRPC, 서버→클라이언트 단방향이면 SSE로 충분한 경우가 많다.
셋은 배타적이지 않다. 외부는 REST, 내부 서비스 간은 gRPC, BFF 계층만 GraphQL 같은 조합이 흔하다.
이 설명이 깨지는 곳
- “gRPC가 빠르다”는 대개 직렬화와 HTTP/2 이야기다. 전체 지연에서 그 비중이 작다면(DB가 대부분) 체감 차이가 없다.
- protobuf의 호환 규칙을 지켜야 계약의 이점이 산다. 필드 번호 재사용, 타입 변경은 조용히 깨진다(API 버저닝).
- REST도 스키마를 강제할 수 있다. OpenAPI에서 코드를 생성하면 gRPC와 비슷한 강제력을 얻는다. 차이가 절대적이지 않다.
- GraphQL의 유연성은 권한 검사를 어렵게 만든다. 필드 단위 인가를 리졸버마다 걸어야 하고, 한 곳만 빠져도 노출이 된다.
무엇을 재면 확인되는가
- 같은 응답을 JSON과 protobuf로 만들어 크기와 직렬화 시간을 비교한다. 전체 지연에서 차지하는 비중까지 봐야 의미가 있다.
- gRPC를 L4 LB 뒤에 두고 서버별 요청 분포를 본다. 쏠림이 실제로 보인다.
- GraphQL 질의의 깊이를 늘려 가며 DB 질의 수를 센다. DataLoader 전후로.
실무와의 접점
JSP 기반 시스템의 구조 전환에서 조회 경로를 옮길 때 가장 어려웠던 것은 프로토콜이 아니라 기존 응답 계약을 깨지 않는 것이었다. 세 방식 중 무엇을 고르든 그 문제는 남는다. 프로토콜 선택은 계약을 어떻게 강제하고 어떻게 진화시킬지의 문제이지, 성능 비교표의 문제가 아니라는 것이 실무에서 배운 순서다.
정리
- REST는 HTTP의 의미와 생태계를 공짜로 얻고 계약의 느슨함을 대가로 낸다.
- gRPC는 타입 강제와 스트리밍을 얻고 브라우저 접근성, HTTP 캐싱, L4 분산을 잃는다.
- GraphQL은 응답 모양의 결정권을 클라이언트에 주고, N+1과 질의 비용 상한과 관측 문제를 서버가 받는다.
- 고르는 순서는 클라이언트 종류 → 호출자 → 계약 강제 필요성 → 화면 다양성 → 스트리밍이다.
- 셋은 배타적이지 않고, 계층마다 다르게 고르는 것이 일반적이다.
댓글
아직 댓글이 없습니다