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 파일 관리와 코드 생성 파이프라인 구축이 필요해 초기 셋업 비용이 있었다.