gRPC 도입 후 REST API와 성능 비교

배경

사용자 서비스와 주문 서비스 간 통신이 많아지면서 네트워크 오버헤드가 문제가 되었다. REST API는 JSON 직렬화/역직렬화 비용과 HTTP/1.1의 한계로 latency가 증가했다. gRPC를 일부 구간에 적용해보기로 결정했다.

환경 구성

Node.js 10 환경에서 grpc 패키지를 사용했다. Protocol Buffers 정의는 다음과 같이 작성했다.

syntax = "proto3";

service UserService {
  rpc GetUser (UserRequest) returns (UserResponse);
  rpc ListUsers (ListUserRequest) returns (stream UserResponse);
}

message UserRequest {
  string user_id = 1;
}

message UserResponse {
  string user_id = 1;
  string name = 2;
  string email = 3;
}

서버 구현은 비교적 간단했다.

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

const packageDefinition = protoLoader.loadSync('user.proto');
const userProto = grpc.loadPackageDefinition(packageDefinition);

function getUser(call, callback) {
  const userId = call.request.user_id;
  // DB 조회 로직
  callback(null, { user_id: userId, name: 'John', email: '[email protected]' });
}

const server = new grpc.Server();
server.addService(userProto.UserService.service, { getUser });
server.bind('0.0.0.0:50051', grpc.ServerCredentials.createInsecure());
server.start();

성능 측정

동일한 데이터를 1000번 요청하는 테스트를 진행했다.

  • REST API (Express): 평균 42ms, 처리량 238 req/s
  • gRPC: 평균 18ms, 처리량 555 req/s

HTTP/2 기반의 멀티플렉싱과 바이너리 프로토콜 덕분에 약 2.3배의 성능 개선이 있었다.

고려사항

gRPC는 브라우저에서 직접 호출하기 어렵다. grpc-web을 사용하거나, API Gateway를 두고 REST로 변환하는 방식을 고려해야 한다. 우리는 마이크로서비스 간 통신에만 적용하고, 클라이언트 API는 REST를 유지하기로 했다.

타입 안정성과 성능 면에서는 만족스러웠지만, .proto 파일 관리와 코드 생성 파이프라인 구축이 필요해 초기 셋업 비용이 있었다.