وقتی retry کردن اوضاع رو بدتر میکنه
خلاصهٔ کاملتر
نویسنده با یه مثال شروع میکنه: سرویس استعلام هزینهٔ ارسال، بهجای ۱۰۰ میلیثانیه، ۲ ثانیه طول میکشه تا جواب بده ولی خطا نمیده. کانکشنها اشغال میمونن، بعضی درخواستها timeout میشن و کلاینت HTTP اونا رو retry میکنه. بعد تیم worker اضافه میکنه که درخواستهای بیشتری به همون سرویس خسته میفرسته. به گفتهٔ نویسنده هر کدوم از این کارها برای کمک بوده، ولی با هم یه کندی ساده رو به قطعی کل اپ تبدیل میکنن.
حساب سادهست: با ۱۰۰ درخواست در ثانیه و پاسخ ۰.۱ ثانیهای، حدود ۱۰ تماس همزمان داریم؛ با پاسخ ۲ ثانیهای میشه حدود ۲۰۰. این از Little's Law میاد، یعنی تعداد کار در جریان برابر نرخ ورود ضربدر زمان ماندنه. اگه pool فقط ۵۰ جا داشته باشه، صف درست میشه. CPU هم ممکنه پایین بمونه چون گلوگاه انتظار برای I/O هست، پس autoscale بر اساس CPU مشکل رو نمیبینه.
مشکل بعدی ضرب شدن retryهاست. اگه مرورگر، اپلیکیشن و کلاینت سرویس هر کدوم تا ۳ تلاش بکنن، یه کلیک کاربر میتونه تا ۲۷ تلاش به سرویس برسونه. نویسنده با ارجاع به AWS Builders' Library و کتاب SRE گوگل پیشنهاد میده فقط یه لایه صاحب retry باشه، اینجا adapter سرویس، و retryهای SDK یا proxy هم یا حساب بشن یا خاموش. یه مهلت کلی (deadline) هم برای کل عملیات بذارین: تلاش دوم فقط زمانِ باقیمونده رو میگیره، نه یه ۸۵۰ میلیثانیهٔ تازه.
کنترلهای بعدی: concurrency limit (یعنی سقف تعداد تلاشهای همزمان در جریان) که با rate limit فرق داره، و اگه جا نبود سریع یه جواب کنترلشده برگردونین نه اینکه درخواست بره تو صف بیانتها. ظرفیت سرویسهایی که باید مستقل خراب بشن رو هم جدا کنین (bulkheading). یادتون باشه سقف ۱۰ تا روی ۲۰ replica یعنی ۲۰۰ تماس همزمان تو کل fleet.
حتی سقف ۳ تلاش هم تو قطعی بار رو سه برابر میکنه، برای همین نویسنده retry budget پیشنهاد میده: هر درخواست اصلی ۰.۱ توکن جمع میکنه، هر retry یه توکن خرج میکنه و سقف ذخیره ۲۰ توکنه. برای اینکه حساب اعشاری نداشته باشه، با credit عددی پیادهش کرده:
public function tryConsume(): bool
{
if ($this->credits < 10) {
return false;
}
$this->credits -= 10;
return true;
}این شیء باید بین درخواستها مشترک باشه وگرنه هیچوقت retry اجازه نمیده، و تو چند process به ذخیرهسازی atomic نیاز داره. برای backoff هم از full jitter (یعنی تأخیر تصادفی بین صفر و یه سقف رو به رشد) استفاده کنین، به Retry-After احترام بذارین و قبل از sleep جای اشغالشده رو آزاد کنین. تو PHP همگام، usleep() هنوز worker رو نگه میداره، پس تأخیرهای طولانی باید برن تو یه job جدا.
نکات کلیدی:
- retry تو چند لایه ضرب میشه: ۳ × ۳ × ۳ یعنی تا ۲۷ تلاش برای یه کار کاربر.
- فقط یه لایه صاحب retry باشه و retryهای مخفی SDK یا proxy خاموش بشن.
- مهلت کل عملیات رو تقسیم کنین؛ timeout ۵۰۰ میلیثانیه روی ۳ تلاش یعنی ۱.۵ ثانیه انتظار.
- concurrency limit با rate limit فرق داره و باید قبل از گرفتن کانکشن چک بشه.
- retry budget مثال مقاله: ۰.۱ توکن برای هر درخواست اصلی، سقف ۲۰ retry ذخیره.
- برای زمان داخل process از hrtime(true) استفاده کنین، ولی مقدارش رو به سرور دیگه نفرستین.




