REST로 충분한데 왜 gRPC를 쓸까요? 마이크로서비스 사이에서 매초 수천 번 호출이 오갈 때, JSON 파싱 비용이 무시할 수 없어지기 때문입니다.

gRPC는 Google이 만든 고성능 RPC 프레임워크입니다. Protocol Buffers로 데이터를 바이너리 직렬화하고 HTTP/2로 전송하여, REST 대비 소규모 페이로드에서 최대 5배 빠른 성능을 보여줍니다. 다만 2025년 기준 채택률은 14%로, REST(93%)와 공존하는 형태입니다.


RPC란

RPC(Remote Procedure Call) 는 원격 서버의 함수를 마치 로컬 함수처럼 호출하는 패러다임입니다.

PLAINTEXT
// REST 방식 — HTTP 중심 사고
POST /users
Content-Type: application/json
{"name": "김철수", "email": "kim@example.com"}

// RPC 방식 — 함수 호출 중심 사고
userService.createUser(name="김철수", email="kim@example.com")

REST는 "리소스를 조작한다"는 관점이고, RPC는 "함수를 호출한다"는 관점입니다. gRPC는 이 RPC 패러다임의 현대적 구현체입니다.


gRPC의 3가지 핵심

1. Protocol Buffers — 바이너리 직렬화

Protocol Buffers(Protobuf)는 Google이 만든 데이터 직렬화 포맷입니다. JSON보다 작고 빠릅니다.

PROTOBUF
// user.proto — 스키마 정의
syntax = "proto3";

package user;

// 서비스 정의
service UserService {
  rpc CreateUser (CreateUserRequest) returns (CreateUserResponse);
  rpc GetUser (GetUserRequest) returns (User);
  rpc ListUsers (ListUsersRequest) returns (stream User);
}

// 메시지 정의
message CreateUserRequest {
  string name = 1;          // 필드 번호 = 1
  string email = 2;         // 필드 번호 = 2
}

message CreateUserResponse {
  int64 id = 1;
  string name = 2;
  string email = 3;
}

message User {
  int64 id = 1;
  string name = 2;
  string email = 3;
  string created_at = 4;
}

.proto 파일에서 protoc 컴파일러로 Java, Go, Python 등의 코드를 자동 생성합니다.

JSON vs Protobuf 크기 비교:

PLAINTEXT
// JSON (약 82바이트)
{"id":1,"name":"김철수","email":"kim@example.com","createdAt":"2026-03-28"}

// Protobuf (약 35바이트) — 바이너리, 사람이 읽을 수 없음
0A 05 EA B9 80... (필드 번호 + 타입 + 값)

필드 번호(1, 2, 3...)는 한번 정하면 바꾸면 안 됩니다. 바이너리 인코딩에서 이 번호가 식별자로 사용되기 때문에, 변경하면 하위 호환성이 깨집니다.

2. HTTP/2 — 멀티플렉싱과 헤더 압축

gRPC는 HTTP/2를 전송 프로토콜로 사용합니다.

  • ** 멀티플렉싱 **: 하나의 TCP 연결에서 여러 요청/응답을 동시에 처리 (HTTP/1.1의 HOL Blocking 해결)
  • ** 헤더 압축 **: HPACK으로 반복되는 헤더를 압축 (메타데이터 오버헤드 감소)
  • ** 바이너리 프레이밍 **: 텍스트가 아닌 바이너리 프레임으로 전송 (파싱 효율)
  • ** 서버 푸시 **: 요청 없이도 서버가 데이터를 보낼 수 있음 (스트리밍의 기반)

3. 코드 생성 — 타입 안전한 클라이언트/서버

.proto 파일에서 자동 생성된 코드를 사용하므로:

  • 컴파일 시점에 타입 오류를 잡을 수 있습니다
  • API 문서가 .proto 파일 자체입니다
  • 클라이언트와 서버가 같은 스키마를 공유하여 불일치가 없습니다

4가지 통신 패턴

1. Unary RPC (일대일)

가장 기본적인 패턴. 요청 하나에 응답 하나.

PROTOBUF
rpc GetUser (GetUserRequest) returns (User);

2. Server Streaming RPC (서버 → 클라이언트 스트림)

서버가 여러 응답을 스트림으로 전송. 실시간 피드, 대량 데이터 조회에 적합.

PROTOBUF
rpc ListUsers (ListUsersRequest) returns (stream User);

3. Client Streaming RPC (클라이언트 → 서버 스트림)

클라이언트가 여러 요청을 보내고 서버가 하나의 응답을 반환. 파일 업로드, 센서 데이터 수집에 적합.

PROTOBUF
rpc UploadFile (stream FileChunk) returns (UploadResult);

4. Bidirectional Streaming RPC (양방향 스트림)

양쪽 모두 스트림으로 주고받음. 채팅, 실시간 게임에 적합.

