문서로 건너뛰기
Python

재시도와 멱등성

무엇이 재시도되고, 무엇이 의도적으로 재시도되지 않으며, 재시도된 발송이 왜 중복될 수 없는지.

발송

클라이언트는 모든 발송(emails.send, emails.send_batch, templates.send, broadcasts.send)에 Idempotency-Key를 붙이며, 이 키는 **호출**마다 한 번 생성되어 그 호출의 재시도에서 재사용됩니다. API는 무엇이든 발송하기 전에 그 키를 선점하므로, 재시도는 두 번째 메시지를 보내는 대신 원래 메시지를 그대로 재생합니다. 반면 의도적으로 send()를 두 번 호출하면 두 번 발송됩니다. 둘은 서로 다른 의도이며, 앞으로도 다르게 남습니다.

직접 만든 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로 거부됩니다.

그 밖의 모든 것

모든 읽기는 재시도됩니다. 쓰기는 두 번째 동일 요청이 첫 번째와 다른 의미를 가질 수 없는 경우에만 재시도되며, 발송은 멱등성 키가 반복을 재생으로 바꾸기 때문에 여기에 해당합니다.

호출재시도 여부이유
모든 읽기예바뀌는 것이 없습니다.
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예레이블 설정입니다. 두 번 적용해도 한 번 적용한 것과 같습니다.
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예각각 최종 결과를 지정하므로, 두 번째 호출은 첫 번째 호출이 남긴 상태를 그대로 남깁니다.
audiences.add_contact, audiences.add_contacts, audiences.remove_contacts, audiences.import_contacts, suppressions.add, domains.create_address 및 senders.research예반복 호출은 첫 호출의 작업이 이미 끝난 것을 확인하고, 두 번 하지 않고 그 사실을 보고합니다.
contacts.set_photo, imports.upload_chunk, account.set_photo, branding.upload_image, domains.set_logo, domains.set_logo_certificate 및 domains.set_address_photo예다시 보낸 바이트가 첫 시도에서 저장된 것을 대체합니다.
drafts.create, labels.create, webhooks.create, templates.create, rules.create, roles.create, temp_mail.create, files.upload, templates.design 및 forms.design아니요재시도하면 객체가 두 개 남습니다.
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아니요두 번째 회전은 첫 번째 시도가 돌려준 시크릿을 무효화합니다.
webhooks.test아니요두 번째 테스트 전달이 발송됩니다.
webhooks.replay_delivery아니요이벤트를 수신 측에 한 번 더 보내게 됩니다.
emails.translate아니요모델 호출 비용이 들므로, 응답 없는 요청을 재시도하면 같은 답을 두 번 사는 셈이 됩니다.
emails.compose, emails.rewrite 및 emails.suggest_subject아니요시도할 때마다 AI 작업을 한 번 더 쓰고, 매번 다른 답이 돌아옵니다.
그 밖의 모든 호출아니요한 번만 전송되며, 실패는 반복되지 않고 보고됩니다.

forms.update는 expectedUpdatedAt을 지정해도 재시도됩니다. 그래서 응답을 잃은 뒤의 재시도는 첫 시도가 이미 반영된 탓에 409 version_conflict로 돌아올 수 있습니다. 다시 시도하기 전에 양식을 다시 읽으십시오.

client.raw.request는 repeatable=True를 넘기지 않는 한 GET은 재시도하고 그 밖의 요청은 한 번만 보냅니다.

백오프

  • 클라이언트의 max_retries로 제한되며, 기본값은 두 번의 추가 시도입니다.
  • 네트워크 실패나 408, 500, 502, 503, 504에서만 재시도합니다. 429는 Retry-After가 함께 올 때만 재시도하는데 이 API는 그 헤더를 보내지 않으므로, 요청 제한은 곧바로 예외를 발생시킵니다. 그 밖의 상태 코드도 즉시 예외를 발생시킵니다.
  • 0.5초에서 8초까지 지수적으로 늘어나며 지터가 적용되므로, 여러 대가 복구 시점에 다시 동기화되지 않습니다.
  • Retry-After가 delay-seconds든 HTTP-date든 그 값에 맞춰 대기합니다. 서버가 대기 시간을 지정하면 클라이언트는 백오프 대신 정확히 그만큼 기다립니다.
  • 서버가 1분보다 긴 대기를 요구하면 잠시 쉬라는 뜻이 아니라 멈추라는 뜻으로 해석하여, retry_after_seconds를 담은 오류를 던집니다. 요청받은 것보다 일찍 돌아오는 것은 그 지시를 따르는 것이 아니기 때문입니다.
  • timeout은 각 시도의 상한이므로, 두 번의 재시도를 모두 쓴 호출은 타임아웃 세 번에 그 사이의 대기 시간까지 걸릴 수 있습니다.
  • AsyncOpenEmail 호출의 취소는 절대 재시도하지 않습니다. 취소는 요청 중이든 다음 시도 전 대기 중이든 즉시 전파됩니다.