代表的な面接トピック

バックエンド面接:RESTとgRPCのどちらを選択すべきか?

バックエンド難しい
Offer.cc 編集チーム公開日 更新日

質問

あるプラットフォームにおいて、ブラウザ、モバイルアプリ、パートナー向けの公開API、単一(Unary)およびサーバースタイルストリーミングメソッドを伴い毎秒20,000コールを処理する内部サービスインターフェース、そしてサードパーティコールバック用エンドポイントが必要です。どの境界にHTTP上のREST+JSONを使用し、どの境界にgRPCを使用すべきでしょうか?コントラクト、互換性、ストリーミング、デッドライン、リトライと冪等性、オブザーバビリティ、セキュリティ、進化(エボリューション)、検証の観点を含めて説明してください。

プロンプトと適用されるコンテキスト

あるプラットフォームには3つのAPI境界があります。1つ目はブラウザ、モバイルアプリ、外部パートナーが利用するため、統合、デバッグ、独立した進化が容易である必要があります。2つ目は、制御されたデータセンター環境内のサービス間トラフィックです。ピーク時には毎秒20,000コールに達し、通常のリクエスト・レスポンス呼び出しとサーバースタイルのストリームの両方が必要です。3つ目は、外部システムからのWebhookコールバックを受信します。

毎秒20,000コールという数値は面接用の前提条件であり、普遍的なパフォーマンスの閾値ではありません。各境界に対してRESTスタイルのHTTP+JSON、ネイティブgRPC、または根拠のある組み合わせを選択し、移行と検証について説明してください。RESTはアーキテクチャスタイルであり、JSONやHTTP/1.1に縛られるものではありません。「REST/HTTP+JSON」は、この設問で比較される一般的な実装を定義しているにすぎません。また、gRPCは操作を自動的に高速、冪等、または信頼性の高いものにする魔法のスイッチではありません。

これはバックエンドの設問であり、その核心はサービスインターフェース、プロトコルセマンティクス、クライアントコントラクト、および本番環境のガバナンスにあります。ビジネスシステム全体の実装を求めているわけではなく、単に「gRPCは高速、RESTは互換性が高い」といった暗記された比較表だけでは不十分です。

面接官が評価するポイント

第1の評価ポイントは、候補者がコンシューマーとネットワーク境界から検討を始めているかどうかです。一般のブラウザやパートナーのエコシステムでは、普及しているHTTPツール群、人間が読めるペイロード、キャッシングセマンティクス、低い統合コストが重視されます。制御された内部の呼び出し元であれば、.protoファイル、生成されたコード、プロキシ、ロードバランシングをより容易に標準化できます。プロトコルは境界に応じて選択可能であり、1つのプラットフォームが1つのインターフェーススタイルのみを公開する必要はありません。

第2の評価ポイントは、抽象概念と実装の分離です。RESTはリソース、HTTPメソッド、ステータスコード、キャッシングセマンティクスを使用し、HTTP/2やHTTP/3上でも動作します。gRPCはサービスとメソッドを中心に据え、デフォルトのインターフェースおよびメッセージ定義としてProtocol Buffersを使用し、Unary、クライアントストリーミング、サーバースタイルストリーミング、双方向ストリーミングのメソッドを提供します。「RESTはHTTP/1.1しか使えない」「RESTはストリーミングができない」というのはどちらも誤った短絡的思考です。

第3の評価ポイントは、コントラクトと障害モデルの完成度です。OpenAPIを使用することで、HTTP APIに機械可読なコントラクトとコード生成を提供できます。Protobufのバイナリ互換性はアプリケーション互換性を保証するものではありません。どちらを選択しても、デッドライン、キャンセル処理、冪等性、リトライ可能エラーのルール、認証、認可、バージョン進化、観測可能なリクエストIDの設定が必要です。

最後に、優れた候補者は証拠を求めます。代表的なペイロード、並行性、圧縮、接続の挙動、障害をベンチマークし、テールレイテンシとエラーを測定しながら結果をカナリアリリースで検証します。「バイナリの方が速い」という理由だけで正当化されるプラットフォーム移行は、再現性のあるエンジニアリングとは言えません。

