Cloud Workflowsで自動リトライ(Exponential Backoff) を実装する(2) 〜 try/retryでリファクタ
前回の記事
こちらの記事の英語版をdev.toに投稿したところ、Cloud WorkflowsのPdMからtry/retry文を使うと、もっとシンプルに表現できるよ的な親切なコメントをもらいました。
そこで、try/retry文でリファクタしてみました。
(参考) 公式ドキュメント
デフォルトのリトライポリシーを使う
デフォルトポリシー http.default_retry を使ったパターン。
本記事執筆時点でのリトライ条件は HTTPステータスコード(429, 502, 503, 504)とConnectionError, TimeoutError エラーが対象。
バックオフ設定はリトライ回数=>5、初期待ち時間=>1s、乗数 1.25。
https://cloud.google.com/workflows/docs/reference/stdlib/http/default_retry
Retries on 429 (Too Many Requests), 502 (Bad Gateway), 503 (Service unavailable), and 504 (Gateway Timeout), as well as on any ConnectionError and TimeoutError.
Uses max_retries of 5, and backoff as per retry.default_backoff.
https://cloud.google.com/workflows/docs/reference/stdlib/retry/default_backoff
It has initial backoff of 1 second, max backoff of 1 minute, and a multiplier of 1.25.
前回の67行が14行になり、短く表現できました。
main:
params: [input]
steps:
- call_api:
try:
call: http.get
args:
url: https://asia-northeast1-xxx.cloudfunctions.net/foobar
auth:
type: OIDC
result: api_result
retry: ${http.default_retry}
- return_value:
return: ${api_result.body}
※ 冪等性が保証されない場合は、http.default_retry
ではなく http.default_retry_non_idempotent の429,503エラーのみをリトライ対象にするポリシーも用意されています
カスタムのリトライポリシーを使う
カスタムポリシーを使ったパターン
前回と同じリトライ条件(429, 500エラー)、バックオフ設定(初期待ち時間:10秒, 乗数:2)。
前回の67行が29行になり、短く表現できました。
main:
params: [input]
steps:
- call_api:
try:
call: http.get
args:
url: https://asia-northeast1-xxx.cloudfunctions.net/foobar
auth:
type: OIDC
result: api_result
retry:
predicate: ${retry_if_429_or_500}
backoff:
initial_delay: 10
max_delay: 300
multiplier: 2
- return_value:
return: ${api_result.body}
retry_if_429_or_500:
params: [e]
steps:
- what_to_repeat:
switch:
- condition: ${("code" in e) and (e.code == 429 or e.code == 500)}
return: True
- otherwise:
return: False
おまけ
おまけ1) リトライ回数を超えた場合=>最後のErrorオブジェクトがraiseされる
リトライ回数を超えたときの挙動を確認したときの検証コード。
try/retry文をtry/except文で囲むことで補足できます。
main:
params: [input]
steps:
- initialize:
assign:
- count: 0
- try_call_func:
try:
try:
steps:
- increment_count:
assign:
- count: ${count + 1}
- log_before_call:
call: sys.log
args:
text: ${"call API. " + string(count) + " time(s)"}
- call_api:
call: http.get
args:
url: https://asia-northeast1-xxx.cloudfunctions.net/foobar
auth:
type: OIDC
result: api_result
- return_ok:
return: ${api_result.body}
retry: ${http.default_retry}
except:
as: e
steps:
- log_error:
call: sys.log
args:
severity: 'ERROR'
json: ${e}
コンソール画面より
おまけ2) カスタムポリシー内でraiseした時の挙動=>そのままraiseされる
カスタムポリシーを書く際に引数で受けるerrorオブジェクトをハンドリングするわけだが、その中でraiseされたらどうなるのか挙動が知りたかったので検証してみた。
結果的にそのままraiseされるので、カスタムポリシー内のerrorオブジェクトのハンドリングも手抜きしてはいけない。
main:
params: [input]
steps:
- call_api:
try:
call: http.get
args:
url: https://asia-northeast1-xxx.cloudfunctions.net/foobar
auth:
type: OIDC
result: api_result
retry:
predicate: ${just_raise}
backoff: ${retry.default_backoff}
- return_value:
return: ${api_result.body}
just_raise:
params: [e]
steps:
- log_error:
call: sys.log
args:
severity: 'WARNING'
json: ${e}
- raise_error:
raise: ${e}
コンソール画面より
http.default_retry
のリトライ条件に500エラーも加えたい=>カスタムポリシーが必要
おまけ3) http.default_retry
では 500エラーはリトライ条件に含まれていないので、Cloud Functions, Cloud Runでスケーリング問題で発生する500エラーもリトライ条件に加えたい場合は、カスタムポリシーを作る必要があります。
main:
params: [input]
steps:
- call_api:
try:
call: http.get
args:
url: https://asia-northeast1-xxx.cloudfunctions.net/foobar
auth:
type: OIDC
result: api_result
retry:
predicate: ${custom_retry_policy}
backoff: ${retry.default_backoff}
- return_value:
return: ${api_result.body}
custom_retry_policy:
params: [e]
steps:
- assign_retry_codes:
assign:
- retry_codes: [429, 500, 502, 503, 504]
- what_to_repeat:
switch:
- condition: ${("code" in e) and (e.code in retry_codes)}
return: True
- condition: ${("tags" in e) and ("ConnectionError" in e.tags)}
return: True
- condition: ${("tags" in e) and ("TimeoutError" in e.tags)}
return: True
- otherwise:
return: False
Discussion