gRPC 도입 후 REST API와 성능 비교

배경

사내 주문 시스템이 MSA로 전환되면서 서비스 간 통신이 빈번해졌다. REST API로 JSON을 주고받다 보니 직렬화 오버헤드와 네트워크 비용이 부담스러웠다. 특히 주문 처리 시 재고, 결제, 알림 서비스를 순차적으로 호출하는 과정에서 레이턴시가 누적됐다.

gRPC를 검토하게 된 계기는 Google Cloud의 권장사항 때문이었다. Protocol Buffers와 HTTP/2 기반이라 성능 개선을 기대할 수 있었다.

프로토 정의

syntax = "proto3";

service OrderService {
  rpc CreateOrder (OrderRequest) returns (OrderResponse);
  rpc GetOrder (OrderId) returns (Order);
}

message OrderRequest {
  string user_id = 1;
  repeated OrderItem items = 2;
}

message OrderResponse {
  string order_id = 1;
  int32 total_amount = 2;
}

.proto 파일로 인터페이스를 정의하면 다양한 언어로 클라이언트/서버 코드를 생성할 수 있다. 타입 안정성이 컴파일 타임에 보장되는 점이 좋았다.

Node.js 서버 구현

const grpc = require('grpc');
const protoLoader = require('@grpc/proto-loader');

const packageDefinition = protoLoader.loadSync('order.proto');
const orderProto = grpc.loadPackageDefinition(packageDefinition);

function createOrder(call, callback) {
  const { user_id, items } = call.request;
  // 주문 처리 로직
  callback(null, { order_id: 'ORD123', total_amount: 50000 });
}

const server = new grpc.Server();
server.addService(orderProto.OrderService.service, { createOrder });
server.bind('0.0.0.0:50051', grpc.ServerCredentials.createInsecure());
server.start();

성능 비교

동일한 주문 생성 로직을 REST와 gRPC로 각각 1000회 호출한 결과:

  • REST API (Express): 평균 45ms
  • gRPC: 평균 12ms

약 73% 레이턴시 감소. 페이로드 크기도 JSON 대비 30% 정도 작았다.

도입 시 고려사항

브라우저에서 직접 호출이 불가능해 웹 클라이언트용으로는 gRPC-Web 프록시가 필요하다. 결국 외부 API는 REST를 유지하고 내부 서비스 간 통신만 gRPC로 전환했다.

디버깅 도구가 REST 대비 부족한 점도 아쉬웠다. Postman 같은 도구 대신 grpcurl을 사용해야 했다.

결론

서비스 간 통신이 많은 MSA 환경에서 gRPC는 확실히 효과적이었다. 타입 안정성과 성능 개선이라는 두 마리 토끼를 잡을 수 있었다. 다만 학습 곡선과 툴링 부족은 팀 전체가 감수해야 할 비용이었다.