본문 바로가기
Backend2026년 9월 4일17분 읽기

gRPC vs REST vs GraphQL — 2026 API 프로토콜 선택 가이드

YS
김영삼
조회 7
gRPC vs REST vs GraphQL — 2026 API 프로토콜 선택 가이드

외부에 공개하고 브라우저가 직접 붙는 API면 REST, 프런트가 화면마다 다른 데이터 조합을 원하면 GraphQL, 서비스끼리 낮은 지연으로 대량 통신하면 gRPC다.

셋 다 "잘 만든 API"를 만들 수 있다. 문제는 뭐가 더 좋냐가 아니라, 내 트래픽 모양과 팀 상황에 뭐가 덜 아프냐다. 2026년 기준으로 성능·스키마·스트리밍·브라우저 지원·오버페칭·툴링을 하나씩 뜯어보고, 내부 API와 외부 API를 나눠서 결정 기준을 정리했다.

나는 이 세 개를 다 프로덕션에서 굴려봤다. 결제 서비스 백엔드는 gRPC로 서비스 메시를 짰고, 모바일 앱 백엔드는 GraphQL로 화면 단위 응답을 내렸고, 서드파티에 열어주는 공개 API는 결국 REST로 돌아왔다. 각각 이유가 있었고, 반대로 각각 "여기선 쓰지 말걸" 했던 순간도 있었다. 그 경험을 최대한 솔직하게 풀어보겠다.

한 문장 요약: 셋의 근본 차이

가장 큰 차이는 "무엇을 규약으로 삼느냐"다. REST는 리소스와 HTTP 동사를 규약으로 삼고, GraphQL은 클라이언트가 원하는 데이터 그래프를 규약으로 삼고, gRPC는 메서드 호출(RPC)과 바이너리 계약을 규약으로 삼는다. 이 관점 하나만 잡아도 나머지 특성은 대부분 따라온다.

  • RESTGET /users/42처럼 URL이 명사, HTTP 메서드가 동사. JSON over HTTP/1.1이 기본. 캐시·프록시·브라우저와 궁합이 제일 좋다.
  • GraphQL — 엔드포인트는 /graphql 하나. 클라이언트가 쿼리로 필요한 필드만 골라 가져온다. 화면마다 데이터 요구가 다른 프런트엔드에 강하다.
  • gRPC.proto에 서비스와 메시지를 정의하고 코드 생성. HTTP/2 위에서 Protocol Buffers 바이너리로 오간다. 스트리밍과 저지연이 강점.

핵심 수치로 보는 감(感)

"얼마나 빠른가"는 워크로드에 따라 크게 갈리니 숫자를 절대치로 믿진 말고, 자릿수 감각으로만 받아들이길. 아래는 공개된 일반적 특성과 내가 여러 프로젝트에서 반복적으로 관찰한 경향을 정리한 것이다.

항목 REST(JSON) GraphQL gRPC
직렬화 페이로드 크기기준(100%)필드 선택 시 30~70%보통 20~50%(바이너리)
서비스간 호출 지연(같은 데이터)기준REST와 유사~약간 느림흔히 2~5배 빠름(고빈도일수록 격차↑)
스트리밍SSE/청크로 단방향만 실용적Subscription(주로 WebSocket)양방향 스트리밍 네이티브
브라우저 직접 호출그냥 됨그냥 됨gRPC-Web/프록시 필요
사람이 curl로 디버깅아주 쉬움쉬움(POST 본문 필요)어려움(grpcurl 필요)
참고 "gRPC가 2~5배 빠르다"는 건 초당 수천 건 이상의 서비스간 호출에서 두드러진다. 하루 몇백 건 부르는 관리자 API라면 이 차이는 사실상 안 보인다. 성능 때문에 gRPC를 고르기 전에, 그 성능이 실제로 병목인지부터 확인하는 게 순서다.

3자 비교 표: 한눈에 보는 트레이드오프

아래 표가 이 글의 핵심이다. 각 축이 왜 그런지는 이어지는 섹션에서 하나씩 설명한다. 표만 보고 결정하지 말고, "내 상황에서 이 축이 중요한가"를 같이 생각하자.

비교 축 REST GraphQL gRPC
전송 규약HTTP/1.1·JSONHTTP·JSON(단일 POST)HTTP/2·Protobuf
스키마/계약선택(OpenAPI)강제(SDL)강제(.proto)
타입 안전성약함(문서 의존)강함(코드젠 가능)매우 강함(코드젠 기본)
오버페칭흔함없음(필드 선택)있음(메시지 고정)
N+1/여러 리소스호출 여러 번한 쿼리로 합침메서드별 호출
스트리밍SSE(단방향)Subscription양방향 네이티브
브라우저최상최상gRPC-Web 필요
HTTP 캐시GET 캐시 그대로까다로움(POST)거의 불가(앱 캐시)
학습 곡선낮음중간(서버 리졸버 복잡)중간(툴체인·빌드)
가장 잘 맞는 곳공개 API·CRUD복잡한 프런트엔드 BFF내부 마이크로서비스

