ドキュメント本文へスキップ
Python

リトライと冪等性

何がリトライされ、何をあえてリトライしないのか、そしてリトライされた送信が重複しない理由。

送信

クライアントはすべての送信(emails.send、emails.send_batch、templates.send、broadcasts.send)に Idempotency-Key を付与する。キーは**呼び出し**ごとに 1 回生成され、その呼び出しのリトライで再利用される。API は何かを送出する前にそのキーを確保するため、リトライは 2 通目を送るのではなく元のメッセージを再生する。一方で、意図的に send() を 2 回呼べば 2 通送られる。この 2 つは異なる意図であり、区別されたままになる。

この保証をプロセスをまたいで及ぼしたい場合は、独自の idempotency_key を渡すこと。そうすれば、クラッシュして再実行されたジョブは送信を繰り返すのではなく再生する。

idempotency.py
invoice_id = 'inv_4192' client.emails.send(    {'from': sender, 'to': recipient, 'subject': subject, 'text': text},    idempotency_key=f'invoice:{invoice_id}',)

キーは、その送信が必要になった原因から導出すること。時計から作ってはならない。同じキーを異なるボディで再利用した場合は、黙って再生されるのではなく idempotency_key_reuse で拒否される。

その他すべて

読み取りはすべてリトライされる。書き込みがリトライされるのは、同一のリクエストを 2 回目に送っても 1 回目と異なる意味になりえない場合だけである。送信がこれに該当するのは、冪等性キーが繰り返しを再生に変えるからである。

呼び出しリトライ理由
すべての読み取りする何も変化しないため。
emails.send、emails.send_batch、templates.send、broadcasts.sendする冪等性キーが繰り返しを再生に変えるため。
emails.cancel、emails.reschedule、broadcasts.cancel、forms.publish、forms.pause、forms.resume、forms.approve_submission、account.accept_invitation、account.decline_invitationする名前の付いた状態を設定するだけの操作であるため。
threads.update、threads.trash、threads.restoreするラベルの設定であり、2 回適用することは 1 回適用することに等しいため。
threads.snooze, threads.unsnoozeする復帰時刻はボディに含まれており、到着時刻から導出されないため。
labels.update、webhooks.update、settings.update、roles.update、members.update、domains.update、contacts.update、audiences.update、keys.update、emails.update、forms.update、branding.update、chats.rename、threads.update_note、domains.update_address、domains.update_address_forward、account.set_email_notification、account.set_push_muted、app_host.set、workspaces.set_activeする名前の付いたフィールドを設定するだけの操作であるため。
members.grant_address、members.grant_domain、rules.reorder、threads.reorder_notesする付与は upsert であり、並び順は全体が明示されるため。
templates.publish, imports.startするすでに公開済みの head を公開しても、すでに開始済みのインポートを開始しても、そのまま返されるだけであるため。
templates.preview、templates.render、broadcasts.preview、rules.testする描画、集計または評価を行うだけで、何も書き込まないため。
domains.verify, app_host.verifyする繰り返しチェックしても、変わるのはチェックした時刻だけであるため。
contacts.save、contacts.set_audiences、contacts.remove_photo、contacts.block、contacts.unblock、contacts.delete_many、keys.revoke、account.remove_photo、branding.remove_image、domains.remove_logo、domains.remove_logo_certificate、domains.remove_address_photo、app_host.delete、files.revoke_link、files.revoke_all_links、subscriptions.moveするいずれも最終的な結果を指定する操作であり、2 回目の呼び出しは 1 回目が残した状態をそのまま残すため。
audiences.add_contact、audiences.add_contacts、audiences.remove_contacts、audiences.import_contacts、suppressions.add、domains.create_address、senders.researchする繰り返しの呼び出しは 1 回目の処理がすでに済んでいることを検出し、2 回行うのではなくそれを報告するため。
contacts.set_photo、imports.upload_chunk、account.set_photo、branding.upload_image、domains.set_logo、domains.set_logo_certificate、domains.set_address_photoする再送されたバイト列が、1 回目の試行で保存されたものを置き換えるため。
drafts.create、labels.create、webhooks.create、templates.create、rules.create、roles.create、temp_mail.create、files.upload、templates.design、forms.designしないリトライするとオブジェクトが 2 つ残るため。
drafts.updateしない送った id を再利用するのではなく、各書き込みの結果から id を読み取ること。
drafts.delete、labels.delete、webhooks.delete、templates.delete、rules.delete、roles.delete、members.remove、members.revoke_address、temp_mail.delete、temp_mail.delete_messageしない応答が失われた後にリトライすると、成功した処理について失敗が報告されるため。
webhooks.rotate_secretしない2 回目のローテーションが、1 回目の試行で返されたシークレットを無効にするため。
webhooks.testしない2 つ目のテスト配信を送ってしまうため。
webhooks.replay_deliveryしないイベントが受信側にもう一度送られてしまうため。
emails.translateしないモデルの呼び出しを消費するため、応答のなかったリクエストをリトライすると同じ答えに二重に課金されるため。
emails.compose、emails.rewrite、emails.suggest_subjectしない試行のたびに AI アクションをもう 1 回分消費し、異なる答えが返ってくるため。
その他すべての呼び出ししない1 回だけ送信され、失敗は繰り返されるのではなく報告されるため。

forms.update は expectedUpdatedAt を指定していてもリトライされる。そのため、応答が失われた後のリトライでは、1 回目の試行がすでに反映されていたために 409 version_conflict が返ることがある。もう一度試す前にフォームを読み直すこと。

client.raw.request は、repeatable=True を渡さない限り、GET をリトライし、それ以外は 1 回だけ送る。

バックオフ

  • クライアントの max_retries で上限が決まり、既定では追加で 2 回試行する。
  • リトライするのは、ネットワーク障害の後、または 408、500、502、503、504 が返った場合だけである。429 は Retry-After が付いている場合にのみリトライされるが、この API はそれを送らないため、レート制限は即座に例外となる。その他のステータスも直ちに例外となる。
  • 0.5 秒から 8 秒まで指数的に増やし、ジッターを加える。復旧時に多数のインスタンスが再び同期しないようにするためである。
  • Retry-After があればその指示に従う。delay-seconds 形式と HTTP-date 形式のどちらにも対応する。サーバーが待機時間を指定した場合、クライアントはバックオフではなくその時間だけ正確に待つ。
  • サーバーが 1 分を超える待機を要求した場合は、待機せよという指示ではなく中止せよという指示として扱い、retry_after_seconds を付けたエラーを送出する。要求より早く再試行することは、その指示に従ったことにはならない。
  • timeout は各試行の上限なので、2 回のリトライをすべて使った呼び出しには、タイムアウト 3 回分に加えて、その間の待機時間がかかることがある。
  • AsyncOpenEmail の呼び出しのキャンセルはリトライされない。キャンセルは、リクエスト中であれ次の試行前の待機中であれ、直ちに伝播する。