إعادة المحاولة واللاتكرارية
ما يُعاد، وما لا يُعاد عن قصد، ولماذا لا يمكن لإرسال مُعاد أن يتضاعف.
الإرسالات
يرفق العميل ترويسة Idempotency-Key بكل إرسال (emails.send وemails.send_batch وtemplates.send وbroadcasts.send)، تُولَّد مرة واحدة لكل **نداء** وتعيد محاولات ذلك النداء استخدامها. ويحجز API ذلك المفتاح قبل أن يرسل أي شيء، فتعيد المحاولة تشغيل الرسالة الأصلية بدل إرسال ثانية، بينما يظل نداءا send() المتعمّدان يرسلان مرتين. فهاتان نيتان مختلفتان وتبقيان مختلفتين.
مرّر idempotency_key الخاص بك لمدّ ذلك الضمان عبر العمليات، فتُعيد مهمة انهارت ثم عملت من جديد تشغيل إرسالاتها بدل تكرارها.
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 | نعم | المنحة إدراج أو تحديث، والترتيب مذكور بالكامل. |
| templates.publish, imports.start | نعم | نشر رأس منشور أصلًا، أو بدء استيراد بدأ أصلًا، يعيده دون تغيير. |
| 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 | لا | اقرأ المعرّف من نتيجة كل كتابة بدل إعادة استخدام المعرّف الذي أرسلته. |
| 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 | لا | كل محاولة تستهلك إجراء ذكاء اصطناعي آخر وتعود بإجابة مختلفة. |
| كل استدعاء آخر | لا | تُرسل مرة واحدة، ويُبلَّغ عن الإخفاق بدل تكراره. |
يُعاد forms.update حتى مع expectedUpdatedAt، لذا قد تعود إعادة المحاولة بعد استجابة ضائعة بالخطأ 409 version_conflict لأن المحاولة الأولى نجحت. اقرأ النموذج قبل أن تحاول مجددًا.
يعيد client.raw.request محاولة طلب GET ويرسل أي شيء آخر مرة واحدة، ما لم تمرّر repeatable=True.
التراجع التدريجي
- محدودة بـ
max_retriesعلى العميل، وافتراضها محاولتان إضافيتان. - لا تحدث إلا بعد إخفاق في الشبكة أو
408أو500أو502أو503أو504. ولا يُعاد429إلا حين يحمل ترويسةRetry-After، وهذا API لا يرسلها، فيرفع حدّ المعدل استثناءً فورًا. وأي رمز حالة آخر يرفع استثناءً في الحال. - أسّية من نصف ثانية حتى ثماني ثوانٍ، مع تشويش زمني، كي لا يعيد أسطول من العملاء مزامنة نفسه عند التعافي.
- تُضبط وتيرتها بـ
Retry-Afterبأي من صيغتيها، delay-seconds وHTTP-date. وحين يحدّد الخادم مدة انتظار، ينتظر العميل تلك المدة بالضبط بدل التراجع التدريجي. - الخادم الذي يطلب أكثر من دقيقة يُعامَل كمن يقول للعميل توقّف لا نَمْ، فيُرفع الخطأ ومعه
retry_after_seconds. فالعودة قبل ما طلب ليست احترامًا لطلبه. - يحدّ
timeoutكل محاولة، لذا قد يستغرق الاستدعاء الذي يستهلك محاولتَي الإعادة كلتيهما ثلاث مهل كاملة إضافةً إلى فترات الانتظار بينها. - لا يُعاد أبدًا استدعاء أُلغي على
AsyncOpenEmail. فالإلغاء ينتشر في الحال، من الطلب أو من الانتظار السابق للمحاولة التالية.