ねののお庭。

かりかりもふもふ。

HTTP/2 と gRPC に対するよくある誤解。

以前 .NET ラボで 「C# と HTTP/2 と gRPC」というタイトルで登壇しました。その時のスライドがこちらなのですが、ちらほら反応を頂きました。その結果、HTTP/2 や gRPC について勘違いしている人がちょこちょこいる事が分かったので、少し補足を書こうと思います。

blog.neno.dev

1. HTTP/2 で向上するのはスループットであって、1リクエストあたりの応答時間ではないよ。

HTTP/2 を使うからといって、1 リクエストあたりの応答時間が短くなるわけではないのです。 まず、1 HTTP リクエストあたりにかかる時間を、RTT とかいったりします。 1 RTT の内訳はだいたいこんな感じになります。

1 RTT = ネットワーク上で往路にかかる時間 + サーバの処理時間 + ネットワーク上で復路にかかる時間

HTTP/2 になったからといって、ネットワークを往路/復路にかかる時間は短くなるでしょうか?ならないのは明らかですね。 HTTP/2 になったからといって、サーバの処理時間 が短くなるでしょうか?それほど極端には変わらないでしょう。

とまぁ、そんなわけなので、1 RTT が短くなるというわけではないのです。 え、じゃぁ HTTP/2 で改善したのは何なんだよ、というとスループットです。 このスループットが劇的に改善する理由は、リクエストの多重化にあるわけです。

HTTP/1.x の場合、リクエストが多重化されていませんでした。なので、同時に複数のリクエストを実行しようとした場合、ある 1 つのリクエストが実行され、残りのリクエストはキューにつまれて待機させられます。 そう、キューで大人しく待機されられます。TCP 接続にどれだけデータが流れているかに関わらずです。 HTTP/1.x ではリクエストが多重化されていないので、レスポンスが返ってくるまでその TCP 接続を使って新しくリクエストを飛ばしたりする事はできません。ひたすらレスポンスが流れてくるのを待つ他ありません。 これを Head of Line Blocking とか言ったりするのですが、まぁつらいのは火を見るよりも明らかですね。 そのため、ブラウザは 6 本 TCP 接続を開いていたりしますが、それでもたかだか 6 並列です。

一方で、HTTP/2 ではリクエストが多重化されています。そのため、TCP 接続になにもデータは流れてないけどレスポンスが返ってくるまで新しいリクエストを行えない...なんて事は無くなるので、リクエストがキューで無駄に長時間待機させられる事もなく、可能な限り次々とリクエストを飛ばす事ができます。結果的にスループットが向上し、帯域幅を有効活用できます。 多重化万歳!

2. gRPC はシリアライズフォーマットに protobuf を使う RPC、ではないよ。

世の中には XML-RPC, JSON-RPC, MessagePack-RPC とかまぁ、{シリアライズフォーマット名}-RPC がいろいろあるから、まぁそういう誤解するんだろうなという感じではあるのですが。

まず、gRPC Motivation and Design Principlespayload-agnostic の項目を見ると、こんな事が書かれています。

Different services need to use different message types and encodings such as protocol buffers, JSON, XML, and Thrift; the protocol and implementations must allow for this. Similarly the need for payload compression varies by use-case and payload type: the protocol should allow for pluggable compression mechanisms.

要は、サービスによって異なるエンコード方式(protocol buffers, JSON, XML, Thrift) を使う必要があるので、プロトコルとその実装はそれらの違うエンコード方式(シリアライズフォーマット) を使えるようにしなければならない、といっています。 gRPC を protobuf 前提の RPC にする気が全くない事が伺えますね。

次に gRPC の HTTP2 上でのプロトコル仕様を見てみましょう。 ここには protocol buffers を使え!なんて事は実は一言も書いてなかったりします。最後の Appendix には、protobuf 使うと楽やぞ、とは書かれていますが。 また、リクエストヘッダに詰める Content-Type について、Content-Type → "content-type" "application/grpc" [("+proto" / "+json" / {custom})] と記述されている事からも、カスタム可能で、protobuf に縛られていない事が分かります。

さらに、pure C# で実装されている gRPC clinet である Grpc.Net.Client パッケージの依存関係を見てみましょう。 依存関係を掘っていっても Google.Protobuf に対する依存が入ってない事がわかります。つまり、Grpc.Net.Client は protobuf からは切り離された存在で成り立っているという事です。 この事からも、gRPC 自体は protobuf に縛られたものではない、という事が分かります。 だから C# で普通に gRPCを使う(= protobuf を利用する)場合、Google.ProtobufGrpc.Tools の 2 つのパッケージを Grpc.Net.Client とは別に追加する事になるわけです。

というわけで、別に gRPC は protobuf を使わないといけないわけではない、ということがお分かりいただけたでしょうか?

まとめ

  • HTTP/2 で向上するのはスループット。
    • 1リクエスト毎のRTTが短くなるわけではない。
  • gRPC は protobuf に縛られた RPC ではない。
    • とはいえ、殆どの場合で protobuf を使うのですけどね!

いろいろ書きましたが、多少なりとも勘違いが解消されるとうれしいです:)