ビッグデータ処理のために作ってるoceanusでは、受け取ったデータをRedis、BigQuery、Google SpreadSheet、SendGrid(メール)など外部に送ることが多く、残念ながら外部とのやりとりはコントロールできない不確定要素が多い。
そのため、いろんなところでリトライ処理が必要になる。
リトライ時の間隔については別記事で書いたので今回は省く。
uyamazak.hatenablog.com
これまで使ったリトライ方法3つをまとめた。
どんなアプリケーションかによって正解は異なると思う。
エラーをtryで取って、except節でリトライ
一番シンプルなのがこれ。
try: result = task() except Exception as e: print("error and retry") # retry result = task()
もしくは処理結果をif文で。
result = task() if not result: # retry result = task()
まあ失敗してもいいけど、一度はリトライしてみるか、程度のあんまり重要じゃないところや、2回以上リトライが必要ない場面ではこれでいいかもしれない。
欠点としては、リトライ回数を増やせないこと。
入れ子にすれば何回でもできないことはないけど、読みにくいし、ダサすぎる。
ループで回す
forなどでリトライ回数分回し、成功次第抜ける。
While True:は無限ループ恐怖症なのか避けるようにしている。
ループ回数や、ループ中の処理などは自由に書ける。
大分端折ったけど、BigQueryや外部のサーバーを使うときは下記のような処理を書いた。
リトライ中ではエラー、全部ダメだったときはクリティカルの出し分けを付けた。
実際はprintではなく、raiseやログ出力している
tryのelse節(tryにエラーが無い時だけ実行する)は、初めて使ったかもしれない。あまり見ないので推奨できないかも。
最初のリトライ回数が0だと、感覚的に違うので、rangeは1から、制限回数にも+1して見やすいようにしている。
from time import sleep CONNECTION_RETRY = 3 def task_with_retry(): for i in range(1, CONNECTION_RETRY + 1): try: result = task() except Exception as e: print("error:{e} retry:{i}/{max}".format(e=e, i=i, max=CONNECTION_RETRY)) sleep(i * 5) else: return True print("critical") return False
パッケージrertyを使う
pypi.python.org
まんまretryというパッケージがpypiに公開されている。
使用する関数にデコレータで利用できるので、コードをシンプルに保てて賢い。
from retry import retry @retry() def make_trouble(): '''Retry until succeed''' @retry(ZeroDivisionError, tries=3, delay=2) def make_trouble(): '''Retry on ZeroDivisionError, raise error after 3 attempts, sleep 2 seconds between attempts.''' @retry((ValueError, TypeError), delay=1, backoff=2) def make_trouble(): '''Retry on ValueError or TypeError, sleep 1, 2, 4, 8, ... seconds between attempts.''' @retry((ValueError, TypeError), delay=1, backoff=2, max_delay=4) def make_trouble(): '''Retry on ValueError or TypeError, sleep 1, 2, 4, 4, ... seconds between attempts.''' @retry(ValueError, delay=1, jitter=1) def make_trouble(): '''Retry on ValueError, sleep 1, 2, 3, 4, ... seconds between attempts.''' # If you enable logging, you can get warnings like 'ValueError, retrying in # 1 seconds' if __name__ == '__main__': import logging logging.basicConfig() make_trouble()
冒頭の記事に書いたリトライ間隔のジッター、loggerなども引数で指定可能
def retry(exceptions=Exception, tries=-1, delay=0, max_delay=None, backoff=1, jitter=0, logger=logging_logger): """Return a retry decorator. :param exceptions: an exception or a tuple of exceptions to catch. default: Exception. :param tries: the maximum number of attempts. default: -1 (infinite). :param delay: initial delay between attempts. default: 0. :param max_delay: the maximum value of delay. default: None (no limit). :param backoff: multiplier applied to delay between attempts. default: 1 (no backoff). :param jitter: extra seconds added to delay between attempts. default: 0. fixed if a number, random if a range tuple (min, max) :param logger: logger.warning(fmt, error, delay) will be called on failed attempts. default: retry.logging_logger. if None, logging is disabled. """
なるべく使用するパッケージは少なくしたいけど、リトライする箇所が増えて、このオプションでなんとかなるのなら、いちいち自分で書かずにこれを使った方がいいと思う。
いちばんやさしいPythonの教本 人気講師が教える基礎からサーバサイド開発まで (「いちばんやさしい教本」シリーズ)
- 作者: 鈴木たかのり,杉谷弥月,株式会社ビープラウド
- 出版社/メーカー: インプレス
- 発売日: 2017/08/10
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る