
シェア:
アーロンはNexmoの開発者支持者だった。ベテランのソフトウェア・エンジニアであり、デジタル・アーティスト志望でもあるアーロンは、コードや電子機器、時にはその両方を使って何かを作っているところをよく見かける。彼が何か新しいことに取り組んでいるときは、空気中の部品が燃える匂いでわかるのが通例だ。
バックオフによるAPIレート制限の尊重
所要時間:5 分
この記事は2025年4月に更新されました。
を使用する場合 VonageコミュニケーションAPI-または どのAPI-を使用する場合、そのレート制限を認識する必要があります。レート・リミットは、サービス・プロバイダーがサーバーの負荷を軽減したり、悪意のあるアクティビティを防止したり、単一のユーザーが利用可能なリソースを独占しないようにするための方法の一つです。
この記事では、"良きAPI市民 "であることを保証するために、APIコールを最適に管理する方法を見ていきます。Vonage Communication APIを尊重する方法について見ていきます。 レート制限また、効率的にAPIコールを完了する方法について説明します。
Vonage API Account
To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.
This tutorial also uses a virtual phone number. To purchase one, go to Numbers > Buy Numbers and search for one that meets your needs.
バーチャル電話番号を購入するには APIダッシュボードにアクセスし、以下の手順に従ってください。
Purchase a phone number
あなたの APIダッシュボード
BUILD & MANAGE > Numbers > Buy Numbersを開きます。
必要な属性を選択し、検索をクリックします。
ご希望の番号の横にある購入ボタンをクリックし、購入を確定する。
バーチャルナンバーを購入したことを確認するには、左側のナビゲーションメニューの「BUILD & MANAGE」から「Numbers」、「Your Numbers」の順にクリックします。
良きAPI市民であるとはどういうことか?
外部APIと連携する際には、常にスループットを許容レベルに保つよう努めるべきである。しかし、ミスが起こり、使用量が突然急増し、APIコールを失敗させるレート制限を超えてしまうかもしれない。
このような場合、すぐに再試行したくなるかもしれないが、それは逆効果だ。APIコールが制限速度に達して失敗している場合、これはサービスがあなたにスピードを落とすように指示しているのです。すぐに同じリクエストを再試行することはスピードダウンではなく、一部のサービスから禁止される可能性がある。その代わりに、再試行する前に "バックオフ "し、一時停止する必要がある。
バックオフでAPIコールを遅延させる
バックオフとは、アクションを起こす前に待つことである。待つ時間はさまざまな戦略で計算できるが、最も一般的なものをいくつか挙げる:
一定:各試合の間に一定の時間を待つ。例えば、遅延を1秒とすると、1秒、2秒、3秒、4秒、5秒、6秒、7秒......といった具合だ。
フィボナッチ数:ここでは、現在の試みに対応するフィボナッチ数を使用し、遅延を1s、1s、2s、3s、5s、8s、13sなどにします。
指数:遅延は、失敗した試行回数の2のべき乗として計算される。例えば
2^1 = 2 = 2
2^2 = 2 * 2 = 4
2^3 = 2 22 = 8
2^4 = 2 22 * 2 = 16
2^5 = 2 22 22 = 32
2^6 = 2 22 22 * 2 = 64
2^7 = 2 22 22 22 = 128
他にも固定、線形、多項式などの戦略があるが、この記事では Pythonバックオフパッケージ.
バックオフを試す
BackoffパッケージのデモのためだけにVonageのAPIレート制限を発動させるつもりはない。その代わりに asyncio.
import asyncio
from datetime import datetime
import backoff
import uvloop
start_time = datetime.now().timestamp()
@backoff.on_predicate(backoff.constant, max_time=300, jitter=lambda x: x)
async def slow_operation():
with open("./attempts.log", "a") as f:
f.write(f"{datetime.now().timestamp() - start_time}\n")
return False
async def main(loop):
for x in range(0, 500):
asyncio.ensure_future(slow_operation(), loop=loop)
if __name__ == "__main__":
loop = uvloop.new_event_loop()
loop.create_task(main(loop))
loop.run_forever()この例では slow_operation()関数はエポックからのミリ秒をログに記録して Falseを返し、関数を呼び出すたびにバックオフ・デコレーターが実行されるようにしています。バックオフは slow_operation()を実行し続けます。 max_timeに達するまで実行し続けます。
グラフ用のデータ・ポイントをたくさん生成するために、asyncioのループ内で slow_operation()関数をasyncioループ内で500回キューイングする。
1秒間に試行された関数呼び出しの数をグラフにすると、一定の戦略を使った場合はこのようになる:
A constant traffic pattern visualised
40から60の関数呼び出しの範囲に太い帯域があるので、一定のバックオフは我々のニーズには適切ではない。毎秒、APIにリクエストが殺到し、スループットが高すぎる。
しかし、同じコードを指数戦略で実行すると、まったく異なるグラフが得られる。
A visualisation of a traffic pattern using exponential backoff
このグラフの方がずっと良い。バックオフ戦略によって遅延が増加し、スループットが低下し、うまくいけばレート制限を終了するのに十分な時間が得られることがわかる。しかし今度は別の問題がある。
グラフを見ると、遅延の終わりごろにコールがまとまっているのがわかる。このような束が再びレート制限をトリガーし続けるという状況に陥る可能性がある。このようなバンチの形成を止めるために、ジッターを使用する。
ランダム性を利用したより均等なワークロードの作成
ジッターは、我々のバックオフにおける遅延時間の計算にランダムな要素を加える。
sleep = random.uniform(0, delay)Pythonのバックオフパッケージには Pythonのバックオフパッケージには、デフォルトでこのジッターが含まれています。.上のコード例では、ラムダ関数を使ってこのジッターを除去している。
An exponential backoff traffic pattern visualised
依然として指数戦略を使用しているため、呼び出しの数が非常に早く減少していることがわかるが、ジッターのランダム性が追加されたおかげで、束になることはない。その代わり、1秒あたりの関数呼び出しは少なく、より均等に分散している。
バックオフによるSMSキューの処理
レートリミットはご利用のVonage Communications APIによって異なります。例えば RedactおよびApplication APIには、毎秒170リクエストのレート制限があります。.ただし、キャリアの制限により 送信 SMS のレートリミットは、1 秒あたり 1 リクエストと低い場合があります。.SMSは、上で見たバックオフ技術を適用するのに最適な候補となる。
タスクキューとブローカー
Pythonには数多くのタスクキューがある。Celeryや [huey²] などがある、 RQ, Kuyruk, タスクマスター, ドラマティーク, WorQ-ブローカーMongoDB, Redis, RabbitMQ, SQS.これらのタスクキューの中には、バックオフのサポートが組み込まれているものもあるが、複雑さが増すため、この記事の対象外となっている。
しかし、タスクキュー、バックオフ、ジッターなどの基礎的なテクニックや、 タスクキューを使用する理由付けに慣れたら、上記のタスクキューへのリンクを 再訪することをお勧めする。この記事の残りの部分で見ていくコード例は、スループット管理だけに集中できるように、意図的に簡潔なものになっている。
Vonage通信APIで非同期にSMSを送信する
1つのリクエストのネットワーク遅延がアプリケーション全体をブロックしないようにするため、SMSを非同期で送信することにします。しかし、これは 私たちはということです。 Vonage Python SDKを使うことができないということです。Python SDKは非同期ではありません。 Requestsを使用するため非同期ではありません。.
私たちは Messages API リクエスト例を見ることができます:
このコード・スニペットでは、エンドポイントに POST リクエストを発行していることがわかります。 https://api.nexmo.com/v0.1/message.このリクエストには、送信するコンテンツのタイプに関する情報と、レスポンスとして期待される情報が含まれています。しかし、注意すべき重要な部分は Authorization ヘッダと data (-d) オプションです。
リクエストは JSONウェブトークン(JWT).JWTはオープンな業界標準であり、その生成に役立ついくつかのPythonパッケージが利用可能である。しかし、便利なことに、Vonage Python SDK はリクエストに対して有効な JWT を生成するために呼び出せる関数を持っています。JWT生成は迅速で、ネットワークI/Oを必要とせず、スクリプトの開始時に一度だけ実行されるので、非同期でないことは問題ではありません。
アプリケーションの作成
このコマンドでVonage CLIをグローバルにインストールします:
npm install @vonage/cli -g次に、Vonage API キーとシークレットを使って CLI を設定します。この情報は 開発者ダッシュボード.
vonage config:set --apiKey=VONAGE_API_KEY --apiSecret=VONAGE_API_SECRETプロジェクト用に新しいディレクトリを作成し、そこにCDを入れる:
mkdir my_project
CD my_projectでは、CLIを使ってVonageアプリケーションを作成してください。
vonage apps:create
✔ Application Name … my_project
✔ Select App Capabilities › Messages
✔ Create messages webhooks? … no
✔ Allow use of data for AI training? noこのコマンドは、秘密鍵をmy_projectファイルに保存します。.key.JWTを生成する際に、アプリケーションIDと一緒にこれが必要になります。コマンドを実行すると、アプリケーションIDがターミナルに出力されます。 app:createコマンドを実行するとターミナルに出力されます。
電話を受けるには番号が必要です。以下のコマンドで番号を借りることができます(国番号をあなたのコードに置き換えてください)。例えば、あなたがアメリカにいる場合、次のように置き換えます。 GBを US:
その番号をアプリにリンクさせる:
vonage apps:link --number=VONAGE_NUMBER APP_ID SMSの送信
import vonage
vonage_client = vonage.Client(
application_id=os.environ["VONAGE_APPLICATION_ID"],
private_key=os.environ["VONAGE_PRIVATE_KEY"],
)
jwt = vonage_client.generate_application_jwt()
@backoff.on_predicate(backoff.expo, max_time=300)
async def send_sms(recipient, message):
async with httpx.AsyncClient() as httpx_client:
response = await httpx_client.post(
"https://api.nexmo.com/v0.1/messages",
headers={
"Authorization": b"Bearer " + jwt,
"Content-Type": "application/json",
"Accept": "application/json",
},
json={
"from": {"type": "sms", "number": os.environ["VONAGE_NUMBER"]},
"to": {"type": "sms", "number": recipient},
"message": {"content": {"type": "text", "text": message,}},
},
)
return response.status_code == 202スクリプトの先頭、async関数の外側で、アプリケーションIDと秘密鍵を使ってVonageクライアントをインスタンス化します。これらは環境変数に格納したので、スクリプト内でハードコードされているわけではない。
この send_sms関数はAPIリクエストを行うので、この関数にバックオフ・デコレーターがある。私たちは on_predicateを使用しているので、関数が Falseを返すと、再度リクエストを試みます。を300秒に設定しています。 max_timeを300秒に設定していますが max_attemptを設定することもできます!
非同期POSTリクエストを行うには、httpxを使う。 httpxはPython 3用のHTTPクライアントです。のHTTPクライアントで、Requestsとよく似たインターフェースを持っていますが、非同期をサポートしています。httpxのリクエストは、上で見たcURLの例とできるだけ同じように構成しました。VonageのPython SDKによって生成されたJWTを含むAuthorizationヘッダだけでなく、コンテンツタイプ情報を含むヘッダがあります。
ペイロードは、送信元番号、受信者番号、メッセージを含むJSON文字列である。
最後に、Messages APIがリクエストに対して返したHTTPステータスコードをチェックする。202 Accepted 以外であれば、この関数は False を返し、再試行を引き起こします。
ループでSMSをキューに入れる
私のスクリプトの例では、受信者のリストをハードコードしただけです。
async def main(loop):
recipients = [
"13055550157",
"15615550134",
]
message = "✨✨✨Hello! This is an SMS from the Vonage Communication APIs Messages API using exponential backoff and jitter 😄"
for recipient in recipients:
asyncio.ensure_future(send_sms(recipient, message), loop=loop)
if __name__ == "__main__":
loop = uvloop.new_event_loop()
loop.create_task(main(loop))
loop.run_forever()しかし、ここでタスク・キューやブローカーを使うことができる。また、私はAPI市民としても失格だ!Message APIが米国内でメッセージを送信する場合、1秒間に1メッセージというレート制限があることは知っている!
私のスクリプトは、APIコールを遅延なく行おうとするため、レート制限のトリガーが非常に速くなります。バックオフは、許容される最大スループットを超えたときの管理に役立つが、それは最後の手段であるべきだ。最も効率的であるためには、レート制限にできるだけ近づけたいが、それを超えないことが理想的である。ループにタスクを追加するときに短いスリープを追加することは、この助けになるはずだ。
for recipient in recipients:
asyncio.ensure_future(send_sms(recipient, message), loop=loop)
await asyncio.sleep(1) すべてをまとめる
この記録では、スリープを削除し、一度に数百のリクエストを試みるように例を修正した。しかし、数秒後に何が起こるか見てください。
Traffic to an API being backed off over time until blocked requests end
スクリプトが始まるとすぐに、Messages APIのレート制限を超え、エンドポイントがHTTPステータス429 "Too Many Requests "を返し始めるのがわかる。そこで、スクリプトはバックオフを開始する。最初のうちは、失敗したリクエストの数はほとんど変わらないように見えるが、遅延が指数関数的に増加するにつれて、失敗したリクエストの数は数秒以内に減少し、スクリプトは再び送信を開始できるようになる。
自分でやってみる
本番の負荷がなければ、レート制限を発動するのに十分なリクエストを生成するのはかなり難しいでしょう。このチュートリアルのサンプルスクリプトや、GitHub にある使い方の説明を見てみましょう。
メッセージの送信はアカウントに課金されますのでご注意ください。また、キャリアはあなたが何百回も同じメッセージを送信することを好意的に見てくれません!だから、もしこれを自分で試してみたいなら、ライブのMessages APIに対してテストするのではなく、代わりに モックする.httpxには、このプロセスを簡単にするためのパッケージがいくつかあります。 pytest-httpxや respx.
次はどうする?
Pythonバックオフで利用できる機能の一部しか見ていません。の詳細についてはドキュメントを確認してください。 異なるタイプの例外に対する異なるバックオフ戦略の提供または バックオフが発する様々なイベント.もしbackoffが on_giveupハンドラが実行された場合、スクリプトは Vonage Voice APIを使用してオンコールエンジニアに電話をかけます。
ご質問がある場合、またはあなたが作っているものを共有したい場合は、こちらをクリックしてください。
会話に参加する VonageコミュニティSlack
登録する 開発者ニュースレター
フォローする X(旧ツイッター)最新情報
チュートリアルを見る YouTubeチャンネル
LinkedInの LinkedIn の Vonage デベロッパーページ
最新の開発者向けニュース、ヒント、イベント情報をお届けします。
さらに読む
フルスタック Python - タスクキュー AWS Architecture Blog - Exponential Backoff と Jitter