성능 — 바이너리 vs 텍스트, 그리고 HTTP/2

순수 성능만 놓고 보면 gRPC가 앞선다. 이유는 두 가지다. 첫째, Protocol Buffers는 바이너리라 JSON보다 직렬화가 빠르고 페이로드가 작다. 둘째, gRPC는 HTTP/2를 기본으로 쓰기 때문에 하나의 커넥션에서 요청을 멀티플렉싱하고, 헤더도 압축한다. 커넥션을 매번 새로 여는 오버헤드가 사라진다.

다만 오해하기 쉬운 게 있다. REST도 HTTP/2로 서빙할 수 있고, JSON 대신 그냥 텍스트를 gzip으로 눌러도 크기 차이는 꽤 줄어든다. 그래서 "REST는 느리다"가 아니라 "gRPC는 고빈도 서비스간 통신에서 누적 이득이 크다"가 정확한 표현이다. 나는 초당 만 건 넘게 오가는 내부 집계 파이프라인을 REST에서 gRPC로 바꿨을 때 CPU가 눈에 띄게 내려가는 걸 봤다. 반대로 사용자당 몇 초에 한 번 부르는 API에선 아무 차이도 못 느꼈다.

참고 GraphQL의 성능은 프로토콜이 아니라 리졸버 구현에 달렸다. 필드를 잘게 나눠 가져오면 서버가 그만큼 여러 데이터소스를 조회한다. DataLoader로 배치·캐시를 안 넣으면 순식간에 N+1 지옥에 빠진다. GraphQL이 느린 게 아니라, 느리게 짜기 쉬운 것이다.

스키마와 타입 안전성 — 계약을 강제하느냐

타입 안전성은 gRPC와 GraphQL이 REST를 크게 앞선다. 둘 다 스키마가 강제되기 때문이다. gRPC는 .proto가 곧 계약이고, GraphQL은 SDL이 계약이다. 여기서 서버·클라이언트 코드를 자동 생성하면 오타나 타입 불일치는 컴파일 단계에서 걸린다. REST도 OpenAPI로 같은 걸 할 수 있지만, 스키마 작성이 선택이라 현실에선 문서와 실제 응답이 어긋나는 일이 잦다.

gRPC의 .proto는 이렇게 생겼다. 이 파일 하나로 Go·TypeScript·Python 클라이언트를 다 뽑아낸다.

// user.proto — 하나의 계약, 여러 언어로 코드 생성
syntax = "proto3";
package user.v1;

service UserService {
  // 단건 조회
  rpc GetUser(GetUserRequest) returns (User);
  // 서버 스트리밍: 여러 유저를 흘려보냄
  rpc ListUsers(ListUsersRequest) returns (stream User);
}

message GetUserRequest {
  int64 id = 1;
}

message User {
  int64 id = 1;
  string name = 2;
  string email = 3;
  repeated string roles = 4; // 반복 필드 = 배열
}

GraphQL은 같은 걸 SDL로 표현한다. 여기선 클라이언트가 필드를 고를 수 있다는 게 핵심 차이다.

# schema.graphql — 필드 선택이 클라이언트 몫
type User {
  id: ID!
  name: String!
  email: String!
  roles: [String!]!
}

type Query {
  user(id: ID!): User
}

# 클라이언트 쿼리: name만 필요하면 name만 받는다
# query { user(id: "42") { name } }
주의 Protobuf는 필드 번호(= 1)가 계약의 핵심이다. 필드 이름은 바꿔도 되지만 번호를 재사용하거나 바꾸면 기존 클라이언트가 데이터를 오독한다. 지운 필드 번호는 reserved로 막아두는 습관이 안전하다. 이거 모르고 번호 돌려쓰다 데이터 깨진 사고, 나도 한 번 냈다.

오버페칭·언더페칭 — GraphQL이 태어난 이유

오버페칭 문제에선 GraphQL이 압도적이다. REST에서 GET /users/42를 부르면 이름만 필요해도 이메일·주소·가입일까지 다 온다(오버페칭). 반대로 유저와 그 유저의 최근 주문을 같이 그리려면 /users/42/users/42/orders를 따로 불러야 한다(언더페칭 → 라운드트립 증가). 모바일처럼 화면마다 데이터 조합이 다르고 네트워크가 비싼 환경에서 이게 진짜 아프다.

GraphQL은 이걸 한 방에 푼다. 클라이언트가 필요한 필드와 중첩 관계를 한 쿼리에 적으면 서버가 딱 그만큼 돌려준다.

