aws-sdk-javaのリトライ間隔(エクスポネンシャルバックオフとジッター)を実際に見てみる

昨日の記事の続きです。

jappy.hatenablog.com

aws-sdk-java に限らず AWS SDK 全般の話ですが、リクエスト時にエラーがあった場合、指定された回数だけ内部でリトライが行われます。
このリトライ時、次の試行までの間隔(本記事内では「遅延」と呼びます)を決めるのに、エクスポネンシャルバックオフとジッターというアルゴリズムが使われています。
この数値を実際の aws-sdk-java のクラスを用いて確認してみました。

が、その前に…。実は、検索したらすでにクラスメソッドさんが同じ趣旨の記事を書かれていましたことに気づきました。
二番煎じ感は否めませんが、クラスメソッドさんの場合は Node.js で、本記事は Java 版と、まったくの同じ内容ではないため、記事としてはまとめます。とはいえ、根柢のアルゴリズムは同じなので、似たような結果になるわけですが…。

dev.classmethod.jp

BackoffStrategy

aws-sdk-java の場合、エラーの種類などに応じてベース遅延や再試行メカニズムが複数用意されており、これらは BackoffStrategy というインタフェース(を実装したクラス)で管理されます。
どれを選択するかによって、遅延の計算結果も変わります。

主なものは以下の2つです。

  • FullJitterBackoffStrategy
    • 5XX エラーなど、非スロット系のエラー時に使われます。
    • DynamoDB の場合はベース遅延が 25 ミリ秒、それ以外の場合はベース遅延が 100 ミリ秒と違いがあります。
  • EqualJitterBackoffStrategy
    • スロットル系エラー時に使われます。
    • ベース遅延は 500 ミリ秒です。

利用シーンが限定的ですが、以下のようなメカニズムもあります(正確には BackoffStrategy の実装クラスではないですが)

  • DefaultBatchWriteRetryStrategy
    • DynamoDBMapper を使っていて BatchWriteItem がエラーになった時に使われます。BatchWriteItem では、書き込み完了できなかった一部のレコードが UnprocessedItems として返却される作りになっており、DynamoDBMapper はこれら未処理レコードを細かく分割して再試行するようになっており、その際のリトライに使われます。

確認してみた

遅延を決めるメソッドは RetryPolicy.java 内にある BackoffStrategy#delayBeforeNextRetryなので、これを呼び出すことで確認できそうです。

今回は以下の4つのパターンで試しています。と言いつつ、3番目と4番目は中身がほぼ同じなので、実質3パターンですね…。

  • DEFAULT
    • DynamoDB 以外のサービスへのリクエストでのスロットルエラー用(ベース遅延 25 ミリ秒の FullJitterBackoffStrategy
  • DYNAMODB_DEFAULT
    • DynamoDB へのリクエストでのスロットルエラー用(ベース遅延 100 ミリ秒の FullJitterBackoffStrategy
  • DEFAULT(throttleError)
    • DynamoDB 以外のサービスへのリクエスト時のスロットルエラー用(ベース遅延 500 ミリ秒の EqualJitterBackoffStrategy
  • DYNAMODB_DEFAULT(throttleError)
    • DynamoDB へのリクエストでのスロットルエラー用(ベース遅延 500 ミリ秒の EqualJitterBackoffStrategy

また、リトライ回数は10回までの結果を出力させてます。
デフォルトのリトライ回数は高々10回であり、かつ、最大遅延が20秒に設定されているので、回数を増やしていってもあまり面白い結果にはなりません。

gist.github.com

実際の結果は以下の通りとなりました。
(ジッターの効果によってランダムなバラつきが発生するため、結果は実行するたびに変わります)

000, DEFAULT:     56, DYNAMODB_DEFAULT:      5, DEFAULT(throttleError):    472, DYNAMODB_DEFAULT(throttleError):    420
001, DEFAULT:    152, DYNAMODB_DEFAULT:      3, DEFAULT(throttleError):    710, DYNAMODB_DEFAULT(throttleError):    657
002, DEFAULT:    345, DYNAMODB_DEFAULT:     27, DEFAULT(throttleError):   1748, DYNAMODB_DEFAULT(throttleError):   1553
003, DEFAULT:      6, DYNAMODB_DEFAULT:    104, DEFAULT(throttleError):   2544, DYNAMODB_DEFAULT(throttleError):   3162
004, DEFAULT:    813, DYNAMODB_DEFAULT:     28, DEFAULT(throttleError):   7258, DYNAMODB_DEFAULT(throttleError):   4162
005, DEFAULT:   1279, DYNAMODB_DEFAULT:    798, DEFAULT(throttleError):  13568, DYNAMODB_DEFAULT(throttleError):  10524
006, DEFAULT:   1308, DYNAMODB_DEFAULT:    565, DEFAULT(throttleError):  12845, DYNAMODB_DEFAULT(throttleError):  16461
007, DEFAULT:   3089, DYNAMODB_DEFAULT:   2425, DEFAULT(throttleError):  17140, DYNAMODB_DEFAULT(throttleError):  12536
008, DEFAULT:   1517, DYNAMODB_DEFAULT:   4147, DEFAULT(throttleError):  17606, DYNAMODB_DEFAULT(throttleError):  13507
009, DEFAULT:  17415, DYNAMODB_DEFAULT:   3009, DEFAULT(throttleError):  10704, DYNAMODB_DEFAULT(throttleError):  13932

エクスポネンシャルバックオフは巷でもよく聞くので、遅延は指数的に増加するものかと思いきや、ジッターのおかげでかなりブレがありますね。

また、DynamoDB の 5XX エラーの遅延はかなり短いこと、スロットルエラーの遅延は長めなことも、改めて確認できました。