تجربه یه incident با رعایت نکردن اصول ساده
خیلی وقت ها پیش میاد که ما یه سری نکته ساده رو رعایت نمی کنیم و همین موضوع باعث میشه که مشکلات بزرگی در سیستم رخ بده.
من سعی میکنم نکات کوچیکی که طبق تجربه خودم داشتم یا اطرافیانم داشتند رو گاه به گاه منتشر کنم. امروز در مورد یکی از این موارد که باعث incident هم شد صحبت می کنم.
قبل از اینکه شرح بدم incident چی بود در مورد root cause صحبت می کنم که تابع Get از پکیج net/http بود. خیلی هاتون ممکنه برای ارسال درخواست های http در گولنگ از این تابع استفاده کنید و خیلی کار رو هم ساده می کنه.
https://pkg.go.dev/net/http#Getاسم سرویس ها یه چیز دیگه ست من ساده سازی کردم.
یه سرویس اصلی رو در نظر بگیرید که وقتی درخواست بهش میرسه، ابتدا یه سری اطلاعات رو از طریق یه درخواست http از یه سرویس خارجی دریافت میکنه و بعد پاسخ درخواست کاربر رو پس میده.
حالا تصور کنید این سرویس کارهای دیگه ای هم انجام میده، مثلا همین سرویس برای انجام paymentها یه ماژول پرداخت داره که باز هم از درخواست های http استفاده میکنه که با ipgها صحبت کنه و پرداخت هارو انجام بده.
این سرویس با همین مشخصات روی پروداکشن زیر لود بود که فهمیدیم سرویس خارجی ای که اطلاعات رو ازش میگیریم خیلی latency بالایی داره و همین باعث شده درخواست های زیادی باز بمونن برای مدت طولانی و مصرف رم و cpu سرویس بالا رفته و پاسخ ها دچار response time بالا شدن.
اولین نکته ای که به ذهن میرسه اینه که خب بهتره از context timeout استفاده کنیم برای درخواست ها که مثلا یه درخواست http به سرویس خارجی بیشتر از ۳۰ ثانیه باز نمونه.
که خب تابع Get خودش ورودی context نمیگیره، پس باید به فکر راه دیگه ای بود.
برنامه نویس فهمیده بود که تابع Get داره از http.DefaultClient استفاده میکنه و این client یه فیلد timeout داره، پس خیلی سریع تایم اوت رو ست کرد، به این صورت
http.DefaultClient.Timeout = time.Second * 5
کد دیپلوی شد و رفت روی پروداکشن، اتفاقی که افتاد این بود که دیگه مشکل response time وجود نداشت برای سرویس خارجی و اگه درخواست بیشتر از ۵ ثانیه طول میکشید کنسل میشد.
اما یه مشکل جدید خیلی بد بوجود اومده بود، ماژول پرداخت به فنا رفته بود و نمیتونست پرداخت هارو درست مدیریت کنه.
علت چی بود؟ علت این بود که اونم داشت از http.Get استفاده میکرد و با تنظیم شدن تایم اوت ۵ ثانیه، اونم درخواست های بیشتر از ۵ ثانیه رو کنسل میکرد.
خب خیلی ها شنیدید که الگوی singleton خیلی جاها میتونه bad practice باشه و یکی از اون جاها همین default http clientی هست که پکیج net/http ارائه میده.
درست ش این بود که هر ماژول برای درخواست های خودش یه http client مجزا داشته باشه که تایم اوت خاص خودش رو ست کنه. حتی میشه از این تابع NewRequestWithContext
استفاده کرد که context رو هم پشتیبانی کنه.
https://pkg.go.dev/net/http#NewRequestWithContextمشکل singleton اینه که side effectهای تغییر singleton object مبهم میشه، شما میای برای درخواست های سرویس خارجی تایم اوت ست کنی، ولی عملا درخواست های پرداخت رو به فنا میدی..
پکیج net/http و پکیج های دیگه گولنگ برای راحتی استفاده خیلی وقت ها یه default object ارائه میدن، ولی واقعا باید برای استفاده از این objectها احتیاط کرد. بهتره instance خاص خودت رو بسازی که مدیریت stateش فقط خودت رو تحت تاثیر بذاره و side effect نداشته باشه.
@gocasts