やりたいことは、もうエラーだ!ってなるまでにリトライできてsleepの秒数を1, 4, 16的な感じで増やして行きたい。
箇条書きすると
- urlアクセスする。
- 偶然にもネットワークが不調でエラー
- sleep 1秒挟んでリトライ
- 偶然にもネットワークが不調でエラー(もしくは取得できてうれしい!!)
- sleep 4秒挟んでリトライ
- 偶然にもネットワークが不調でエラー
- もうエラーだ!
なぜリトライしたいか
- 通信は失敗するものなので!
- 失敗しない前提で作ったシステムは死ぬ
sleepの秒数をなぜ増やしていくか
下記に則るため。
ざっくりいうと、失敗回数が増えるに連れて再送信するまでの待ち時間を指数関数的に増やす仕組み
AWSとかGCPとかでもリトライはこの方法を使うべきと書いている(仕様が微妙にことなるみたい)。
Exponential Backoff
• リトライの間隔を指数関数的に増加させる
例:1秒後、2秒後、4秒後、8秒後、、、
• 長期障害発生時にシステムへの不必要な負担を軽減
大規模分散システム内での常に部分障害(特に一時的)が発生している
• 障害発生時にいちいちシステムを止めていては高可用性を実現できない
リトライにより後に成功する可能性が高い
HTTP Status Code | リトライ |
---|---|
200 (OK) | N/A |
400 (Client Error) | 非推奨(回復の見込みなし) |
500 (Server Internal Error) | 推奨 |
503 (Server Unavailable) | 推奨 |
どう実装するのか
retryableって言う便利gemがあるので使う
使い方の例
OpenURI::HTTPErrorが出たときのみリトライ
(400エラーの非推奨も合わせて実行してしまうがopen-uriの例外がこれしかない←コメントで教えてもらいました!コード修正しました)
リトライ回数は3回
リトライするまでの待ち時間を[1, 4, 16, ...]にする場合
(厳密には待ち時間に乱数をつけるべきだが省く)
retry_setting = {
on: OpenURI::HTTPError,
tries: 3,
sleep: ->(n) { 4**n }
}
Retryable.retryable(retry_setting) do |retries, exception|
break if retries > 0 && exception.io.status[0] == "400"
open('https://www.example.com/')
end
最後に
自前でbegin書いちゃってたんですが、Gemを使うことによってリトライ処理の書き忘れなどなくなって幸せです。