تصور کنید برنامههای شما در محیط کوبرنتیز، با وجود داشتن منابع آزاد در گرهها، چندین بار در ثانیه یخ میزنند. این «دیوار عملکرد»، نتیجهی مستقیم یک عادت رایج در مهندسی پلتفرم است: تعیین محدودیتهای CPU؛ اما طبق تحلیل فنی منتشر شده در گیتهاب در ۱۴ اوت ۲۰۲۶، حذف این محدودیتها میتواند تأخیرهای انتهایی (Tail Latency) را در زمان پیک ترافیک تا ۸۷٪ کاهش دهد.
سالهاست که مهندسان پلتفرم، محدودیتهای CPU و حافظه را به عنوان مکانیسمهای ایمنی مشابه میبینند. اما در واقعیت، CPU یک منبع «فشردهپذیر» است — یعنی کمبود آن فقط باعث کند شدن برنامه میشود. در مقابل، حافظه فشردهپذیر نیست و کمبود آن بلافاصله منجر به خطای Out-of-Memory (OOM) و بسته شدن برنامه میشود. این تفاوت بنیادی یعنی در حالی که محدودیت حافظه برای پایداری گره ضروری است، محدودیت CPU اغلب هیچ حفاظتی ارائه نمیدهد که یک «درخواست CPU» (CPU Request) بهدرستی پیکربندیشده، پیشتر فراهم نکرده باشد.
همانطور که در تحلیلهای قبلی ما دربارهی بهینهسازی منابع در محیطهای ابری اشاره کردیم، درک تفاوت میان تخصیص تضمینشده و سقف مصرف، کلید کاهش هزینههاست. در این راستا، مدیریت بهینه منابع سختافزاری در کوبرنتیز بهویژه برای بارهای کاری سنگین مانند مدلهای زبانی، اهمیت دوچندانی مییابد.
مکانیسم «یخزدگی» برنامهها
هستهی لینوکس محدودیتهای CPU را با استفاده از زمانبندی CFS (Completely Fair Scheduler) در پنجرههای ۱۰۰ میلیثانیهای اعمال میکند. اگر یک پاد (Pod) محدودیت ۵۰۰ میلیکور (۵۰۰m یا نصف یک هسته) داشته باشد، در هر پنجره تنها ۵۰ میلیثانیه زمان CPU دریافت میکند. به محض اتمام این بودجه، هسته کل برنامه را تا شروع پنجره بعدی متوقف میکند؛ پدیدهای که به آن گلوگاه یا تروتلینگ (Throttling) میگویند.

این توقفها برای محیطهای چندرشتهای مثل .NET بهشدت مخرب است. یک سرویس معمولی چندین رشته (Thread) را بهطور همزمان اجرا میکند — از مدیریت درخواستهای HTTP گرفته تا مصرفکنندگان پسزمینه (Background Consumers) و جمعآوریکننده زباله (Garbage Collector). تمام این رشتهها از یک بودجه واحد استفاده میکنند. در یک گره ۴ هستهای، تا ۴ رشته میتوانند بهطور همزمان اجرا شوند. اگر ۸ رشته فعال باشند، میتوانند بودجه ۵۰ میلیثانیهای را تنها در ۱۲.۵ میلیثانیه از زمان واقعی مصرف کنند. نتیجه این است که برنامه تا ۱۰ بار در ثانیه یخ میزند و تأخیر p99 را نابود میکند، در حالی که نمودارهای میانگین مصرف CPU در داشبوردها، بهطور فریبندهای پایین و سبز میمانند.

تلهی «داشبوردهای سبز»
ابزارهای نظارتی استاندارد اغلب این مشکل را پنهان میکنند چون بر دادههای میانگین تکیه دارند. یک برنامه میتواند تمام روز در وضعیت تروتلینگ باشد، اما چون میانگین یکدقیقهای هرگز به سقف نمیرسد، داشبورد سبز میماند. برای دیدن حقیقت، مهندسان باید معیار container_cpu_cfs_throttled_periods_total را بررسی کنند.

