Skip to content

gRPCとは

gRPCは、Googleが開発した高性能なRPC(Remote Procedure Call)フレームワークです。Protocol Buffersを使用して、効率的な通信を実現します。

❌ 問題のある実装:

// RESTful API: JSON形式で通信
// リクエスト
POST /api/users
Content-Type: application/json
{
"name": "Alice",
"email": "alice@example.com",
"age": 25
}
// レスポンス
{
"id": 1,
"name": "Alice",
"email": "alice@example.com",
"age": 25
}
// 問題点:
// - テキスト形式のため、サイズが大きい
// - 型安全性がない
// - パフォーマンスが限定的

⚠️ 影響:

  • 📈 通信量の増加
  • ⚠️ パフォーマンスの低下
  • 型安全性の欠如

✅ 改善された実装:

// Protocol Buffers: バイナリ形式で通信
syntax = "proto3";
service UserService {
rpc GetUser(GetUserRequest) returns (User);
rpc CreateUser(CreateUserRequest) returns (User);
}
message GetUserRequest {
int32 id = 1;
}
message CreateUserRequest {
string name = 1;
string email = 2;
int32 age = 3;
}
message User {
int32 id = 1;
string name = 2;
string email = 3;
int32 age = 4;
}

✅ メリット:

  • 📦 バイナリ形式のため、サイズが小さい
  • 型安全性が保証される
  • ⚡ 高いパフォーマンス
  • 🔄 ストリーミング対応

📋 定義: 構造化データをシリアライズするための言語非依存のフォーマットです。

✅ メリット:

  • 📦 サイズ: JSONより約3-10倍小さい
  • 速度: シリアライズ/デシリアライズが高速
  • 型安全性: スキーマにより型が保証される
  • 🔄 後方互換性: スキーマの進化に対応

📋 特徴:

  • 🔄 マルチプレクシング(複数のリクエストを並列処理)
  • 📤 サーバープッシュ
  • 📦 ヘッダー圧縮
  • 🔄 ストリーミング

⚡ なぜgRPCは速いのか(仕組みまで)

Section titled “⚡ なぜgRPCは速いのか(仕組みまで)”

gRPCの速さは「通信プロトコル」と「データ表現」の両方で発生します。

1. バイナリシリアライズ(Protocol Buffers)

  • JSONは「文字列」であり、サイズもパースコストも大きい
  • Protocol Buffersは「数値タグ+バイナリ」で構造を表現する
  • 送受信サイズが小さく、シリアライズ/デシリアライズが高速

2. HTTP/2の多重化(マルチプレクシング)

  • HTTP/1.1は同一コネクション内でリクエストが詰まりやすい
  • HTTP/2は1本のTCP上で複数ストリームを同時処理できる
  • 待ち時間が減り、スループットが上がる

3. ヘッダー圧縮(HPACK)

  • RESTはヘッダーが毎回冗長になりやすい
  • HTTP/2はヘッダーを圧縮し、繰り返しを省く
  • 小さなリクエストほど効果が大きい

4. 接続回数の削減

  • gRPCは長時間の接続を前提に設計されている
  • ハンドシェイク回数が減り、往復遅延が縮む

5. 双方向ストリーミング

  • リクエスト/レスポンスの分離ではなく、ストリームで連続送信できる
  • 大量データやリアルタイム通信でオーバーヘッドが減る

1. Unary RPC(単一リクエスト・単一レスポンス):

rpc GetUser(GetUserRequest) returns (User);

2. Server Streaming(単一リクエスト・複数レスポンス):

rpc ListUsers(ListUsersRequest) returns (stream User);

3. Client Streaming(複数リクエスト・単一レスポンス):

rpc CreateUsers(stream CreateUserRequest) returns (CreateUsersResponse);

4. Bidirectional Streaming(複数リクエスト・複数レスポンス):

rpc Chat(stream ChatMessage) returns (stream ChatMessage);

プロトファイル:

user.proto
syntax = "proto3";
package user;
service UserService {
rpc GetUser(GetUserRequest) returns (User);
rpc CreateUser(CreateUserRequest) returns (User);
rpc ListUsers(ListUsersRequest) returns (stream User);
}
message GetUserRequest {
int32 id = 1;
}
message CreateUserRequest {
string name = 1;
string email = 2;
int32 age = 3;
}
message ListUsersRequest {
int32 page = 1;
int32 page_size = 2;
}
message User {
int32 id = 1;
string name = 2;
string email = 3;
int32 age = 4;
}

サーバー実装:

import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';
import path from 'path';
const PROTO_PATH = path.join(__dirname, '../proto/user.proto');
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
});
const userProto = grpc.loadPackageDefinition(packageDefinition).user as any;
// サーバーの実装
const server = new grpc.Server();
server.addService(userProto.UserService.service, {
GetUser: (call: any, callback: any) => {
const userId = call.request.id;
const user = getUserById(userId);
if (!user) {
callback({
code: grpc.status.NOT_FOUND,
message: `User ${userId} not found`,
});
return;
}
callback(null, user);
},
CreateUser: (call: any, callback: any) => {
const { name, email, age } = call.request;
const user = createUser({ name, email, age });
callback(null, user);
},
ListUsers: (call: any) => {
const { page, page_size } = call.request;
const users = getUsers(page, page_size);
users.forEach((user: any) => {
call.write(user);
});
call.end();
},
});
server.bindAsync(
'0.0.0.0:50051',
grpc.ServerCredentials.createInsecure(),
(error, port) => {
if (error) {
console.error('Server failed to start:', error);
return;
}
server.start();
console.log(`Server running on port ${port}`);
}
);

クライアント実装:

import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';
import path from 'path';
const PROTO_PATH = path.join(__dirname, '../proto/user.proto');
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
});
const userProto = grpc.loadPackageDefinition(packageDefinition).user as any;
const client = new userProto.UserService(
'localhost:50051',
grpc.credentials.createInsecure()
);
// Unary RPC
client.GetUser({ id: 1 }, (error: any, user: any) => {
if (error) {
console.error('Error:', error);
return;
}
console.log('User:', user);
});
// Server Streaming
const stream = client.ListUsers({ page: 1, page_size: 10 });
stream.on('data', (user: any) => {
console.log('User:', user);
});
stream.on('end', () => {
console.log('Stream ended');
});
stream.on('error', (error: any) => {
console.error('Stream error:', error);
});
項目gRPCRESTful API
通信形式Protocol Buffers(バイナリ)JSON(テキスト)
パフォーマンス高い中程度
型安全性高い低い
ストリーミング対応限定的(SSE、WebSocket)
ブラウザサポート限定的(gRPC-Web)完全
学習コスト高い低い

✅ gRPCを使うべき場合:

  • 🔄 マイクロサービス間の通信
  • ⚡ 高いパフォーマンスが必要
  • 型安全性が重要
  • 🔄 ストリーミングが必要

✅ RESTful APIを使うべき場合:

  • 🌐 ブラウザからのアクセス
  • 📝 シンプルなAPI
  • 🌍 広くサポートされている形式が必要

gRPCのポイント:

  • 📦 Protocol Buffers: 効率的なシリアライゼーション
  • 🌐 HTTP/2: マルチプレクシングとストリーミング
  • 🔄 4つの通信パターン: Unary、Server Streaming、Client Streaming、Bidirectional Streaming
  • 高いパフォーマンス: バイナリ形式により高速
  • 型安全性: スキーマにより型が保証される

適切にgRPCを使用することで、高性能なAPIを構築できます。