gRPC 완벽 가이드 | Protocol Buffers·서비스 정의·Streaming·성능·마이크로서비스
이 글의 핵심
gRPC로 고성능 API를 구축하는 완벽 가이드입니다. Protocol Buffers, 서비스 정의, Unary/Streaming RPC, Node.js/Go 구현까지 실전 예제로 정리했습니다.
실무 경험 공유: REST API를 gRPC로 전환하면서, 응답 속도를 5배 향상시키고 네트워크 대역폭을 70% 절감한 경험을 공유합니다.
들어가며: “REST API가 느려요”
실무 문제 시나리오
시나리오 1: JSON 파싱이 느려요
대용량 데이터 전송 시 느립니다. gRPC는 바이너리로 빠릅니다.
시나리오 2: 타입 안전성이 부족해요
API 스펙이 불명확합니다. gRPC는 강력한 타입을 제공합니다.
시나리오 3: 스트리밍이 필요해요
REST는 요청/응답만 가능합니다. gRPC는 양방향 스트리밍을 지원합니다.
1. gRPC란?
핵심 특징
gRPC는 Google이 만든 고성능 RPC 프레임워크입니다.
주요 장점:
- 빠른 성능: 바이너리 프로토콜
- 타입 안전성: Protocol Buffers
- 스트리밍: 양방향 지원
- 다국어: 10+ 언어 지원
- HTTP/2: 멀티플렉싱
성능 비교:
- REST (JSON): 100ms, 10KB
- gRPC (Protobuf): 20ms, 2KB
2. Protocol Buffers
.proto 파일
// user.proto
syntax = "proto3";
package user;
message User {
int32 id = 1;
string name = 2;
string email = 3;
int32 age = 4;
}
message GetUserRequest {
int32 id = 1;
}
message GetUserResponse {
User user = 1;
}
message ListUsersRequest {
int32 page = 1;
int32 page_size = 2;
}
message ListUsersResponse {
repeated User users = 1;
int32 total = 2;
}
service UserService {
rpc GetUser(GetUserRequest) returns (GetUserResponse);
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
rpc CreateUser(User) returns (User);
}
3. Node.js 구현
설치
npm install @grpc/grpc-js @grpc/proto-loader
서버
// server.ts
import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';
const PROTO_PATH = './user.proto';
const packageDefinition = protoLoader.loadSync(PROTO_PATH);
const userProto = grpc.loadPackageDefinition(packageDefinition).user as any;
const users = [
{ id: 1, name: 'John', email: '[email protected]', age: 30 },
{ id: 2, name: 'Jane', email: '[email protected]', age: 25 },
];
const server = new grpc.Server();
server.addService(userProto.UserService.service, {
getUser: (call: any, callback: any) => {
const user = users.find(u => u.id === call.request.id);
if (user) {
callback(null, { user });
} else {
callback({
code: grpc.status.NOT_FOUND,
message: 'User not found',
});
}
},
listUsers: (call: any, callback: any) => {
callback(null, { users, total: users.length });
},
createUser: (call: any, callback: any) => {
const newUser = {
id: users.length + 1,
...call.request,
};
users.push(newUser);
callback(null, newUser);
},
});
server.bindAsync(
'0.0.0.0:50051',
grpc.ServerCredentials.createInsecure(),
() => {
console.log('gRPC server running on :50051');
server.start();
}
);
클라이언트
// client.ts
import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';
const PROTO_PATH = './user.proto';
const packageDefinition = protoLoader.loadSync(PROTO_PATH);
const userProto = grpc.loadPackageDefinition(packageDefinition).user as any;
const client = new userProto.UserService(
'localhost:50051',
grpc.credentials.createInsecure()
);
// GetUser
client.getUser({ id: 1 }, (error: any, response: any) => {
if (error) {
console.error('Error:', error);
} else {
console.log('User:', response.user);
}
});
// ListUsers
client.listUsers({ page: 1, page_size: 10 }, (error: any, response: any) => {
if (error) {
console.error('Error:', error);
} else {
console.log('Users:', response.users);
}
});
// CreateUser
client.createUser(
{ name: 'Bob', email: '[email protected]', age: 35 },
(error: any, response: any) => {
if (error) {
console.error('Error:', error);
} else {
console.log('Created:', response);
}
}
);
4. Streaming
Server Streaming
service LogService {
rpc StreamLogs(StreamLogsRequest) returns (stream LogEntry);
}
// 서버
streamLogs: (call: any) => {
const logs = [
{ message: 'Log 1', timestamp: Date.now() },
{ message: 'Log 2', timestamp: Date.now() },
{ message: 'Log 3', timestamp: Date.now() },
];
logs.forEach((log) => {
call.write(log);
});
call.end();
},
// 클라이언트
const call = client.streamLogs({});
call.on('data', (log: any) => {
console.log('Log:', log);
});
call.on('end', () => {
console.log('Stream ended');
});
Bidirectional Streaming
service ChatService {
rpc Chat(stream ChatMessage) returns (stream ChatMessage);
}
// 서버
chat: (call: any) => {
call.on('data', (message: any) => {
console.log('Received:', message);
// 모든 클라이언트에게 브로드캐스트
call.write({
user: message.user,
text: message.text,
timestamp: Date.now(),
});
});
call.on('end', () => {
call.end();
});
},
// 클라이언트
const call = client.chat();
call.on('data', (message: any) => {
console.log('Message:', message);
});
call.write({ user: 'John', text: 'Hello!' });
5. Go 구현
서버
// server.go
package main
import (
"context"
"log"
"net"
pb "myapp/proto"
"google.golang.org/grpc"
)
type server struct {
pb.UnimplementedUserServiceServer
}
func (s *server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error) {
user := &pb.User{
Id: req.Id,
Name: "John",
Email: "[email protected]",
Age: 30,
}
return &pb.GetUserResponse{User: user}, nil
}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("Failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterUserServiceServer(s, &server{})
log.Println("gRPC server running on :50051")
if err := s.Serve(lis); err != nil {
log.Fatalf("Failed to serve: %v", err)
}
}
6. 에러 처리
import * as grpc from '@grpc/grpc-js';
// 서버
getUser: (call: any, callback: any) => {
const user = findUser(call.request.id);
if (!user) {
return callback({
code: grpc.status.NOT_FOUND,
message: 'User not found',
details: 'No user with the given ID exists',
});
}
callback(null, { user });
},
// 클라이언트
client.getUser({ id: 999 }, (error: any, response: any) => {
if (error) {
if (error.code === grpc.status.NOT_FOUND) {
console.error('User not found');
} else {
console.error('Error:', error.message);
}
} else {
console.log('User:', response.user);
}
});
7. 인증
JWT Metadata
// 클라이언트
import * as grpc from '@grpc/grpc-js';
const metadata = new grpc.Metadata();
metadata.add('authorization', `Bearer ${token}`);
client.getUser({ id: 1 }, metadata, (error: any, response: any) => {
// ...
});
// 서버
getUser: (call: any, callback: any) => {
const metadata = call.metadata;
const auth = metadata.get('authorization')[0];
if (!auth || !verifyToken(auth)) {
return callback({
code: grpc.status.UNAUTHENTICATED,
message: 'Invalid token',
});
}
// ...
},
8. 배포
Docker
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
EXPOSE 50051
CMD ["node", "server.js"]
정리 및 체크리스트
핵심 요약
- gRPC: 고성능 RPC 프레임워크
- Protocol Buffers: 바이너리 직렬화
- 타입 안전성: 강력한 타입
- Streaming: 양방향 지원
- HTTP/2: 멀티플렉싱
- 다국어: 10+ 언어 지원
구현 체크리스트
- .proto 파일 작성
- 서버 구현
- 클라이언트 구현
- Streaming 구현
- 에러 처리
- 인증 구현
- 배포
같이 보면 좋은 글
- GraphQL 완벽 가이드
- 마이크로서비스 아키텍처 가이드
- Kubernetes 실전 가이드
이 글에서 다루는 키워드
gRPC, Protocol Buffers, RPC, Microservices, API, Performance, Backend
자주 묻는 질문 (FAQ)
Q. gRPC vs REST, 어떤 게 나은가요?
A. gRPC가 훨씬 빠릅니다. REST는 더 간단하고 브라우저 친화적입니다. 마이크로서비스 간 통신은 gRPC, 외부 API는 REST를 권장합니다.
Q. 브라우저에서 사용할 수 있나요?
A. gRPC-Web을 사용하면 가능하지만 제한적입니다. 브라우저는 REST나 GraphQL을 권장합니다.
Q. Protocol Buffers를 배워야 하나요?
A. 네, 하지만 간단합니다. JSON과 비슷하지만 타입이 명확합니다.
Q. 프로덕션에서 사용해도 되나요?
A. 네, Google, Netflix, Square 등 많은 기업에서 사용합니다.