این تحلیل با ارسال باری که بهطور میانگین تنها به ۳۲۰ میلیکور نیاز داشت به برنامهای با محدودیت ۵۰۰ میلیکور، این موضوع را ثابت کرد. با وجود اینکه میانگین مصرف بسیار کمتر از سقف بود، برنامه همچنان دچار توقف شد. در آزمونی با ۸ تسک موازی با مدت زمان ۵ میلیثانیه و نرخ ۸ درخواست در ثانیه، محدودیت CPU باعث شد درخواستهای کند، ۲.۴ برابر کندتر شوند. این نشان میدهد چرا موازیسازی تروتلینگ را بدتر میکند: ۱۶ رشته میتوانند سهمیه ۳۰۰ میلیکوری را در کمتر از ۲ میلیثانیه از زمان واقعی بسوزانند و باعث توقف کامل شوند، نه یک کند شدن تدریجی.
درخواستها در برابر محدودیتها
کوبرنتیز از درخواستهای CPU (CPU Requests) برای تعیین «وزن» یک پاد در زمانبند CFS استفاده میکند. وقتی گره کاملاً مشغول است، پادها بر اساس این وزنها CPU را تقسیم میکنند. اما اگر گره CPU آزاد داشته باشد، پادها میتوانند آن را بهطور رایگان «قرض بگیرند» — مگر اینکه یک محدودیت سختگیرانه (Limit) وجود داشته باشد.

حذف محدودیت به برنامهها اجازه میدهد از این ظرفیت آزاد برای پردازشهای لحظهای (Burst) استفاده کنند بدون اینکه مزاحم همسایگان شوند. برای بررسی ترس از «همسایه پرصدا»، یک پاد تشنهی CPU (با ۸ رشته در ۱۰۰٪ مصرف و بدون محدودیت) در کنار یک برنامه قربانی قرار داده شد. نتایج نشان داد که درخواست CPU برنامه قربانی، حفاظت کافی را فراهم میکند و تأخیر p99 کمتر از ۷ میلیثانیه تغییر کرد. این ثابت میکند که درخواستها — و نه محدودیتها — حفاظ واقعی هستند.