回答前に確認すべき明確化のための質問

  • クライアントとアップグレードを誰が管理しているか? 単一組織内のサービスであれば、生成されたクライアントのリリースを連携して行えます。強制的にアップグレードできないパートナーには、個別に利用しやすい安定した境界が必要です。
  • インタラクションはUnary、ストリーミング、非同期通知のいずれか? 通常のCRUDはgRPCによる自動的な恩恵をあまり受けません。長時間接続での順序付きストリームはネイティブgRPCに適している可能性があります。サードパーティのWebhookは相手側から開始されるため、通常はその事業者が公開しているHTTPコントラクトに従う必要があります。
  • ブラウザがサービスを直接呼び出す必要があるか? ブラウザはネイティブgRPCが必要とするHTTP/2の制御を直接すべて提供できるわけではありません。gRPC-Web、JSONトランスコーディング、またはBFFを導入すると、デバッグ、ストリーミング機能、および運用に影響するレイヤーが追加されます。
  • 実際のパフォーマンス上の問題は何か? シリアライゼーションを変更しても、データベースのボトルネック、ダウンストリームへのファンアウト、無制限のクエリは解消されません。ペイロードサイズ、QPS、並行性、p95/p99、CPU、ネットワークバジェットを確認してください。
  • 現在のゲートウェイとオブザーバビリティスタックは何をサポートしているか? gRPCステータス、ストリーム、ヘルスチェック、エンドツーエンドのトレース相関に対するプロキシのサポート状況は、ロールアウトのリスクに直結します。
  • インターフェースはどのように進化する必要があるか? 公開APIには互換性ポリシーが必要です。内部のProtobufにはフィールド番号と混在バージョンのルールが必要です。バージョン間のテストがなければ、強く型付けされたコントラクトであってもローリングデプロイ中に障害が発生する可能性があります。

30秒の回答フレームワーク

「プラットフォーム全体で1つの選択をするのではなく、コンシューマーの境界ごとに決定します。ブラウザ、モバイル、パートナー向けAPIは、OpenAPI、HTTPセマンティクス、互換性ポリシーによって管理されるRESTスタイルのHTTP+JSONから始めます。サードパーティのWebhookも公開HTTPコントラクトに従います。毎秒20,000コールの制御された内部通信については、代表的なベンチマークでシリアライゼーションや接続コストが重要であり、サーバースタイルのストリームが実際の要件であることが示された場合に、ネイティブgRPCを選択します。コード生成されたクライアントを使用しても、デッドラインの設定、キャンセルの伝播、安全な操作のみのリトライ、フィールド互換性の維持は依然として必要です。エッジでは、JSONトランスコーディングや薄いゲートウェイによって1つのドメイン実装を共有できます。ロールアウト前に、実際のペイロードを使用してエンドツーエンドのp99、CPU、バイト数、障害復旧を比較し、呼び出し元ごとに移行を進めます。プロトコルは認証、冪等性、オブザーバビリティの代替にはなりません。」

ステップごとの詳細解説

ステップ1: 各境界を個別に決定する

公開境界にはRESTスタイルのHTTP+JSONを使用します。リソースURI、メソッド、ステータスコード、条件付きリクエスト、キャッシングは、ブラウザ、CDN、コマンドラインツール、パートナーによって広く理解されています。OpenAPIはドキュメントや生成されるSDKのコントラクトソースとなり得るため、RESTを「型がなく手書き」と表現するのは不適切です。トレードオフとして、チームはエラーモデル、ページネーション、オープンなEnum、仕様の乖離(ドリフト)を能動的に管理する必要があります。

内部境界に対するgRPCの採用は、2つの条件をクリアした後にのみ選択します。呼び出し元とサーバーが生成コードとランタイムインフラを標準化できること、そしてプロキシ、デバッグ、混在バージョンの複雑さに見合うだけの十分なメリットが代表的なベンチマークで示されることです。また、本設問にはサーバースタイルのストリーミングという実際の要件があります。gRPCのストリーミングメソッド、および呼び出しごとのメタデータ、ステータス、デッドラインは、そのための首尾一貫したモデルを構成します。20,000 QPSという数値単独では決定の決め手になりません。

サードパーティのコールバックにはHTTP Webhookを使用します。外部の送信元がプロトコルを制御しており、公開された受信側にはTLS、署名検証、迅速な確認応答(Ack)、非同期処理が必要です。受信側をgRPCに置き換えても、HTTP POSTリクエストを送信するパートナーが呼び出せるようにはなりません。内部の処理経路でgRPCを使用している場合、Webhookアダプターがイベントを検証して永続化してから、その経路を呼び出します。

