생각보다 국내 파이썬 진영에서 @backoff에 대한 정보가 없어 여기에 기록한다.

파이썬에서 백오프는 특정 오류가 발생하면, 이를 백 오프 시켰다가 재시도를 해 주는 데코레이터이다.

먼저 사용전에 pip설치를 해야 한다

$ pip install backoff
Collecting backoff
  Downloading backoff-1.11.1-py2.py3-none-any.whl (13 kB)
Installing collected packages: backoff
Successfully installed backoff-1.11.1

간단한 예제를 하나 들어 보자.

def test():
    number = int(input("Enter an integer: "))

test()

실행 후 값 입력시에 int가 아닌 값을 입력하면 ValueError가 뜨게 되지만, 이를 3회정도 기회를 주고 안되면 오류를 발생시키려고 한다. 방법은 2가지가 있다.

  • input(...)결과를 타입 체크를 하여 int이면 Pass, 아니면 어떤 인디케이터 변수를 두어 3이 넘어가야 오류를 발생시키게 만든다.
  • @backoff를 사용한다.

첫번째 방법은 코드를 많이 수정해야 하며 복잡해진다(정책과 메커니즘의 분리 원칙에도 맞지 않는다). 그래서 두 번째 방법을 쓸 것이다.

import backoff

@backoff.on_exception(backoff.expo,
                      ValueError,
                      max_tries=3)
def test():
    number = int(input("Enter an integer: "))

test()

실행 결과다.

$ python3 3test.py
Enter an integer: e
Enter an integer: r
Enter an integer: t
Traceback (most recent call last):
  File "3test.py", line 9, in <module>
    test()
  File "/home/ubuntu/.local/lib/python3.8/site-packages/backoff/_sync.py", line 94, in retry
    ret = target(*args, **kwargs)
  File "3test.py", line 7, in test
    number = int(input("Enter an integer: "))
ValueError: invalid literal for int() with base 10: 't'

@backoff 데코레이터에서 max_tries=3 으로 입력 기회를 3회주고 계속 오류가 떨어지면 해당 오류를 발생시켰다.

이게 유용할 때는 API서버 접속이나 DB접속과 같이 서로 다른 시스템에 접속을 할 때이다. 다음은 DB접속 + HTTP request접속을 하는 특정 코드의 일부를 발췌한 것이다. (from the book “Robust Python”)

import backoff
import requests
from autokitchen.database import OperationException 
# setting properties of self.*_db objects will
# update data in the database

@backoff.on_exception(backoff.expo,
                      OperationException,
                      max_tries=5) 
def on_dish_ordered(dish: Dish):
    self.dish_db[dish].count += 1

@backoff.on_exception(backoff.expo,
                      OperationException,
                      max_tries=5) 
@backoff.on_exception(backoff.expo,
                      requests.exceptions.HTTPError,
                      max_time=60)
def save_inventory_counts(inventory):
    for ingredient in inventory: 
        self.inventory_db[ingredient.name] = ingredient.count

첫 번째 함수 on_dish_ordered에서의 @backoff는 db입력이 OperationException으로 실패하면 최대 5회까지 재시도를 하라는 것이다.

두 번째 함수인 save_inventory_counts@backoff를 중첩시켜 쓰고 있는데, DB입력 및 HTTP.request도 사용하기 때문에 각각의 경우 재시도 조건을 정의하였다. OperationException의 경우 5회까지 재시도를 하며, HTTPError의 경우에는 60초동안 계속 재시도를 한다.