تست ۱۱ کتابخونهی HTTP resilience: ریترای همهجا درسته، ترکیبها نه
خلاصهٔ کاملتر
نویسنده که کتابخونهی ffetch رو ساخته، میگه میخواست یه wrapper ساده دور fetch بنویسه که retry، backoff و circuit breaking رو با پلاگین اضافه کنه. ولی وقتی دنبال باگ گشت، به خصوص جایی که این الگوها با هم ترکیب میشن، کدش بزرگ و پیچیده شد. بعد کنجکاو شد ببینه بقیهی کتابخونهها با همین مشکلات چیکار کردن. برای همین ۲۱ سناریو نوشت و ۱۱ کتابخونه رو باهاش تست کرد: ffetch، ky، fetch-retry، ofetch، wretch، resilient-fetch-client، fetch-smartly، @resili/fetch، fetch-resilience، flowshield و ts-retry-circuit.
سناریوها retry و timeout، circuit breaker، bulkhead (یعنی محدود کردن تعداد درخواستهای همزمان با یه صف)، hedging (یعنی فرستادن یه درخواست دوم موازی اگه اولی دیر کرد) و deduplication رو پوشش میدن. بیشترشون با یه fetch اسکریپتشده و fake timerهای Vitest اجرا میشن تا بشه دقیقاً لحظهی abort رو شبیهسازی کرد. دو سناریو هم با fetch واقعی روی یه سرور node:http محلی اجرا میشن. نسخهی هر کتابخونه تو package.json قفل شده، پس هر نتیجه به یه نسخهی مشخص مربوطه.
نتیجه: پنج سناریو تو هر ۱۱ کتابخونه پاس شدن، از جمله retry ساده، تموم شدن تعداد retry و replay کردن body یه POST. یعنی retry ساده دیگه چیز مشترک همهست. ولی ۲۵ شکستِ موجود تقریباً همه یه الگو دارن: یه اتفاق دیگه وسط انتظارِ یه policy پیش میاد. مثلاً لغو وسط sleepِ backoff، تموم شدن deadline تو همون انتظار، یا کاربری که تو صف بوده و رفته.
تو سناریوی abort وسط backoff، شش کتابخونه promise رو تا آخر sleep معلق نگه میدارن و پنجتاشون بعد از abort هم باز درخواست میفرستن. نویسنده مکانیزم رو تو fetch-retry نشون میده: تاخیر ریترای یه setTimeout سادهست که اصلاً init.signal رو نمیخونه. یعنی کسی که abort کرده باید صبر کنه و تازه ببینه یه تلاش دیگه هم رفت.
setTimeout(function () { wrappedFetch(++attempt); }, delay)تو سناریوی bulkhead، چهار تا از پنج کتابخونهای که صف دارن، وقتی کاربرِ تو صف لغو میکنه جاش رو آزاد نمیکنن و درخواست جایگزین رد میشه؛ فقط ffetch پاس شد. تو timeout کلی هم wretch، resilient-fetch-client، fetch-resilience و flowshield بعد از deadline باز تلاش دوم رو میفرستن. hedging کمپختهترین بخشه: فقط سه کتابخونه دارنش و flowshield حتی بعد از timeout شاخهی hedge جدید راه میندازه. wretch هم اگه callbackِ retry خطا بده، promise کاربر رو برای همیشه معلق میذاره.
به گفتهی نویسنده، خود ffetch هم تو یه سناریو شکست خورد: یه پاسخ موفقِ دیررس میتونه فلگ isOpen circuit رو پاک کنه، در حالی که breaker دوباره باز شده. دلیلش اینه که پلاگینش شمارندهی نسل نداره. fetch-smartly و ts-retry-circuit تو همین سناریو بدتر عمل کردن و واقعاً وسط cooldown جدید درخواست بعدی رو راه دادن. نویسنده قبول داره سناریوها از باگهای خود ffetch اومدن و ffetch با ۲۰ از ۲۱ مزیت داره؛ برای همین همهی تنظیمات و انتظارات هر سلول منتشر شده تا بشه نقدشون کرد. گزارش هم هر هفته خودکار بهروز میشه.
نکات کلیدی:
- ۱۱ کتابخونهی HTTP resilience با ۲۱ سناریو تست شدن و ۱۴۷ سلول پیادهسازیشده داشتن.
- پنج سناریو، شامل retry ساده و replay کردن body، تو هر ۱۱ کتابخونه پاس شدن.
- ۲۵ شکست تقریباً همه مربوط به abort، deadline یا لغو وسط انتظار یه policy بودن.
- fetch-retry تنها کتابخونهایه که هیچ timeoutی نداره و sleepش به abort گوش نمیده.
- تو bulkhead با صف، فقط ffetch جای کاربرِ لغوشده رو درست آزاد کرد.
- خود ffetch 5.7.1 هم تو سناریوی half-open concurrency circuit شکست خورد.
- بنچمارک، throughput و کیفیت API اصلاً بررسی نشدن.