چرا گره از کار نمیافتد؟
بسیاری میترسند بدون محدودیت، چند پاد حریص باعث کرش کردن گره شوند. این تصور غلط است و ناشی از شباهتسازی CPU با حافظه است. سه لایه حفاظتی از سقوط گره جلوگیری میکند:
- CPU رزرو شده برای سیستم: سیستمعامل و kubelet سهم CPU خود را خارج از استخر پادها دارند. یک پاد مشغول نمیتواند به این بخش دست بزند و این امر تضمین میکند که گره پاسخگو باقی بماند.
- محاسبات زمانبند: زمانبند کوبرنتیز پادها را تنها در صورتی جایگذاری میکند که مجموع درخواستهای آنها با ظرفیت گره همخوانی داشته باشد. مجموع درخواستها هرگز از ظرفیت کل فراتر نمیرود و سهم هر پاد تضمین میشود.
- فشردهپذیری: همانطور که گفته شد، CPU فشردهپذیر است. در صورت تداخل منابع، برنامهها فقط کندتر میشوند و کرش نمیکنند. حافظه غیرفشردهپذیر است و به همین دلیل محدودیتهای حافظه باید باقی بمانند.
پاسخ به اعتراض «قطعکننده مدار»
برخی تیمها از محدودیتهای بالا (مثلاً limits.cpu: 1) به عنوان یک قطعکننده مدار (Circuit Breaker) استفاده میکنند. اما این تحلیل استدلال میکند که این مکانیسم ناقص است. محدودیتی که آنقدر بالا باشد که هرگز فعال نشود، از هیچکس محافظت نمیکند. محدودیتی که آنقدر پایین باشد که فعال شود، فقط به همان پادی که به آن متصل است آسیب میزند، نه به همسایگان.
اگر تیمی فاقد ResourceQuota یا LimitRange باشد، ممکن است نگران باشد که پادهای بدون محدودیت خطرناک هستند. در واقعیت، پادی که درخواست (Request) ندارد یا درخواستش بسیار کوچک است، خودش قربانی است نه مهاجم؛ زیرا کمترین وزن را دریافت میکند و در زمان تداخل منابع، اولین پادی است که تحت فشار قرار میگیرد. safeguard واقعی، زمانبند است که تضمین میکند مجموع درخواستها از ظرفیت گره فراتر نرود.
زنجیره سقوط تا قطعی کامل
تروتلینگ شدید CPU فقط باعث کندی نمیشود، بلکه میتواند از طریق یک زنجیره شکست، منجر به قطعی کامل سیستم شود:
۱. یخزدگی: برنامه بودجه خود را در ابتدای پنجره ۱۰۰ میلیثانیهای میسوزاند و بقیه زمان را معلق میماند.
۲. گرسنگی GC: محیط اجرا CPU کافی برای بازپسگیری حافظه را ندارد. یک پیک حافظه معمولی به یک مارپیچ مرگ تبدیل میشود.
۳. خطای OOM: تخصیص حافظه سریعتر از بازپسگیری پیش میرود و منجر به OOM Kill میشود — یک شکست حافظه که علت آن تنظیمات CPU است.
۴. تایماوت وابستگیها: فراخوانیهای دیتابیس یا کشها تایماوت میدهند چون «فراخواننده» یخ زده است، نه چون دیتابیس کند است.
۵. رفتار اشتباه خاموش: Feature Flagها یا جستجوهای پیکربندی که تایماوت میدهند، به مقادیر پیشفرض برمیگردند و محصول بدون هشدار، اشتباه رفتار میکند.
۶. وضعیت زامبی: بررسیهای Readiness که فقط باز بودن پورت را چک میکنند، پاس میشوند و ارکستراتور پاد بیمار را در چرخه نگه میدارد.
جریمه عملکرد در .NET
محیطهای اجرای .NET محدودیتهای CPU را میخوانند تا اجزای داخلی خود را اندازه کنند. یک محدودیت پایین (مثلاً ۱۰۰ میلیکور) برنامه را مجبور میکند با ProcessorCount برابر ۱ و تنها یک رشته در ThreadPool شروع به کار کند، فارغ از اینکه اندازه واقعی گره چقدر است. این کار برنامه را به حالت Workstation GC (یک Heap) محدود میکند، در حالی که حالت Server GC (یک Heap برای هر پردازنده) بسیار سریعتر است.
در تستهای یک گره ۴ هستهای، پادی بدون محدودیت شاهد جهش ProcessorCount از ۱ به ۴ بود که Server GC را فعال کرد و عملکرد را بهشدت بهبود بخشید. برای جلوگیری از رفتار متناقض در اندازههای مختلف گره، توصیه میشود مقدار DOTNET_PROCESSOR_COUNT بهطور پیشفرض در Helm chartهای مشترک تنظیم شود.
اثرات مالی
حذف محدودیتها به تیمها اجازه میدهد درخواستهای CPU را بر اساس مصرف واقعی P95 (سطحی که تنها در ۵٪ مواقع از آن فراتر میروند) تنظیم کنند، نه اینکه برای جنگ با تروتلینگ، اعداد را بهطور مصنوعی بالا ببرند. در مدلی برای یک خوشه ۱۰۰۰ هستهای:
- وضعیت فعلی: ۵۰۰ هسته درخواست شده است.
- پیک مصرف واقعی: ۱۴۰ هسته.
- درخواست بهینه: حدود ۲۸۰ هسته (۲ برابر پیک برای ایمنی).
- هستههای آزاد شده: ۲۲۰ هسته.

