指数バックオフアルゴリズム
SFN(Step Functions) は指数バックオフによるリトライが行われます。
(エクスポネンシャルバックオフ)
SFNにおける式はこんな感じ
リトライ時間 = IntervalSeconds * BackoffRate ^ (Attempts-1)
簡単に言うと、回数を追うごとに待ち時間が長くなっていくようなリトライアルゴリズムです。
いきなりこんな式を見てもピンと来ないので、エラー時に成功するまでn回リトライするようなケースを考えてみます。
よくあるリトライ
-
リトライ間隔 = 2秒
(固定) リトライ回数 = n回
リトライ間隔は常に一定で、n回リトライを行います。
指数バックオフによるリトライ
-
リトライ間隔 = 2秒
(初回のみ) リトライ回数 = n回
バックオフレート = 3秒
こちらは通常のリトライとは異なり、リトライ回数が増えるごとに、リトライ間隔が増えていきます。
リトライ間隔の計算
-
リトライ間隔 = 2秒
(初回のみ) リトライ回数 = 10回
バックオフレート = 3秒
リトライ間隔を計算するワンライナーのPython スクリプト
(python環境があるコンソールに貼り付ければ動きます)
python -c "for s in (lambda i,m,b: (i*b**(n-1) for n in range(1, m+1)))(2,10,3): print(s)"
2 ・・・ 2 * 3^(1-1) = 2 * 3^0 = 2 * 1
6 ・・・ 2 * 3^(2-1) = 2 * 3^1 = 2 * 3
18 ・・・2 * 3^(3-1) = 2 * 3^2 = 2 * 9
54 ・・・2 * 3^(4-1) = 2 * 3^3 = 2 * 27
162 ・・・ 以下省略
486
1458
4374
13122
39366
総リトライ時間[s]
-
リトライ間隔 = 2秒
(初回のみ) リトライ回数 = 10回
バックオフレート = 3秒
python -c "print(sum((lambda i,m,b: (i*b**(n-1) for n in range(1, m+1)))(2,10,3)),'[s]')"
59048 [s]
総リトライ時間[h]
-
リトライ間隔 = 2秒
(初回のみ) リトライ回数 = 10回
バックオフレート = 3秒
python -c "print(sum((lambda i,m,b: (i*b**(n-1) for n in range(1, m+1)))(2,10,3))/3600,'[h]')"
16.40222222222222 [h]
SFN での書き方
使用するステートマシン
定義(JSON)
{
"Comment": "Start the EC2 instance.",
"StartAt": "StartInstances",
"States": {
"StartInstance": {
"Type": "Task",
"Resource": "arn:aws:states:::aws-sdk:ec2:startInstances",
"Parameters": {
"InstanceIds": [
"i-1234567890abcdef0"
]
},
"Retry": [
{
"ErrorEquals": [
"States.ALL"
],
"IntervalSeconds": 2,
"MaxAttempts": 10,
"BackoffRate": 3
}
],
"End": true
}
}
}
定義(YAML)
※Serverless Framework などでデプロイする時
---
Comment: "Start the EC2 instance."
StartAt: "StartInstances"
States:
StartInstance:
Type: "Task"
Resource: "arn:aws:states:::aws-sdk:ec2:startInstances"
Parameters:
InstanceIds:
- "i-1234567890abcdef0"
Retry:
- ErrorEquals:
- "States.ALL"
IntervalSeconds: "2"
MaxAttempts: "10"
BackoffRate: "3"
End: "true"
ErrorEquals
このエラーに合致したものが、リトライの対象となります。
以下のようなものがデフォルトで用意されているっぽいですね
- States.Runtime
- States.Timeout
- etc...
Lambda なんかだと・・・
ErrorEquals によるエラー判定 (独自実装の例外)
例えばPythonランタイムのLambdaで例外クラスAccountAlreadyExistsException
をraise する
class AccountAlreadyExistsException(Exception): pass
def create_account(event, context):
raise AccountAlreadyExistsException("Account is in use!")
ステートマシンでAccountAlreadyExistsException
をキャッチしたい場合、以下のように書く
"ErrorEquals": ["AccountAlreadyExistsException"]