「システム障害」に見えた、アクセス制限
以前、プログラムのループ処理で、外部のサービスのAPIに、大量のリクエストを一度に送ってしまい、サービス側からアクセスを制限されたことがありました。外から見ると、システムが突然止まったように見え、原因の切り分けに時間がかかりました。
ループでAPIを呼ぶことは、データを一括で取得する場面などで、よく行います。ただ、相手のサービスには、受け付けられる量に限りがあるため、何も考えずに繰り返すと、制限に引っかかります(用語の意味は、レートリミットとはで解説しています)。
なぜ、制限があるのか
サービス側は、すべての利用者に、安定して提供するために、一定の時間内に受け付ける量を、制限しています。1人が大量に使うと、他の利用者に影響が出るためです。
| 制限の例 | 内容 |
|---|---|
| 1秒あたりの回数 | 短い時間に、何回までか |
| 1分・1日あたりの回数 | 一定期間の、合計の上限 |
| 同時に送れる数 | 並行して送れる、リクエストの数 |
具体的な上限は、サービスによって異なります。必ず、公式の説明で確認してください。
3つの約束
ループでAPIを呼ぶ時に、守りたい約束は、次の3つです。
① 間隔を空ける
リクエストの間に、待ち時間を入れます。連続して、続けざまに送らないようにします。
② 件数や並列数に、上限を設ける
一度に処理する件数や、同時に送る数に、上限を決めます。たとえば、「1回の実行で、最大100件まで」と制限します。
③ 失敗したら、間隔を延ばして、回数を決めて再試行する
エラーが返ってきたら、すぐに同じリクエストを繰り返さず、待ち時間を、少しずつ延ばしながら再試行します。そして、再試行の回数にも上限を設けます。永遠に繰り返さないためです。
サンプルコード(Python)
考え方を示す、簡単な例です。実際に使う前に、公式の仕様を確認し、小さなデータで試してください。
import time
import requests
URL = "https://api.example.com/items" # 例のURLです
WAIT_SECONDS = 1.0 # ① リクエストの間隔
MAX_ITEMS = 100 # ② 1回の実行で処理する上限
MAX_RETRY = 3 # ③ 再試行の上限回数
def fetch(item_id):
wait = 2.0
for attempt in range(MAX_RETRY + 1):
response = requests.get(f"{URL}/{item_id}", timeout=10)
if response.status_code == 200:
return response.json()
if response.status_code == 429 and attempt < MAX_RETRY:
time.sleep(wait) # 失敗したら待つ
wait *= 2 # 待ち時間を延ばす
continue
response.raise_for_status()
return None
results = []
for item_id in range(1, MAX_ITEMS + 1):
results.append(fetch(item_id))
time.sleep(WAIT_SECONDS) # ① 間隔を空ける
ポイントは、待ち時間・上限・再試行の回数を、はっきりした数値として、コードの先頭にまとめていることです。後から、公式の上限に合わせて、調整しやすくなります。
AIにコードを書いてもらう時の注意
「全件を取得するコードを書いて」と頼むと、間隔を空けず、連続でリクエストを送るループが、書かれることがあります。動くコードではありますが、相手のサービスの制限は、考慮されていません。
依頼する時に、条件を添えると、安全なコードが出やすくなります。
APIを使って、〇〇を全件取得するコードを書いてください。条件:①リクエストの間隔は1秒空ける ②1回の実行の上限は100件 ③エラーが返ったら、待ち時間を延ばしながら、最大3回まで再試行する ④APIキーは、コードに直接書かず、環境変数から読み込む
書かれたコードも、この条件が入っているかを、必ず確認してください(AIが書いたコードをそのまま使ってはいけない理由)。