境界初期の選択主な理由主なコスト
ブラウザ、モバイル、パートナーREST/HTTP+JSON幅広い互換性、HTTPセマンティクス、低い統合コストコントラクトとクライアント差異のガバナンスが必要
制御された内部サービスgRPC生成されるコントラクト、ストリーミングメソッド、コンパクトなメッセージツール、プロキシ、ローリングアップグレードの複雑性
サードパーティコールバックのイングレスHTTP Webhook送信元のコントラクトとインターネット相互運用性署名、重複排除、非同期分離がアプリケーション側の実装課題となる

ステップ2: 実行可能なコントラクトを記述する

公開読み取りインターフェースはリソースとHTTPセマンティクスを使用します:

http
GET /v1/orders/ord_123
If-None-Match: "order-v7"

200 OK
ETag: "order-v7"
Content-Type: application/json

内部インターフェースはアクションとメッセージを定義します:

proto
service OrderService {
  rpc GetOrder(GetOrderRequest) returns (Order);
  rpc WatchOrder(WatchOrderRequest) returns (stream OrderEvent);
}

message GetOrderRequest {
  string order_id = 1;
}

どちらのコントラクトにも、認証、認可、エラー処理、ページネーションまたはストリーム終了のルール、サイズ制限、監査フィールドが必要です。gRPCのメソッド名はネットワークコストを隠蔽してしまう可能性がありますが、呼び出し元は依然としてレイテンシ、タイムアウト、部分障害を伴うリモート操作として扱う必要があります。RESTのPOSTも自動的に冪等になるわけではありません。作成操作には安定した冪等性キーまたはビジネス上のユニーク制約が必要です。

ステップ3: 障害セマンティクスを設計する

デフォルトではgRPCクライアントにデッドラインが設定されていない場合があるため、エンドツーエンドのバジェットからデッドラインを設定します。デッドラインが切れると呼び出し元は待機を停止しますが、サーバーアプリケーション側では生成した処理を停止する責任が残ります。選択した言語やフレームワークにおいて、デッドラインとキャンセルの伝播が機能することも検証する必要があります。同様に、HTTPクライアントにも接続、応答、合計のバジェットが必要です。ソケットのデフォルト値はビジネス上のSLOではありません。

リトライの判断はビジネスセマンティクスに基づきます。HTTPのGET、HEAD、PUT、DELETEは仕様上、安全または冪等な特性を持っていますが、実装がそれらのメソッドセマンティクスを尊重している必要があります。POSTは、冪等性キーまたは同等のメカニズムによって結果が安全になる場合にのみリトライ可能です。gRPCのメソッド名に自動的な冪等性は備わっていません。コントラクトでリトライ可能なステータスと操作を明示する必要があります。どちらのアプローチでも、試行回数の上限、残りデッドラインのチェック、およびゲートウェイ、SDK、アプリケーションでのリトライの重なり(増幅)に対する保護が必要です。

ストリーミングRPCでは、低速コンシューマーの処理、バックプレッシャー、メッセージごとのサイズ制限、再開位置、重複イベントへの対応が加わります。コンシューマーがカーソルから再開する必要がある場合、イベントには安定したIDまたはシーケンス番号が必要です。ストリームを再オープンするだけでは、欠落や重複がないことを保証できません。

ステップ4: ローリングエボリューションに耐えうるコントラクトにする

REST/JSONの互換性には、構造とセマンティクスが含まれます。レスポンスフィールドの追加が安全なのは、クライアントが未知のフィールドを許容する場合のみです。デフォルトのページネーション、ソート順、Enumの意味を変更すると、JSONのパースは通ってもアプリケーションが破損する可能性があります。OpenAPIの差分チェック、最新の公開SDK、記録されたリクエストの再生テスト、エンドツーエンドのアサーションを用いてリリースを制御します。

Protobufフィールドの追加は、通常、古いリーダーが未知のフィールドを無視するためワイヤー上はバイナリ安全ですが、新しいEnumやデフォルト値によってアプリケーションコードが破損することがあります。既存のフィールド番号は絶対に変更しないでください。削除されたフィールドの番号と名前は予約(reserve)し、どちらも再利用されないようにします。ローリングリリース中は、「古いクライアントと新しいサーバー」「新しいクライアントと古いサーバー」の組み合わせをテストします。同一バージョン間のテストだけでは不十分です。

