gRPC로 마이크로서비스 간 통신 전환 후기
배경
사내 마이크로서비스 아키텍처에서 서비스 간 통신을 REST API로 처리하고 있었다. 서비스가 늘어나면서 API 스펙 불일치, JSON 파싱 오버헤드, 타입 안정성 문제가 반복적으로 발생했다. gRPC 도입을 검토하게 된 계기였다.
Protocol Buffers 스키마 정의
먼저 .proto 파일로 서비스 인터페이스를 정의했다.
syntax = "proto3";
package user;
service UserService {
rpc GetUser (GetUserRequest) returns (User);
rpc ListUsers (ListUsersRequest) returns (ListUsersResponse);
}
message GetUserRequest {
int64 user_id = 1;
}
message User {
int64 id = 1;
string email = 2;
string name = 3;
}
스키마를 코드로 컴파일하면 타입 안전한 클라이언트/서버 코드가 자동 생성된다. TypeScript와 Go 양쪽에서 동일한 스키마를 공유할 수 있어 타입 불일치 문제가 사라졌다.
Node.js 서버 구현
grpc 패키지로 서버를 구현했다.
const grpc = require('grpc');
const protoLoader = require('@grpc/proto-loader');
const packageDefinition = protoLoader.loadSync('user.proto');
const userProto = grpc.loadPackageDefinition(packageDefinition).user;
function getUser(call, callback) {
const userId = call.request.user_id;
// DB 조회 로직
callback(null, {
id: userId,
email: '[email protected]',
name: 'John Doe'
});
}
const server = new grpc.Server();
server.addService(userProto.UserService.service, { getUser });
server.bind('0.0.0.0:50051', grpc.ServerCredentials.createInsecure());
server.start();
성능 개선
REST API 대비 평균 응답 시간이 30% 감소했다. HTTP/2 기반의 멀티플렉싱과 바이너리 프로토콜의 효율성 덕분이었다. 특히 빈번한 내부 호출이 있는 서비스에서 효과가 컸다.
한계점
브라우저에서 직접 gRPC를 호출할 수 없어 클라이언트 대응 API는 여전히 REST로 유지했다. gRPC-Web도 있지만 프록시 설정이 필요해 도입하지 않았다. 내부 서비스 간 통신에만 적용하는 것으로 범위를 제한했다.
결론
마이크로서비스 간 통신에는 gRPC가 REST보다 적합했다. 타입 안정성과 성능 모두 개선되었고, 특히 다중 언어 환경에서 스키마 공유가 편리했다.