با تخمین ۳۵ دلار برای هر هسته در ماه، این تغییر میتواند سالانه حدود ۹۲,۰۰۰ دلار در هر خوشه صرفهجویی کند. البته این صرفهجویی به نوع استخر (Pool) بستگی دارد؛ چون VMها تنها زمانی حذف میشوند که هم CPU و هم حافظه آزاد شوند:
- استخرهای Memory-bound: ماشینهای مجازی فارغ از تغییرات CPU باقی میمانند و صرفهجویی صفر است. اینها اغلب سنگینترین مصرفکنندگان CPU هستند.
- استخرهای General-purpose: صرفهجویی زمانی محقق میشود که Autoscaler (مانند Karpenter) گرههای خالی را حذف کند.
- استخرهای با بار کم: مدل کامل صرفهجویی CPU در اینجا اعمال میشود.
برنامه عملیاتی برای حذف محدودیتها
برای حذف ایمن محدودیتها، این رویکرد مرحلهای پیشنهاد میشود:
۱. تنظیم محیط اجرا (Runtime Tuning):
- تنظیم
DOTNET_PROCESSOR_COUNTبرای برنامههای .NET در سطح کل ناوگان. - تنظیم
GOMAXPROCSبرای Go یا تعداد Workerها برای پایتون پیش از حذف محدودیتها.
۲. مشاهدهپذیری (Observability):
- ایجاد داشبوردی برای ردیابی نسبت تروتلینگ (
container_cpu_cfs_throttled_periods_total). - افزودن هشدار فشار CPU گره (Node CPU-pressure) برای زمانی که درخواستها بیش از حد پایین هستند یا گره جدیدی نیاز است.
۳. نردههای حفاظتی (Guardrails):
- استقرار
LimitRangeبرای ارائه درخواستهای پیشفرض به پادهایی که از جریانهای GitOps عبور نمیکنند. - استقرار
ResourceQuotaبرای سقفگذاری مجموع درخواستهای Namespace جهت کنترل هزینهها.
۴. اجرا (Execution):
- حذف محدودیتها به ازای هر Namespace، با شروع از محیطهای کماهمیتترین و در نهایت محیط Production.
- افزایش درخواستها در همان تغییر اگر بهوضوح خیلی پایین هستند (مثلاً ۱۰ میلیکور در مقابل ۱۰۰ میلیکور مصرف واقعی).
- بهینهسازی درخواستها بر اساس دادههای P95 سیروزه.
این تغییر، فرض عملیاتی را از «محدودیتها از گره محافظت میکنند» به «درخواستها از همسایه محافظت میکنند» تغییر میدهد. با اعتماد به CFS و زمانبند کوبرنتیز، سازمانها میتوانند به زمانهای استارتآپ سریعتر دست یابند — در برخی تستهای CPU-bound، زمان استارت از ۲۰ ثانیه به ۱۰ ثانیه کاهش یافت (۵۰٪ بهبود) — و تأخیرهای انتهایی بسیار تابآورتری داشته باشند. در یک تست Postgres خودمیزبان، توان عملیاتی از ۱۷۲۰ به ۱۸۹۷ تراکنش در ثانیه (حدود ۱۰٪ افزایش) رسید. این بهبود در لایه دیتابیس یادآور بهینهسازیهای پیشرفته در Postgres است که میتواند منجر به جهشهای چشمگیر در توان عملیاتی سیستم شود.
خلاصه دستاوردهای مورد انتظار
- APIهای پاسخدهنده به درخواست: تأخیر p99 در بار ثابت حدود ۱۴٪ کمتر و در زمان پیک تا ۸۷٪ کمتر میشود.
- مصرفکنندگان صف/رویداد: تأخیر p99 در تستهای Burst تا ۵۹٪ کاهش مییابد که به بازتعادل (Rebalance) گروههای مصرفکننده کمک میکند.
- دیتابیسها: حدود ۱۰٪ افزایش توان عملیاتی در تستهای pgbench تکاجرایی.
- فرانتاندها و CI: بیشترین سود به دلیل کارهای Burst-y مانند Bundling و پردازش تصاویر.
مهندسان پلتفرم اکنون باید خوشههای خود را برای «نسبت تروتلینگ» در حیاتیترین سرویسها بازرسی کنند تا نقاطی را که یخزدگیهای پنهان بر تجربه کاربر تأثیر میگذارد، شناسایی نمایند.




گفتگو