gRPC クライアントの keepalive の薦め


以下では grpc-go (v1.12.2) を前提に書きます。
ただ、 grpc/proposal を参照しているので、ほかの言語のライブラリにも当てはまるところがあると思います。

gRPC クライアントの再接続

gRPC クライアントがサーバーと接続を確立したあとにネットワークが切れた場合、gRPC ライブラリ側で再接続を期待すると思います。
しかし、keepalive が適切に機能していない場合、基本的に再接続は行われません。
grpc-go では、デフォルトでアプリケーションレイヤーでの keepalive は無効です。

RPC タイムアウトが短く設定されているかどうかによって次のように動作します。

  • 設定されている場合、再接続しない。以降のリクエストもタイムアウトしたまま。
  • 設定されていない場合、 20 秒ほどで transport is closing エラーが返ってきて、再接続が行われる。以降のリクエストは正常に処理されるようになる。

GRPC_GO_LOG_SEVERITY_LEVEL 環境変数に info を渡すと、接続状態がログに出るのでいつ接続が確立していつエラーになるのかが確認できます。

この挙動は gRPC を直接利用している場合だけでなく、 例えば cloud.google.com/go のように内部で gRPC を利用しているクライアントライブラリにも当てはまります。

ちなみに、サーバーがシャットダウンするようなケースでは http/2 の GoAway フレームを受信するので、 keepalive に関係なく再接続が行われます。

gRPC クライアントの keepalive

gRPC クライアントで考慮するべき keepalive は 2 種類あります。

  • TCP レイヤーの keepalive
    • これは TCP ソケットの SO_KEEPALIVE オプション
    • 細かい設定をアプリケーションから行えないので、アプリケーションレイヤーの実装が提供されている
  • アプリケーションレイヤーの keepalive
    • http/2 の PING フレームを定期的に送ってタイムアウトしたら再接続する
    • クライアントのバッテリーや DDos 配慮のため、アクティブなストリームがない場合は PING フレームを送らないオプションが提供されている

アプリケーションレイヤーの keepalive は現時点で C, Go, Java のライブラリで実装されています。

keepalive を有効にするコード例

keepalive.ClientParameters が keepalive の設定。
PermitWithoutStream はアクティブなストリームがないときも probe を送るかどうかを表す。

const (
    // GRPCClientKeepaliveTime は活動がなくなってから PING を送るまでの間隔を表す。
    GRPCClientKeepaliveTime = 1 * time.Second
    // GRPCClientKeepaliveTimeout は PING 応答を待つ時間を表す。
    GRPCClientKeepaliveTimeout = 5 * time.Second
)
client, err := storage.NewClient(ctx, option.WithGRPCDialOption(
    grpc.WithKeepaliveParams(keepalive.ClientParameters{
        Time:                GRPCClientKeepaliveTime,
        Timeout:             GRPCClientKeepaliveTimeout,
        PermitWithoutStream: true,
    }),
))