0. はじめに
今回の記事は Ruby 2.5.7 で動作確認しています 😗
1. 前回のモジュールを rescue 〜 retry
で使ってみよう
前回の記事 Exponential Backoff ってなんじゃらホイ?を Enumerator クラスで書いてみた のコメント欄で、「リトライ処理は rescue 〜 retry
を使うことが多い」というご意見をいただきましたので、前回作った ExponentialBackoff
モジュールを、rescue 〜 retry
のパターンに組み込むことを考えてみたいと思います。
とりあえず組込例は以下のような感じになります
def request_with_retry
request # リクエスト失敗時は例外エラーが発生
rescue => exception
eb ||= ExponentialBackoff.call(max_attempt: 5) # 5回分の値を返す Enumerator オブジェクトを生成
backoff = eb.next rescue nil # 次の値が取得できずに例外が発生した場合は、rescue して nil を代入
sleep backoff and retry if backoff # 次の値が取得できていれば、その値秒だけ sleep して retry
end
5 行目の eb.next
で次の値を取り出すときに、max_attempt
に達していたら StopIteration
エラーが発生しますが、それを後置の rescue
で捕捉して nil を返しています。例外エラーの発生ありきでリトライのフローが制御されており、なんとも微妙です
そこで、リトライ上限に達したかどうかを判定できるメソッドを追加することにしました。
2. #peek?
メソッドを追加しよう
追加するメソッドは、返却される Enumerator オブジェクトを拡張する形で実装することにします。
以下のように、リトライ上限に達したかどうかを boolean で返す、#peek?
メソッドを備えた PeekQues
モジュールを用意しました。
module PeekQues
def peek?
peek
true
rescue StopIteration
false
end
end
そして、ExponentialBackoff.call
が Enumerator オブジェクトを返す際に、上記の PeekQues
モジュールを extend します。
module ExponentialBackoff
def self.call(max_attempt: Float::INFINITY, capacity: Float::INFINITY, base: 1)
Enumerator
.new(max_attempt) { |yielder|
(1..max_attempt) { |attempt|
yielder << [capacity, base * 2 ** attempt].min
}
}
.extend(PeekQues) # 返却するオブジェクトに #peek? メソッドを追加
end
end
3. 追加した #peek?
メソッドを使ってみよう
それでは、追加した #peek?
メソッドを利用して、冒頭のリトライ処理を改善してみます。
こんな感じになります。
def request_with_retry
request # リクエスト失敗時は例外エラーが発生
rescue => exception
eb ||= ExponentialBackoff.call(max_attempt: 5) # 5回分の値を返す Enumerator オブジェクトを生成
sleep eb.next and retry if eb.peek? # 次の値が取得できるなら、その値秒だけ sleep して retry
end
後置の rescue
が消えていい感じになりました
4. おわりに
これで、リトライの制御フローに rescue を利用する必要がなくなり、ExponentialBackoff
モジュールがより使いやすくなりました
みなさんもぜひ使ってみてくださいね