↪️
Google Cloud Client Libraries for Go の Retry 処理のコードを追ってみる
Google Cloud Client Libraries for Go を使って Google Cloud APIs を呼び出すとき(ネットワークの問題などで)エラーになった場合、デフォルトでリトライします。
裏で何が起こっているか知りたいと思い、コードを追ってみようと思いました。
Retryの処理までコードリーディング
PubSub パッケージの Publish メソッドから追ってみようと思います。
topic.go
func (t *Topic) Publish(ctx context.Context, msg *Message) *PublishResult {
// (略)
t.initBundler()
// (略)
}
topic.go
func (t *Topic) initBundler() {
// (略)
t.publishMessageBundle(ctx, bundle.([]*bundledMessage))
// (略)
}
topic.go
func (t *Topic) publishMessageBundle(ctx context.Context, bms []*bundledMessage) {
// (略)
// Apply custom publish retryer on top of user specified retryer and
// default retryer.
opts := t.c.pubc.CallOptions.Publish
var settings gax.CallSettings
for _, opt := range opts {
opt.Resolve(&settings)
}
r := &publishRetryer{defaultRetryer: settings.Retry()}
res, err = t.c.pubc.Publish(ctx, &pb.PublishRequest{
Topic: t.name,
Messages: pbMsgs,
}, gax.WithGRPCOptions(grpc.MaxCallSendMsgSize(maxSendRecvBytes)),
gax.WithRetry(func() gax.Retryer { return r }))
// (略)
}
Retryerの項目が出てきました。
デフォルトでは次のような設定となっています。
apiv1/publisher_client.go
func (c *PublisherClient) Publish(ctx context.Context, req *pubsubpb.PublishRequest, opts ...gax.CallOption) (*pubsubpb.PublishResponse, error) {
return c.internalClient.Publish(ctx, req, opts...)
}
apiv1/publisher_client.go
func (c *publisherGRPCClient) Publish(ctx context.Context, req *pubsubpb.PublishRequest, opts ...gax.CallOption) (*pubsubpb.PublishResponse, error) {
if _, ok := ctx.Deadline(); !ok && !c.disableDeadlines {
cctx, cancel := context.WithTimeout(ctx, 60000*time.Millisecond)
defer cancel()
ctx = cctx
}
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "topic", url.QueryEscape(req.GetTopic())))
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
opts = append((*c.CallOptions).Publish[0:len((*c.CallOptions).Publish):len((*c.CallOptions).Publish)], opts...)
var resp *pubsubpb.PublishResponse
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.publisherClient.Publish(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
gax.Invoke()
の中で c.publisherClient.Publish
が呼ばれています。
invoke.go
func Invoke(ctx context.Context, call APICall, opts ...CallOption) error {
var settings CallSettings
for _, opt := range opts {
opt.Resolve(&settings)
}
return invoke(ctx, call, settings, Sleep)
}
invoke.go
func invoke(ctx context.Context, call APICall, settings CallSettings, sp sleeper) error {
var retryer Retryer
for {
err := call(ctx, settings)
if err == nil {
return nil
}
// Never retry permanent certificate errors. (e.x. if ca-certificates
// are not installed). We should only make very few, targeted
// exceptions: many (other) status=Unavailable should be retried, such
// as if there's a network hiccup, or the internet goes out for a
// minute. This is also why here we are doing string parsing instead of
// simply making Unavailable a non-retried code elsewhere.
if strings.Contains(err.Error(), "x509: certificate signed by unknown authority") {
return err
}
if apierr, ok := apierror.FromError(err); ok {
err = apierr
}
if settings.Retry == nil {
return err
}
if retryer == nil {
if r := settings.Retry(); r != nil {
retryer = r
} else {
return err
}
}
if d, ok := retryer.Retry(err); !ok {
return err
} else if err = sp(ctx, d); err != nil {
return err
}
}
}
こちらの for 文内で設定に応じて、リトライが実行されてそうです。
Retryの設定変更
NewClientWithConfig
の引数にClientConfig
があります。
pubsub.go
// ClientConfig has configurations for the client.
type ClientConfig struct {
PublisherCallOptions *vkit.PublisherCallOptions
SubscriberCallOptions *vkit.SubscriberCallOptions
}
*vkit.PublisherCallOptions
がこちらになるので、変えれば変更できそうです。
終わりに
クライアントライブラリの公式ドキュメント
- https://cloud.google.com/apis/docs/client-libraries-explained
- https://cloud.google.com/apis/docs/cloud-client-libraries
- https://cloud.google.com/apis/docs/client-libraries-best-practices
- Cloud APIs を簡単かつ直感的に使用できるように、各言語で慣用的なコードを提供します。
- 複数の Cloud サービスでの作業を簡素化するため、クライアント ライブラリ間で一貫したスタイルを指定します。
- Google での認証など、サーバーとの通信に関する下位レベルのすべての詳細を処理します。
- npm や pip など、使い慣れたパッケージ管理ツールを使用してインストールできます。
- 場合によっては、gRPC を使用してパフォーマンス上のメリットが得られます。詳細については、gRPC API をご覧ください。
リトライのことは利点としては書いてなさそうですが、利用側のプログラムでリトライ処理を書かなくてもデフォルトで良しなに行ってくれるのは非常にありがたいです。
Discussion