一般社団法人 全国個人事業主支援協会

COLUMN コラム

  • gRPC入門:Protocol Buffersを使った高効率なAPI通信の実装

gRPCとは

gRPCはGoogleが開発した高性能なRPCフレームワークです。HTTP/2をトランスポートに使い、Protocol Buffers(protobuf)でデータをシリアライズすることで、RESTful APIと比較して大幅な効率向上を実現します。マイクロサービス間の通信や、モバイルアプリとバックエンドの通信で特に威力を発揮します。

筆者が初めてgRPCを本番導入したのは3年ほど前だが、RESTからの移行でレイテンシが約40%改善し、ペイロードサイズが60%以上削減された経験がある。本記事ではその実践知を踏まえ、gRPCの基礎から実装まで解説します。

Protocol Buffersの基礎

gRPCの通信で使われるProtocol Buffersは、構造化データのシリアライゼーションフォーマットです。JSONと比較してバイナリ形式のためサイズが小さく、パース速度も高速です。

protoファイルの定義

まずはサービスとメッセージを定義するprotoファイルを作成します。

syntax = "proto3";

package bookstore;

option go_package = "./pb";

// 書籍サービスの定義
service BookService {
// 単一の書籍を取得
rpc GetBook(GetBookRequest) returns (Book);
// 書籍一覧を取得
rpc ListBooks(ListBooksRequest) returns (ListBooksResponse);
// 書籍を作成
rpc CreateBook(CreateBookRequest) returns (Book);
// 書籍の更新ストリーム(サーバーストリーミング)
rpc WatchBooks(WatchBooksRequest) returns (stream BookEvent);
}

message Book {
string id = 1;
string title = 2;
string author = 3;
int32 price = 4;
repeated string tags = 5;
}

message GetBookRequest {
string id = 1;
}

message ListBooksRequest {
int32 page_size = 1;
string page_token = 2;
}

message ListBooksResponse {
repeated Book books = 1;
string next_page_token = 2;
}

message CreateBookRequest {
string title = 1;
string author = 2;
int32 price = 3;
repeated string tags = 4;
}

message WatchBooksRequest {
repeated string tag_filter = 1;
}

message BookEvent {
string event_type = 1;
Book book = 2;
}

Protocol Buffersの大きな利点は、この定義ファイルから各言語のクライアント・サーバーコードを自動生成できることです。

Goでのサーバー実装

protoファイルからGoのコードを生成し、サーバーを実装してみよう。

// コード生成コマンド
// protoc --go_out=. --go-grpc_out=. proto/book.proto

package main

import (
"context"
"log"
"net"
"sync"

"google.golang.org/grpc"
pb "bookstore/pb"
)

type bookServer struct {
pb.UnimplementedBookServiceServer
mu sync.RWMutex
books map[string]*pb.Book
}

func (s *bookServer) GetBook(ctx context.Context, req *pb.GetBookRequest) (*pb.Book, error) {
s.mu.RLock()
defer s.mu.RUnlock()

book, ok := s.books[req.Id]
if !ok {
return nil, status.Errorf(codes.NotFound, "book %s not found", req.Id)
}
return book, nil
}

func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}

s := grpc.NewServer()
pb.RegisterBookServiceServer(s, &bookServer;{
books: make(map[string]*pb.Book),
})

log.Println("gRPC server listening on :50051")
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}

Pythonクライアントからの呼び出し

gRPCの言語非依存性を活かし、GoサーバーにPythonクライアントからアクセスしてみよう。

# pip install grpcio grpcio-tools
# python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. proto/book.proto

import grpc
import pb.book_pb2 as book_pb2
import pb.book_pb2_grpc as book_pb2_grpc

def main():
channel = grpc.insecure_channel('localhost:50051')
stub = book_pb2_grpc.BookServiceStub(channel)

# 書籍の作成
new_book = stub.CreateBook(book_pb2.CreateBookRequest(
title="gRPC実践入門",
author="山田太郎",
price=3200,
tags=["gRPC", "Go", "microservices"]
))
print(f"Created: {new_book.id} - {new_book.title}")

# 書籍の取得
book = stub.GetBook(book_pb2.GetBookRequest(id=new_book.id))
print(f"Got: {book.title} by {book.author}")

if __name__ == "__main__":
main()

gRPCの4つの通信パターン

gRPCはRESTにはない柔軟な通信パターンをサポートしています。

  • Unary RPC:1リクエスト1レスポンスの標準的なパターン。RESTと同等
  • Server Streaming:1リクエストに対してサーバーが複数レスポンスをストリームで返す。リアルタイム通知やログ配信に最適
  • Client Streaming:クライアントが複数リクエストを送り、サーバーが1レスポンスを返す。ファイルアップロードや大量データ送信に適しています
  • Bidirectional Streaming:双方向にストリーム通信。チャットや協調編集などインタラクティブな用途に向いています

本番運用で押さえるべきポイント

エラーハンドリング

gRPCは独自のステータスコード体系を持つ。HTTPステータスコードとは異なるため、適切なマッピングが必要だ。特にクライアントサイドでのリトライ戦略や、デッドラインの設定は本番環境で必須となります。

ヘルスチェックとロードバランシング

gRPCはHTTP/2の長時間コネクションを使うため、L4ロードバランサだけではリクエストが均等に分散されないことがある。L7ロードバランサの導入や、クライアントサイドロードバランシングの検討が必要だ。

後方互換性

Protocol Buffersのフィールド番号は変更してはなりません。フィールドの追加は安全だが、削除や型変更は互換性を壊す。この点はチーム全体で理解しておく必要がある。

まとめ

gRPCはマイクロサービスアーキテクチャにおける通信の効率化に大きく貢献する技術だ。Protocol Buffersによる型安全な通信、HTTP/2の多重化、豊富なストリーミングパターンなど、RESTでは得られない利点が多いです。学習コストはあるものの、パフォーマンスと開発体験の両面で投資に見合うリターンが得られるだろう。

この記事をシェアする

  • Twitterでシェア
  • Facebookでシェア
  • LINEでシェア