الكبح الإجباري للمعالج (CPU Throttling): ليه تطبيقك بطيء رغم إن العقدة فاضية
مستوى المقال: محترف. ده شرح لمشكلة إنتاج بتضرب فرق كتير على Kubernetes، وبيفترض إنك متعوّد على البودات والحدود (requests / limits) وتقدر توصل للـ shell جوّه الحاوية.
لو الـ p99 latency بتاع خدمتك بيقفز لثانيتين، وانت شايف إن استهلاك الـ CPU على العقدة تحت 40%، ركّز: غالبًا مش محتاج عقدة أقوى ولا ريبليكات زيادة. اللي بيحصل فعلاً إن الـ kernel بيجمّد بودك بسبب حاجة اسمها CFS quota. المقال ده هيوريك تكتشفها بأمر واحد وتصلّحها.
المشكلة باختصار
بتحطّ limits.cpu على البود عشان تحمي العقدة. المفاجأة إن الحد ده مش سقف بيشتغل "لو زاد الاستهلاك"، ده سقف بيتحاسب كل 100 مللي ثانية على حدة. لو تطبيقك خلّص حصته بدري في الفترة، الـ kernel بيوقفه تمامًا لحد ما الفترة اللي بعدها تبدأ. النتيجة: latency عالي ومتوسط CPU منخفض في نفس الوقت، وده بالظبط اللي بيخلّي الناس تشك في السيرفر بدل ما تشك في الحد اللي هي حاطّاه بإيدها.
المفهوم: CFS quota بمثال بسيط ثم علميًا
تخيّل خط موبايل بباقة "40 دقيقة كل ساعة". لو اتكلمت الـ 40 دقيقة كلها في أول 15 دقيقة، الخط بيتقفل فورًا وتفضل مستني 45 دقيقة لحد ما الساعة الجديدة تبدأ. انت استهلكت 40 دقيقة بس في ساعة كاملة، فالمتوسط بتاعك يبان قليل، لكنك عمليًا كنت "متقفول" معظم الوقت. ده بالظبط اللي بيعمله الـ CPU throttling مع بودك.
علميًا: الـ CFS (Completely Fair Scheduler) في لينكس بيطبّق حد الـ CPU عن طريق قيمتين في الـ cgroup: cpu.cfs_period_us (افتراضيًا 100000، يعني 100 مللي ثانية) و cpu.cfs_quota_us. لمّا تكتب limits.cpu: "1"، ده بيتحوّل لـ quota = 100ms لكل فترة 100ms. و limits.cpu: "500m" يعني quota = 50ms. النقطة المهمة إن الحصّة دي بتتقسم على كل خيوط التطبيق مجمّعة. لو عندك 8 خيوط شغّالة بالتوازي وحدّك "1"، الـ 8 خيوط بيستهلكوا الـ 100ms في حوالي 12ms من الزمن الحقيقي، وبعدها البود يتجمّد 88ms. ده اللي بيفجّر الـ tail latency.
إزاي تتأكد إن ده اللي بيحصل فعلاً
متخمّنش. الرقم اللي بيحسم الموضوع موجود جوّه الحاوية في cpu.stat.
# cgroup v2 (الأحدث)
kubectl exec -it <pod> -- cat /sys/fs/cgroup/cpu.stat
# بتطلّع: nr_periods, nr_throttled, throttled_usec
# cgroup v1
kubectl exec -it <pod> -- cat /sys/fs/cgroup/cpu,cpuacct/cpu.stat
# بتطلّع: nr_periods, nr_throttled, throttled_time