query DashboardData {
  user(id: "42") {
    name                 # 필요한 필드만
    orders(last: 3) {    # 중첩 관계도 한 번에
      id
      total
    }
  }
}

단, 공짜는 아니다. 이 유연함의 대가로 서버는 임의의 쿼리 조합을 감당해야 한다. 악의적으로 깊게 중첩된 쿼리 하나가 DB를 갈아버릴 수 있어서, 깊이 제한·복잡도 점수·타임아웃을 반드시 걸어야 한다. gRPC와 REST는 메시지/응답이 고정이라 이런 걱정이 없는 대신 오버페칭을 감수한다. 세상에 공짜는 없다.

스트리밍 — 실시간이 필요하면

양방향 실시간 통신은 gRPC가 가장 자연스럽다. HTTP/2 스트림 위에서 클라이언트 스트리밍, 서버 스트리밍, 양방향 스트리밍을 프로토콜 차원에서 지원한다. 채팅, 실시간 위치, 대용량 데이터 파이프라인처럼 커넥션을 열어두고 계속 주고받는 워크로드에 잘 맞는다.

GraphQL은 Subscription으로 서버→클라이언트 실시간 푸시를 지원하지만 보통 WebSocket 위에서 돈다. REST는 원래 요청-응답 모델이라 실시간이 약하다. 단방향 푸시는 SSE(Server-Sent Events)로 그럭저럭 되지만, 양방향이 필요하면 결국 WebSocket을 따로 얹어야 한다. "REST로 실시간"은 대개 REST 밖의 무언가를 덧대는 얘기다.

// gRPC 서버 스트리밍 (Go) — 결과를 흘려보낸다
func (s *server) ListUsers(req *pb.ListUsersRequest,
    stream pb.UserService_ListUsersServer) error {
    for _, u := range s.repo.All() {
        if err := stream.Send(u); err != nil { // 하나씩 전송
            return err
        }
    }
    return nil
}

브라우저와 툴링 — 사람이 만질 수 있느냐

브라우저 친화성은 REST와 GraphQL의 압승이다. 브라우저는 gRPC가 요구하는 HTTP/2 프레임을 JS에서 직접 제어할 수 없어서, gRPC를 웹에서 쓰려면 gRPC-Web + Envoy 같은 프록시가 필요하다. 이 한 겹이 운영 복잡도를 은근히 올린다. 반면 REST와 GraphQL은 fetch로 그냥 부른다.

디버깅 관점도 크다. REST는 브라우저 주소창이나 curl로 즉시 확인된다. GraphQL은 GraphiQL/Apollo 같은 훌륭한 탐색 도구가 있다. gRPC는 바이너리라 grpcurl이 없으면 눈으로 못 본다. 신입이 합류했을 때 학습 비용도 여기서 갈린다.

# REST: 그냥 이거면 끝
curl https://api.example.com/users/42

# gRPC: 리플렉션 켜져 있어야 하고 전용 툴 필요
grpcurl -d '{"id":42}' api.example.com:443 user.v1.UserService/GetUser

# GraphQL: 엔드포인트 하나에 POST
curl -X POST https://api.example.com/graphql \
  -H 'content-type: application/json' \
  -d '{"query":"{ user(id:\"42\"){ name } }"}'

내부 API vs 외부 API — 경계가 기준을 바꾼다

가장 실무적인 결정 기준은 "누가 이 API를 부르는가"다. 내부(서비스간)외부(서드파티·브라우저)냐에 따라 답이 크게 갈린다.

내부 API는 클라이언트를 내가 통제한다. 코드 생성한 스텁을 강제할 수 있고, 프록시를 얹어도 되고, 성능이 곧 비용이다. 여기선 gRPC가 강하다. 스키마 계약이 팀 간 인터페이스 문서 역할까지 해준다. 마이크로서비스가 서로 수천 번씩 부르는 백엔드 메시라면 나는 gRPC를 기본값으로 둔다.

외부 API는 반대다. 누가 어떤 언어·툴로 붙을지 모른다. 진입 장벽이 낮고, curl로 바로 되고, 캐시·CDN과 잘 붙는 REST가 사실상 표준이다. 서드파티 개발자에게 "gRPC 스텁부터 생성하세요"라고 하면 채택률이 떨어진다. 공개 API를 gRPC로 냈다가 결국 REST 게이트웨이를 앞에 붙인 사례를 여럿 봤다.

그리고 브라우저 앞단(BFF)이라면 GraphQL이 빛난다. 프런트가 화면마다 다른 데이터를 원하고, 여러 백엔드 서비스를 하나의 그래프로 합쳐 내려주고 싶을 때. 실제로 잘 굴러가는 조합은 이런 식이다: 브라우저 ↔ GraphQL(BFF) ↔ gRPC(내부 서비스들). 각자 잘하는 자리에 배치하는 것.