エッジクライアントと内部クライアントが同じ機能を必要とする場合は、ドメインロジックと明示的なコントラクトソースを共有し、必要に応じてJSONトランスコーディングや薄いアダプターを介してHTTPを公開します。アダプターは、HTTPステータスとgRPCステータスのマッピング、ヘッダーとメタデータ、フィールド名、認証、ストリーミングの制限事項を適切に変換する必要があります。手動で同期された2つのビジネス実装は、必ず乖離(ドリフト)していきます。

ステップ5: 本番を模した証拠に基づいて移行を決定する

1つの小さなオブジェクトをシリアライズするようなマイクロベンチマークではなく、実際のペイロード分布とメソッドの組み合わせを使用します。同一のドメインロジックに対して、エンドツーエンドのスループット、p50/p95/p99、クライアントおよびサーバーのCPU、転送バイト数、接続数、メモリを測定します。Unary呼び出し、大小のメッセージ、圧縮、サーバースタイルのストリーム、低速コンシューマー、ゾーン間トラフィックを網羅します。プロトコルの影響を分離できるよう、データベースやダウンストリームの処理は同一に保ちます。

リスクの低い1つの内部メソッドをデュアルスタックモードで実行し、呼び出し元ごとにカナリアリリースを実施します。ビジネス結果、エラーの分類、デッドライン超過イベント、キャンセル後も継続してしまった処理、リトライの増幅、トレースの完全性を比較します。事前に定めたメリットと信頼性の閾値を満たした場合にのみ展開を拡大します。得られるメリットがわずかである場合にHTTP APIを維持することは、完全に妥当な判断です。

質の高い模範回答

「プラットフォーム全体を一括してRESTまたはgRPCと決定することはしません。各境界を誰が呼び出すか、誰がアップグレードを管理するか、そして通信パターンに基づいて判断します。

ブラウザ、モバイルアプリ、パートナーに対しては、RESTスタイルのHTTP+JSONを使用します。幅広いHTTPエコシステムと連携でき、OpenAPIによって機械可読なコントラクト、SDK生成、互換性チェックが提供されます。サードパーティのWebhookは、送信元のプロトコルが外部の制約であるため、HTTP POSTのままとします。イングレスで署名を検証し、イベントIDで重複排除し、イベントを永続化して非同期に処理します。

内部境界は毎秒20,000コールとサーバースタイルのストリームを伴います。本番を模したペイロードでベンチマークを実施します。すべての呼び出し元が生成クライアントを使用でき、プロキシ、ロードバランサー、モニタリングがgRPCを理解し、p99、CPU、または帯域幅が事前に定めた閾値を満たすほど改善される場合は、内部でgRPCを採用します。Unaryおよびストリーミングメソッドは.proto内に定義しますが、各呼び出しにはデッドラインを設定し、キャンセルによって生成された処理を停止させ、規約上安全な操作のみをリトライするようにします。

進化(エボリューション)に関して、HTTP APIはOpenAPIの差分、古いSDK、セマンティックリプレイを使用してページネーションやEnumの破損を検出します。Protobufのフィールド番号は絶対に変更せず、削除されたフィールドは予約し、新旧クライアントとサーバーの組み合わせを相互にテストします。公開インターフェースと内部インターフェースが同一の機能を共有する場合、単一のドメイン実装からHTTPアダプターとgRPCサービスの両方に提供するか、マッピングを検証した上でJSONトランスコーディングを使用します。

呼び出し元単位でカナリアリリースを行い、エンドツーエンドのp99、CPU、バイト数、ステータスマッピング、リトライ増幅、トレースの完全性を監視します。もしマイクロベンチマークでしか改善が見られず、本番のボトルネックがデータベースのままであるなら、単なるプロトコルの統一性を求めて移行を拡大することはありません。」

