AWS SDK for Go でAPIコールのリトライ

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

渡辺です。

AWSのAPIコールを行う時、ネットワーク系の問題でエラーとなる可能性があるため、適切にリトライを行うことが強く推奨されています(AWS でのエラーの再試行とエクスポネンシャルバックオフ)。 例えば、Javaの場合、こちらのエントリーが参考になるでしょう。 今日はAWS SDK for Goのケースを紹介します。

リトライ処理は不要

結論から言えば、 AWS SDK for Goを利用する場合、APIコールのリトライは実装する必要がありません

以下、調査結果です。

リトライはSDKレベルで組み込まれている

AWS SDK for GoでAPIコールのリトライが不要な理由は、 SDKレベルでリトライが組み込まれている ためです。 実装レベルでは、client.DefaultRetryerがリトライを行っています。

DefaultRetryer implements basic retry logic using exponential backoff for most services. If you want to implement custom retry logic, implement the request.Retryer interface or create a structure type that composes this struct and override the specific methods. For example, to override only the MaxRetries method:

サービス経由でAPIコールが行われると、HTTPリクエストを行うRequestが作成されます。 HTTPリクエストの結果、エラーが発生した場合、Requestの処理では、Retryerにリトライの有無を問い合わせます。 リトライが必要な場合、Retryerの返す待機時間の後、リトライされます。

DefaultRetryerのコードを一部抜粋します。

// ShouldRetry returns true if the request should be retried.
func (d DefaultRetryer) ShouldRetry(r *request.Request) bool {
	// If one of the other handlers already set the retry state
	// we don't want to override it based on the service's state
	if r.Retryable != nil {
		return *r.Retryable
	}

	if r.HTTPResponse.StatusCode >= 500 {
		return true
	}
	return r.IsErrorRetryable() || d.shouldThrottle(r)
}

// ShouldThrottle returns true if the request should be throttled.
func (d DefaultRetryer) shouldThrottle(r *request.Request) bool {
	if r.HTTPResponse.StatusCode == 502 ||
		r.HTTPResponse.StatusCode == 503 ||
		r.HTTPResponse.StatusCode == 504 {
		return true
	}
	return r.IsErrorThrottle()
}

基本的に502, 503, 504のレスポンスコードはリトライ対象となるようです。

もちろん、リトライの待機時間は増分遅延となっています。

// RetryRules returns the delay duration before retrying this request again
func (d DefaultRetryer) RetryRules(r *request.Request) time.Duration {
	// Set the upper limit of delay in retrying at ~five minutes
	minTime := 30
	throttle := d.shouldThrottle(r)
	if throttle {
		minTime = 500
	}

	retryCount := r.RetryCount
	if retryCount > 13 {
		retryCount = 13
	} else if throttle && retryCount > 8 {
		retryCount = 8
	}

	delay := (1 << uint(retryCount)) * (seededRand.Intn(minTime) + minTime)
	return time.Duration(delay) * time.Millisecond
}

リトライ処理を実装するのは面倒ですが、Goでは予め仕組みが提供されていました!

なお、DefaultRetryerとなっていることからも分かるように、カスタムのRetryerを実装して利用することもできます。

最大リトライ回数を設定する

最大リトライ回数は、aws.ConfigのWithMaxRetriesで設定できます。

sess := session.Must(session.NewSession())
svc := ec2.New(
    sess,
    aws.NewConfig().WithRegion("ap-northeast-1").WithMaxRetries(10),
)

デフォルトでは各サービスの推奨値が利用されます。 基本的にはデフォルトで良いと思われますが、通信エラーが増えるようなケースでカスタム設定を検討してください。

まとめ

流石は後発のSDKあって、ベストプラクティスを踏襲しているようです。 APIコールのリトライは面倒な部分なのでSDKでカバーしていると助かりますね。 AWS CLIで書いていたスクリプトはGoに書き換えるだけで品質があがるかもしれません。