待てば直るエラーがある
クラウドのサービスは、どの呼び出しにも答えますが、その答えがエラーのこともあります。エラーコードを見れば、どの種類かがわかります。
存在しない S3 バケットは、もう一度聞いても、存在しません。混み合っている DynamoDB の表は、1秒後に答えるかもしれません。このレッスンでは、2つを見分け、再試行してよいほうだけをやり直すことを学びます。
このレッスンのゴール
メモ
再試行するエラーと、直すエラーを、見分けることができる。
キーワード
| ことば | いみ | れい |
|---|---|---|
| エラーコード | 何が悪かったかを表す、短い名前 | NoSuchKey |
| ステータスコード | 答えを種類ごとに分ける数 | 404 |
| スロットリング | 呼び出しが多すぎるので、サービスが断ること | |
| 再試行 | 同じ呼び出しを、もう一度送ること | |
| バックオフ | 再試行のたびに、待つ時間を長くすること | |
| ジッター | 待つ時間のうち、ランダムな割合。呼び出し側が同時に動かないようにする |
学生が入力するセルは、このプレビューでは空白です。
エラーには、コードと状態がある
呼び出しが失敗すると、boto3 はエラーを渡します。エラーには、404 のようなステータスコードと、NoSuchKey のようなエラーコードがあります。
調べるのは、エラーコードと状態です。メッセージは調べません。文言はサービスによって変わることがあり、エミュレーターの文言は、本物と違う場合があるからです。
例: 取り込んだ答えのエラー
| 呼び出し | Code | 状態 | 再試行するか |
|---|---|---|---|
| get_object、キーがない | NoSuchKey | 404 | しない。キーを直す |
| head_object、キーがない | 404 | 404 | しない。読む本文がない |
| list_objects_v2、バケットがない | NoSuchBucket | 404 | しない。バケットを作る |
| put_item、条件が偽 | ConditionalCheckFailedException | 400 | しない。その答えが答え |
| query、キーの名前がちがう | ValidationException | 400 | しない。呼び出しを直す |
| send_message、キューがない | AWS.SimpleQueueService.NonExistentQueue | 400 | しない。キューを作る |
学生が入力するセルは、このプレビューでは空白です。
実行: コードと状態を読む
answers = [
{"error": {"Code": "NoSuchKey", "Message": "Missing.", "HTTPStatusCode": 404}},
{"error": {"Code": "ConditionalCheckFailedException", "Message": "No.", "HTTPStatusCode": 400}},
{"error": {"Code": "InternalServerError", "Message": "Oops.", "HTTPStatusCode": 500}},
{"Items": []},
]
for answer in answers:
if "error" not in answer:
print("成功")
else:
error = answer["error"]
print(error["Code"], error["HTTPStatusCode"])
次はうまくいくものだけを再試行する
エラーには、「今はだめ」と言うものがあります。スロットリングは、呼び出しが多すぎる、という意味です。状態が 500 以上なら、サービスの側の故障です。どちらも、待てば通るかもしれません。
ほかのエラーは、「この呼び出しはだめ」と言います。呼び出しが間違っているので、同じ呼び出しは、何度でも失敗します。再試行せず、直します。
よくある間違い
注意
念のため、どのエラーも再試行する人が多いです。すると、間違ったキーが5回失敗し、待つ時間が無駄になります。再試行するのは、スロットリングとサーバーの故障だけです。ほかは、同じ呼び出しが同じ答えになるので、直します。
実行: 再試行と修正に分ける
# スロットリングの Code は文書にあるもので、ここで取り込んだものではありません。
THROTTLING = ["ThrottlingException", "ProvisionedThroughputExceededException", "SlowDown"]
def worth_retrying(error):
if error["HTTPStatusCode"] >= 500:
return True
return error["Code"] in THROTTLING
errors = [
{"Code": "NoSuchKey", "HTTPStatusCode": 404},
{"Code": "SlowDown", "HTTPStatusCode": 503},
{"Code": "ThrottlingException", "HTTPStatusCode": 400},
{"Code": "ValidationException", "HTTPStatusCode": 400},
]
for error in errors:
print(error["Code"], worth_retrying(error))
# THROTTLING に "RequestLimitExceeded" を足して、試しましょう。
バックオフは、待ちを長くする
サービスが混んでいるときに、1ミリ秒後に再試行すると、もっと混みます。だから待ちます。失敗のたびに、待ちを長くします。待ちを2倍にするのが、よくある方法です。上限も決めて、待ちが限りなく伸びないようにします。
さらにジッターを足します。その時間の、ランダムな割合だけ待つ方法です。ないと、100の呼び出し側が同時に起き、同時にまた失敗します。
例: 各回の待ちの上限
| 回 | 2倍 | 上限 8 のとき | pick が 0.5 のときの待ち |
|---|---|---|---|
| 1 | 0.5 秒 | 0.5 | 0.25 |
| 2 | 1 秒 | 1 | 0.5 |
| 3 | 2 秒 | 2 | 1 |
| 4 | 4 秒 | 4 | 2 |
| 5 | 8 秒 | 8 | 4 |
| 6 | 16 秒 | 8 | 4 |
学生が入力するセルは、このプレビューでは空白です。
実行: テストできる待ち
# pick は 0 から 1 までの数です。本物の呼び出し側は、random.random() を渡します。
# テストは決まった数を渡すので、答えがいつも同じになります。
def backoff(attempt, base, cap, pick):
ceiling = min(cap, base * 2 ** (attempt - 1))
return ceiling * pick
for attempt in range(1, 7):
print(attempt, backoff(attempt, 0.5, 8, 1.0), backoff(attempt, 0.5, 8, 0.5))
# 上限を 8 から 2 に変えましょう。どの回が変わりますか。