よくある間違い

  • RESTをHTTP/1.1と同一視する → HTTPセマンティクスとトランスポートバージョンを混同している → REST APIはHTTP/2やHTTP/3上でも動作可能であることを述べる。
  • gRPCが常に高速であると主張する → ストレージ、ビジネスロジック、またはプロキシが支配的である可能性がある → 代表的なペイロードとテールレイテンシを用いて、同一のドメイン処理でベンチマークを行う。
  • RESTには厳格なコントラクトがないと言う → OpenAPIによる記述、生成、テストを無視している → 放置された仕様書と維持管理されたものを比較するのではなく、実際のコントラクトワークフローを比較する。
  • ブラウザからネイティブgRPCを直接呼び出す → ブラウザには必要なネイティブgRPC制御機能がない → gRPC-Web、JSONトランスコーディング、またはBFFを使用し、その制限事項を考慮する。
  • gRPCのデッドライン設定を省略する → クライアントが無期限に待機し、リソースを消費する可能性がある → エンドツーエンドのバジェットからデッドラインを算出し、キャンセルの動作を検証する。
  • Protobufのワイヤー互換性をアプリケーション互換性と混同する → 新しいEnum、デフォルト値、セマンティクスによってコードが破損する可能性がある → バージョン間の相互テストを実施し、フィールド番号を予約する。
  • すべてのレイヤーでリトライを有効化する → 障害時にトラフィックが増幅し、書き込みが重複する可能性がある → リトライを一元管理し、安全な操作、残り時間、試行回数を制限する。
  • 統一性のためだけに単一のプロトコルに固執する → 公開側の統合コストや内部のストリーミング要件が犠牲になる → 正当な理由がある場合は、明示的なHTTPエッジとgRPC内部境界を使い分ける。

フォローアップの質問と回答

フォローアップ 1: わずか500 QPSの内部APIでもgRPCを使用すべきですか?

QPSの数値だけで決定することはできません。成熟したgRPCプラットフォーム、多言語でのコード生成コントラクト、ストリーミング要件を持つ組織であれば、500 QPSであっても恩恵を受けられる場合があります。一方、シンプルな単一のCRUDサービス、成熟したHTTPツール群、十分なパフォーマンスの余裕を持つチームであれば、REST/HTTP+JSONの方が運用コストを低く抑えられる可能性が高いです。まず目標メトリクスを文書化し、メリットを実証できない場合は移行すべきではありません。

フォローアップ 2: 公開モバイルアプリで生成されたgRPCクライアントを使用できます。gRPCを公開APIにすることは可能ですか?

管理下にあるモバイルクライアント向けの候補にはなり得ますが、プロキシ、エンタープライズネットワーク、デバッグツール、証明書処理、バージョン互換性、リリース頻度を検証する必要があります。パートナーやブラウザは依然としてHTTP APIを必要とする可能性があるため、公開gRPCを採用してもデュアルプロトコルのコストが自動的に解消されるわけではありません。「パブリックインターネット」を一括りのクライアントとして扱うのではなく、呼び出し元のグループごとに決定します。

フォローアップ 3: 1つの.protoからJSON APIも公開するにはどうすればよいですか?

メソッドにHTTPマッピングのアノテーションを付与し、JSONトランスコーディングまたはゲートウェイを使用します。ロールアウト前に、フィールド名、nullおよびデフォルト値の挙動、HTTPとgRPCのステータスマッピング、メタデータとヘッダー、認証、キャッシング、ストリーミングの制限事項を検証します。.protoコントラクトソースによって重複は減りますが、アダプターのセマンティクスについては依然としてテストが必要です。

フォローアップ 4: イベントを失うことなくgRPCストリームを再開するにはどうすればよいですか?

プロトコルが提供するのは単一RPC内でのストリーミングと順序保証であり、永続的なビジネスサブスクリプションではありません。イベントに安定したシーケンス番号を付与し、再生可能なログを保持し、再接続時にクライアントが消費済みカーソルを永続化または送信するようにします。保持期間、期限切れカーソルの処理、重複排除を定義します。これらの要件が支配的である場合は、RPCを無理に拡張するのではなく、メッセージログやメッセージキューの採用を検討してください。

フォローアップ 5: ベンチマークではgRPCのp99が優れていましたが、本番環境では改善が見られません。何を調査しますか?

レイテンシの内訳を、クライアントのキューイング、DNSおよび接続処理、プロキシ、シリアライゼーション、アプリケーションロジック、データベース、ダウンストリーム呼び出しに分解します。本番のペイロード、圧縮、接続の再利用、TLS、ゾーン間ルーティング、隠れたリトライがベンチマークと一致しているか確認します。プロトコルがレイテンシ全体のごく一部にすぎない場合は、移行を拡大するのではなく、ボトルネックとなっている支配的なコンポーネントを最適化します。

公開情報ソース

関連する質問