본문으로 건너뛰기
홈
기술
기술 전체
프로그래밍68
컴퓨터 과학63
AI48
웹 개발36
인프라33
데이터31
소프트웨어 공학18
소개
← 목록으로웹 개발 › API › gRPC

gRPC

목차

gRPC

gRPC는 다른 프로세스에 있는 기능을 원격 메서드 호출(RPC) 형태로 사용하는 통신 프레임워크다. 클라이언트에서는 메서드를 호출하는 것처럼 보이지만, 실제로는 직렬화·네트워크 전송·서버 실행·응답 수신을 거친다. 따라서 로컬 함수와 달리 지연, 연결 실패, 시간 초과를 설계에 포함해야 한다.

계약부터 정의하기

주문 상태를 읽는 서비스를 만든다고 가정하자. 먼저 .proto 파일에 호출할 메서드와 메시지 구조를 정의한다. Protocol Buffers는 gRPC의 기본 인터페이스 정의·직렬화 방식이며, 다른 데이터 형식을 사용하는 구현도 가능하다.

syntax = "proto3";

package orders.v1;

service OrderService {
  rpc GetOrder(GetOrderRequest) returns (GetOrderResponse);
  rpc WatchOrder(GetOrderRequest) returns (stream OrderEvent);
}

message GetOrderRequest {
  string order_id = 1;
}

message GetOrderResponse {
  string order_id = 1;
  string status = 2;
}

message OrderEvent {
  string order_id = 1;
  string status = 2;
}

GetOrder는 요청 하나에 응답 하나를 돌려주는 일반적인 호출이다. WatchOrder는 요청 하나를 받은 뒤 상태 변경 메시지를 여러 번 전송하는 서버 스트리밍 호출이다. protoc와 언어별 gRPC 플러그인을 사용하면 이 계약에서 클라이언트용 stub과 서버 구현용 코드를 생성할 수 있다. 서버는 선언된 메서드의 실제 처리를 작성한다.

flowchart LR
  A[클라이언트: GetOrder 호출] --> B[생성된 stub]
  B --> C[요청 메시지 직렬화]
  C --> D[HTTP/2 연결]
  D --> E[서버: 요청 복원]
  E --> F[OrderService.GetOrder 실행]
  F --> G[응답 메시지 + gRPC 상태]
  G --> B
  B --> H[클라이언트: 결과 확인]

예를 들어 클라이언트가 order_id = "A-104"를 보내면 서버가 { order_id: "A-104", status: "PAID" }를 반환할 수 있다. 주문이 없다면 비어 있는 성공 응답으로 얼버무리기보다 NOT_FOUND 같은 gRPC 오류 상태를 선택한다. 여기서 주문의 status는 업무 데이터, gRPC의 상태 코드는 호출의 성공·실패를 나타내므로 구분해야 한다.

네 가지 호출 방식

방식메시지 흐름사용 예
Unary요청 1개 → 응답 1개주문 한 건 조회
Server streaming요청 1개 → 응답 여러 개주문 변경 이벤트 구독
Client streaming요청 여러 개 → 응답 1개측정값을 모아 한 번에 집계
Bidirectional streaming양쪽 모두 여러 메시지양방향 실시간 세션

양방향 스트리밍에서는 양쪽이 각자 읽고 쓸 수 있다. 반드시 요청 하나와 응답 하나가 번갈아 나오는 것은 아니며, 각 방향의 메시지 순서는 한 RPC 안에서 유지된다. 긴 스트림은 연결이 끊겼을 때 어디서 다시 시작할지도 애플리케이션이 결정해야 한다.

장점과 비용을 함께 보기

  • 명시적인 계약: 메서드·요청·응답의 타입을 .proto로 공유하고 여러 언어의 코드를 생성할 수 있다. 호환성을 위해 이미 사용 중인 필드 번호를 바꾸거나 재사용하지 않는다. 필드를 삭제하면 해당 번호를 reserved로 남긴다.
  • 효율적인 전송: Protocol Buffers의 바이너리 인코딩과 HTTP/2 연결·스트리밍은 서비스 간 통신에 유용하다. 하지만 모든 REST/JSON API보다 항상 빠르다고 단정할 수는 없다. 메시지 크기, 호출 빈도, 네트워크, 프록시, 직렬화 비용을 실제 부하로 측정해야 한다.
  • 운영 복잡도: 바이너리 메시지는 일반 텍스트 HTTP보다 눈으로 확인하기 어렵다. 브라우저에서 직접 사용할 때는 일반 서버 간 gRPC와 다른 gRPC-Web 구성이 필요할 수 있다. 기존 API 클라이언트와 호환해야 한다면 REST/JSON 진입점을 함께 둘지도 결정해야 한다.
  • 기능의 경계: gRPC는 메타데이터, 상태 코드, 데드라인, TLS 같은 수단을 제공하지만 인증 정책, 서비스 발견, 재시도 기준, 로드 밸런싱과 장애 복구가 애플리케이션 요구에 맞게 자동 완성되는 것은 아니다.

실패를 설계에 넣기