PROTOBUF
rpc Chat (stream ChatMessage) returns (stream ChatMessage);

REST는 Unary만 자연스럽게 지원합니다. 스트리밍이 필요하면 WebSocket이나 SSE를 별도로 구현해야 하지만, gRPC는 프레임워크 레벨에서 4가지 패턴을 모두 지원합니다.


gRPC vs REST 비교

항목RESTgRPC
프로토콜HTTP/1.1 ~ 3HTTP/2 (필수)
데이터 포맷JSON (텍스트)Protobuf (바이너리)
스키마선택 (OpenAPI)필수 (.proto)
코드 생성선택필수
스트리밍별도 구현 필요네이티브 지원
브라우저 호환완벽제한적 (프록시 필요)
채택률 (2025)93%14%
성능기준소규모 페이로드에서 최대 5배 빠름

벤치마크 수치 (Kong 2026)

  • 1,000 동시 접속 기준 gRPC의 지연 시간이 REST 대비 45% 낮음
  • 특히 페이로드가 작을수록(마이크로서비스 간 호출) 차이가 큼
  • 대용량 페이로드에서는 차이가 줄어듦

언제 무엇을 쓸 것인가

  • REST: 공개 API, 브라우저 직접 호출, 범용 호환성 필요
  • gRPC: 내부 서비스 간 통신, 저지연 필요, 스트리밍 필요
  • ** 현대적 패턴 **: 외부 → REST, 내부 → gRPC (공존)

Connect Protocol — 브라우저에서 직접 gRPC

기존 gRPC의 가장 큰 한계는 브라우저 호환성이었습니다. HTTP/2의 바이너리 프레이밍을 브라우저가 직접 다룰 수 없어서, gRPC-Web + Envoy 프록시가 필요했습니다.

Connect Protocol(Buf 사)은 이 문제를 해결합니다:

  • 표준 HTTP를 사용하여 프록시 없이 브라우저에서 직접 호출 가능
  • gRPC, gRPC-Web, Connect 세 가지 프로토콜을 하나의 서버에서 동시 지원
  • JSON과 Protobuf 인코딩을 모두 지원
PLAINTEXT
// 기존: 브라우저 → Envoy 프록시 → gRPC 서버
// Connect: 브라우저 → Connect 서버 (프록시 불필요)

Spring Boot에서 gRPC 연동

grpc-spring-boot-starter를 사용하면 Spring Boot에 gRPC를 통합할 수 있습니다.

서버 구현

JAVA
@GrpcService
public class UserGrpcService extends UserServiceGrpc.UserServiceImplBase {

    private final UserService userService;

    public UserGrpcService(UserService userService) {
        this.userService = userService;
    }

    @Override
    public void getUser(GetUserRequest request,
                        StreamObserver<User> responseObserver) {
        // 비즈니스 로직 호출
        var user = userService.findById(request.getId());

        // Protobuf 메시지로 변환하여 응답
        var response = User.newBuilder()
            .setId(user.getId())
            .setName(user.getName())
            .setEmail(user.getEmail())
            .build();

        responseObserver.onNext(response);
        responseObserver.onCompleted();
    }
}

클라이언트 호출

JAVA
@Service
public class UserClient {

    @GrpcClient("user-service")
    private UserServiceGrpc.UserServiceBlockingStub userStub;

    public User getUser(long id) {
        var request = GetUserRequest.newBuilder()
            .setId(id)
            .build();

        // 로컬 함수처럼 호출
        return userStub.getUser(request);
    }
}

공부하다 보니, gRPC는 설정과 빌드 파이프라인이 REST보다 복잡하지만, 한번 세팅하고 나면 타입 안전성과 코드 생성의 편리함이 꽤 큽니다. .proto 파일 하나 수정하면 클라이언트와 서버 코드가 동시에 업데이트되니까요.


HTTP/3과 gRPC의 미래

HTTP/3(QUIC)는 2025년 10월 기준 35% 채택률에 도달했습니다. gRPC는 현재 HTTP/2를 필수로 사용하지만, HTTP/3 지원이 진행 중입니다. QUIC의 0-RTT 연결과 스트림 독립성은 gRPC의 스트리밍 패턴과 자연스럽게 어울립니다.


정리

  • gRPC = Protocol Buffers(바이너리) + HTTP/2(멀티플렉싱) + 코드 생성(타입 안전)
  • 4가지 통신 패턴: Unary, Server Streaming, Client Streaming, Bidirectional
  • REST는 공개 API, gRPC는 내부 서비스 간 통신 — 공존이 현실적인 선택
  • Connect Protocol로 브라우저에서도 프록시 없이 gRPC 호출이 가능해지고 있음
  • .proto 파일의 필드 번호는 절대 변경하면 안 됨 — 하위 호환성의 핵심
댓글 로딩 중...