참고 "무조건 하나로 통일"하려는 욕심이 오히려 독이 될 때가 많다. 조직이 작으면 REST 하나로 다 하는 게 정답일 수 있고, 규모가 커지면 경계마다 다른 프로토콜을 쓰는 게 자연스럽다. 프로토콜은 신념이 아니라 도구다.

그래서 언제 무엇을 — 결정 가이드

고민을 줄이는 실전 규칙으로 정리하면 이렇다. 애매하면 REST로 시작하는 게 대체로 후회가 적다. 나중에 병목이 명확해지면 그 지점만 바꾸면 된다.

  • REST를 골라라 — 공개/서드파티 API, 표준 CRUD, 캐시가 중요한 읽기 위주, curl 디버깅과 낮은 진입장벽이 중요할 때. 리소스 모델이 자연스러운 도메인.
  • GraphQL을 골라라 — 화면마다 데이터 요구가 제각각인 웹/모바일 프런트, 여러 백엔드를 하나로 합치는 BFF, 오버페칭/언더페칭이 실제 비용일 때. 단, 서버 복잡도·쿼리 방어를 감당할 여력이 있어야 한다.
  • gRPC를 골라라 — 내부 마이크로서비스 간 고빈도 통신, 저지연·저대역폭이 중요할 때, 양방향 스트리밍, 폴리글랏 팀에서 강한 계약이 필요할 때.

반대로 쓰지 말아야 할 때도 분명히 있다. 트래픽이 작은데 성능만 보고 gRPC 넣으면 툴링·프록시 비용만 늘어난다. 단순 CRUD 세 개짜리 서비스에 GraphQL 얹으면 리졸버·캐시 설계 오버헤드가 배보다 배꼽이다. 실시간 양방향이 핵심인데 REST로 우기면 WebSocket을 어설프게 덧대다 복잡해진다. 도구가 문제를 만드는 상황을 피하는 게 실력이다.

자주 묻는 질문

gRPC가 REST보다 항상 빠른가요?

아니요. 고빈도 서비스간 통신에서는 gRPC가 흔히 2~5배 빠르지만, 사용자당 저빈도 호출이나 소규모 트래픽에서는 체감 차이가 거의 없습니다. 성능이 실제 병목인지부터 측정한 뒤 선택하는 게 맞습니다.

공개 API에 gRPC를 써도 되나요?

권장하지 않습니다. 브라우저 직접 호출이 어렵고(gRPC-Web·프록시 필요), 서드파티 개발자의 진입 장벽이 높아 채택률이 떨어집니다. 외부 공개 API는 REST가 사실상 표준이며, 필요하면 내부 gRPC 앞에 REST 게이트웨이를 두는 방식이 일반적입니다.

GraphQL은 REST를 완전히 대체하나요?

아니요. GraphQL은 화면마다 데이터 요구가 다른 프런트엔드나 여러 백엔드를 합치는 BFF에서 강하지만, HTTP 캐시 활용이 까다롭고 서버 리졸버 복잡도와 쿼리 방어(깊이·복잡도 제한) 부담이 있습니다. 단순 CRUD나 캐시 중심 API는 REST가 더 단순합니다.

세 가지를 한 서비스에서 같이 써도 되나요?

네, 오히려 흔한 구성입니다. 브라우저는 GraphQL BFF에 붙고, 그 BFF가 내부 마이크로서비스는 gRPC로, 외부 공개 API는 REST로 노출하는 조합이 실무에서 잘 작동합니다. 경계마다 잘 맞는 프로토콜을 쓰는 게 핵심입니다.

실시간 기능에는 무엇이 가장 적합한가요?

양방향 실시간이면 gRPC의 스트리밍이 가장 자연스럽습니다. 서버에서 클라이언트로의 단방향 푸시는 GraphQL Subscription이나 REST+SSE로도 충분합니다. REST 단독으로 양방향 실시간을 구현하려면 WebSocket을 별도로 얹어야 합니다.

어떤 걸 배우기가 가장 쉽나요?

REST가 진입 장벽이 가장 낮습니다. HTTP 메서드와 URL만 이해하면 시작할 수 있고 curl로 바로 확인됩니다. GraphQL은 서버 리졸버 설계에서, gRPC는 코드 생성 툴체인과 빌드 설정에서 각각 중간 정도의 학습 곡선이 있습니다.

스키마 없이 REST를 쓰면 뭐가 문제인가요?

문서와 실제 응답이 어긋나기 쉽고, 클라이언트 타입 안전성이 약해집니다. OpenAPI 스펙을 작성하고 코드를 생성하면 gRPC·GraphQL 수준의 계약 안정성을 상당 부분 확보할 수 있으니, 규모가 커지면 스펙 작성을 선택이 아닌 필수로 다루길 권합니다.

댓글 0

아직 댓글이 없습니다.
Ctrl+Enter로 등록