클라이언트는 호출마다 업무에 맞는 데드라인을 정하는 편이 좋다. gRPC는 기본적으로 데드라인을 설정하지 않으므로, 하위 서비스가 응답하지 않으면 오래 기다릴 수 있다. 시간이 지나면 DEADLINE_EXCEEDED가 반환되지만 서버가 이미 작업을 일부 수행했을 수도 있다. 따라서 주문 생성이나 결제처럼 부작용이 있는 메서드를 무조건 재시도하면 중복 처리 위험이 있다. 멱등성 키나 조회·확인 절차를 설계한 뒤 재시도해야 한다.

입력이 잘못되면 INVALID_ARGUMENT, 없는 주문이면 NOT_FOUND, 일시적 서비스 중단이면 UNAVAILABLE처럼 오류 상태를 구분하면 호출자가 대응하기 쉽다. 네트워크를 사이에 둔 호출이라는 사실을 드러내는 계약과 관측 지표가, 단순히 메서드 호출 문법을 쓰는 것보다 중요하다.

참고 자료

계약을 바꿀 때 클라이언트는 무엇을 보는가?

GetOrderResponse에 배송 예정 시각이 필요해졌다고 하자. 기존 필드 번호 1·2를 바꾸지 않고 새 번호를 쓴다. 클라이언트와 서버를 동시에 배포하지 못하는 동안에도, 오래된 클라이언트는 모르는 필드를 건너뛰고 새 클라이언트는 값이 없을 때 기본값을 처리하도록 설계한다.

message GetOrderResponse {
  string order_id = 1;
  string status = 2;
  optional string estimated_delivery_at = 3;
}

optional을 쓰면 값이 없다는 상태와 빈 문자열을 구별하는 데 도움이 된다. 단, 생성 언어와 사용하는 Protobuf 버전에서 제공하는 presence API를 확인한다. 필드를 삭제할 때는 번호를 재사용하지 않도록 reserved 3;처럼 남긴다. 필드 번호는 전송 형식에서 필드를 식별하므로 “순서를 예쁘게 맞추기 위해” 번호를 바꾸는 것은 호환성 파괴다. 상태를 문자열로 둘지 enum으로 둘지도 계약 변경 비용을 고려해 결정한다.

실무에서는 .proto 파일만 바꾸고 끝내지 않는다. 생성 코드의 배포 순서를 정하고, 이전 클라이언트·새 서버, 새 클라이언트·이전 서버 두 조합을 검사한다. 계약 검사와 실제 호출 테스트를 분리하면 “코드는 컴파일되는데 배포 중 한쪽에서 값이 없다”는 문제를 줄일 수 있다.

타임아웃을 한 요청의 결과로 읽기

주문 조회는 재시도가 비교적 쉬운 편이지만, 주문 생성은 같은 요청을 두 번 처리하면 중복 주문이 생긴다. 클라이언트가 데드라인을 넘겨 DEADLINE_EXCEEDED를 받았다는 사실만으로 서버가 작업을 하지 않았다고 단정할 수 없다. 서버가 DB에 저장한 뒤 응답이 늦었을 수도 있다. 클라이언트는 안정적인 요청 ID를 보내고 서버는 같은 ID의 재요청에 이전 결과를 돌려주는 멱등성 계약을 둘 수 있다.

sequenceDiagram
  participant C as 클라이언트
  participant S as 주문 서버
  participant DB as DB
  C->>S: CreateOrder(request_id=R1)
  S->>DB: 주문 저장
  DB-->>S: 저장 완료
  Note over C,S: 응답 지연으로 데드라인 만료
  C->>S: R1 재요청 또는 상태 조회
  S->>DB: R1의 기존 결과 확인
  S-->>C: 같은 주문 ID 반환

그림의 재요청은 서버가 R1을 저장하고 중복 요청을 검사한다는 전제에서만 안전하다. gRPC의 자동 재시도 설정만으로 업무 멱등성이 생기지 않는다. 읽기 RPC라도 하위 서비스에 부하를 만들 수 있어 최대 시도 횟수와 백오프를 정한다. 서버는 취소 신호를 받으면 자신이 시작한 장시간 작업을 중단할 수 있도록 협력해야 자원 낭비를 줄인다.

스트리밍 호출은 별도 실패 계약이 필요하다. WatchOrder가 세 번째 이벤트까지 보낸 뒤 연결이 끊기면 클라이언트는 재접속 후 어디서 이어야 하는가? 이벤트에 순번을 넣고 마지막 수신 순번을 다시 보내거나, 최신 상태를 다시 조회한 뒤 구독하는 방식을 정할 수 있다. 서버 메모리만 이용한 임시 스트림이면 과거 이벤트 재생을 약속할 수 없다.

구현 전 검사표

확인할 조건실패 시 결과
클라이언트 데드라인과 서버 처리 시간오래 기다리거나 중간 상태를 모름
요청·응답 크기 상한큰 메시지로 메모리 압박
상태 코드와 업무 상태 구분없는 주문과 서버 장애를 같은 결과로 처리
인증 메타데이터와 TLS내부 네트워크라는 이유로 권한 누락
스키마 버전 조합배포 순서에 따라 필드 해석 오류

간단한 서버 간 조회라면 unary RPC부터 구현하고, 연결 실패·없는 ID·데드라인 만료를 테스트한다. 스트리밍이 실제 사용자 경험에 필요한 경우에만 재연결과 취소 의미를 추가한다.

참고: gRPC 재시도, gRPC 취소, Protobuf 필드 번호와 삭제 규칙.