1行で結論
sters/go-grpc-middleware-circuitbreaker: go-grpc-middleware + go-circuitbreaker
もうちょっと詳しく
grpc-ecosystem/go-grpc-middleware: Golang gRPC Middlewares: interceptor chaining, auth, logging, retries and more. を使うことで、クライアント、サーバー、両サイドで処理前後に任意の処理を追加ができる。
mercari/go-circuitbreaker: A context aware circuit breaker library in Go. を使うことで、任意の処理にサーキットブレーカー、つまり一定回数が失敗したときに処理を行わない、ということができる。
詳しくはこのスライドがわかりやすい: mercari.go #12 go-circuitbreakerのご紹介 - Speaker Deck
ということは、合わせることで、どこかのgRPCサービスにリクエストしようとしたときに、そのサービスがたまによく不安定になる、みたいなケースで、Jitter付きのExponentialBackoffなリトライ戦略( BackoffするときにJitterがあると何がいいのかを見る )よりも、効果的にリクエスト量を減らしてお互いに平和的な解決ができるのでは、と考えた。
Backoffなリトライは1リクエストを頑張って届けるぞ!という方向で、サーキットブレーカーはそのサービス宛すべてのリクエストに影響(実装による)して無駄なリクエストを回避する。というイメージ。
もちろんその間、そのサービスへのリクエストができない、機能が利用できないので、自分たちのアプリケーションがどう振る舞うかを考える必要がある。そのサービスへのリクエストだけが動かないのときにどうするか。ようするにグレースフルデグラデーション。 グレースフルデグラデーションをどうするのか
とりあえずgo-circuitbreakerをUnaryClientInterceptorで使えるようなものを書いてみた。
sters/go-grpc-middleware-circuitbreaker: go-grpc-middleware + go-circuitbreaker
このままだと全体に適用されるので、例えばメソッドやリクエストの内容ごとにコントロールできるようにするといいかもしれないな〜と思いつつも、すぐ書けたのでこれは便利なのでは?という気配が漂っている。
外への通信は何かしらの理由で失敗するという前提で考えて、問題がないかもしれないけれど一旦入れておく、でもいいと思った。発動したときどうするか、と、いつ発動するかの設定値を見定める必要があるけれど、いざというときにあらゆる負荷が爆発せずに済む。
とはいえ、こういったあれこれ考えてアプリケーションコード上で実装するのはまあ大変なので、Envoyとかなにかしらのプロキシでいい感じになってほしいな〜〜とおもうけど、その道も結局あれこれ考えて大変なんだよなあ…。アプリケーションコード上はスッキリする(かもしれない)のは間違いなくいいポイントだろう。