في البنى التحتية الحديثة لمرحلات الشبكات، تمثل الاستجابة اللحظية وخفض زمن الانتقال عنصراً حاسماً يماثل في أهميته سعة النطاق الترددي الصافية. تتعامل بوابات VPN للمؤسسات، وأنفاق WireGuard الاحتياطية المعتمدة على بروتوكول TCP، ونقاط إنهاء اتصالات TLS المشفرة يومياً مع تدفقات بيانات بمليارات البتات في الثانية، مع ضرورة الحفاظ على سرعة تفاعل الحزم الآنية. ولكن مع قفز سرعات الروابط الفيزيائية من 1 جيجابت إلى 100 جيجابت في الثانية، برزت مشكلة هيكلية خفية: ظاهرة تضخم الذاكرة المؤقتة (bufferbloat) في طوابير كتابة البيانات داخل مكدس شبكات نواة لينكس.

عندما يرسل تطبيق بيانات عبر اتصال TCP، تحدد نواة لينكس تلقائياً حجم المخزن المؤقت للإرسال (sk->sk_sndbuf) وفق آليات الموازنة الذاتية. وفي المسارات العابرة للقارات ذات حاصل ضرب النطاق الترددي في زمن التأخير (BDP) المرتفع، ترفع النواة هذا المخزن إلى 4 ميجابايت، أو 8 ميجابايت، أو حتى 16 ميجابايت لكل مقبس. ورغم أن هذه المساحة الرحبة تضمن الاستغلال الكامل لقنوات السرعة الفائقة، فإنها تخلق عنق زجاجة تشغيلياً: تواصل برامج فضاء المستخدم ضخ ميجابايتات البيانات غير المضغوطة إلى طوابير النواة بمعدل يتجاوز كثيراً قدرة كابلات الشبكة على بثها في الحال.

يؤدي هذا التخزين العشوائي إلى تشكل طوابير راكدة صامتة داخل ذاكرة النواة. وإذا تطلب الأمر تمرير حزمة فائقة الأهمية—كنقرة زر على طرفية تحكم عن بُعد، أو مقطع محادثة صوتية، أو حزمة مصافحة أمنية لتدوير مفاتيح التشفير—فإنها تعلق خلف ميجابايتات من ملفات التنزيل الضخمة المتكدسة مسبقاً في الذاكرة. وللقضاء على هذا التأخير المصطنع وإعادة السيطرة الكاملة على تدفق البيانات إلى طبقة التطبيق، وفرت نواة لينكس خيار المقبس المخصص TCP_NOTSENT_LOWAT.


عنق زجاجة طابور الكتابة: تضخم BDP وتراكم البيانات في النواة

يكمن السبب الجوهري لتضخم مخزن الكتابة في الانفصال البنيوي بين حلقات معالجة الأحداث في فضاء المستخدم وآلات حالات TCP داخل النواة. يبادر أي برنامج لا تزامني (يعتمد على epoll أو io_uring) بكتابة البيانات فور إشعار المقبس بوجود مساحة إرسال شاغرة (EPOLLOUT):

  1. تقدير حجم المخزن في النواة: تعتمد النواة المعادلة sk_sndbuf = 2 * BDP، مخصصة ما يصل إلى 16 ميجابايت لضمان عدم اختناق نافذة الازدحام.
  2. الكتابة الاستباقية للتطبيقات: طالما أن حجم البيانات المودعة يقل عن سقف sk_sndbuf، تعلن النواة أن المقبس جاهز للكتابة، فيفرغ التطبيق كافة مخازنه الداخلية في طابور النواة دفعة واحدة.
  3. تراكم الطوابير الراكدة: البيانات التي يتعذر بثها فوراً عبر الكابل (بسبب ضوابط pacing أو حدود نافذة استقبال الطرف الآخر) تتكدس داخل طابور الكتابة في النواة (sk_write_queue).
طابور الكتابة التقليدي في المقابس دون تفعيل TCP_NOTSENT_LOWAT:

[ حلقة أحداث التطبيق ] ──► write(16 ميجابايت)
                                  │
                                  ▼
[ مخزن المقبس في النواة: sk_sndbuf (تم حجز 16 ميجابايت) ]
┌─────────────────────────────────────────────────────────────┬─────────────┐
│ بيانات غير مرسلة راكدة في طابور النواة (15.5 ميجابايت معلقة)│ حزم سارية   │
│ (تسبب انسداداً هائلاً في رأس الطابور للحزم العاجلة)         │ في المسار   │
└─────────────────────────────────────────────────────────────┴─────────────┘
                                  │
                                  ▼  (تدفق بطيء يفرضه معدل الكابل)
                     [ بطاقة الشبكة الفيزيائية ]

يفرض تراكم البيانات الراكدة في النواة خطرين تشغيليين جسيمين:

  • انسداد رأس الطابور (Head-of-Line Blocking): يفقد التطبيق كلياً قدرته على إلغاء، أو إعادة ترتيب، أو إقحام حزم عاجلة لأن البايتات أصبحت محتجزة بالكامل في أروقة النواة.
  • استنزاف هائل للذاكرة العشوائية: لو أدارت عقدة التوزيع 20,000 جلسة نشطة متزامنة، وكل مقبس يحتجز 8 ميجابايت من البيانات الراكدة، تستهلك المنظومة ما يقارب 160 جيجابايت من الذاكرة الحية لمجرد تخزين طوابير خاملة.

الآليات الداخلية في TCP_NOTSENT_LOWAT: منطق النواة وأحداث المقابس

يغير خيار TCP_NOTSENT_LOWAT، الذي ابتكره المطور إريك دومازيه، الشرط المعتمد لجاهزية المقبس للكتابة. فبدلاً من فحص إجمالي الفراغ المتبقي في sk_sndbuf، تراقب النواة بدقة حجم البايتات غير المرسلة (unsent bytes) الكامنة فعلياً في طابور الكتابة.

تحدد النواة البيانات غير المرسلة بالفارق بين إجمالي البايتات المكتوبة والبايتات التي عبرت بطاقة الشبكة بالفعل:

$$\text{البايتات غير المرسلة} = \text{write_seq} - \text{snd_nxt}$$

وعند تعيين TCP_NOTSENT_LOWAT على قيمة محددة (مثل 16 كيلوبايت أو 32 كيلوبايت)، تحجب النواة إشعار الحدث EPOLLOUT طالما ظل رصيد البيانات المعلقة في الطابور أعلى من هذا السقف.

مسار الإرسال المنضبط عند ضبط TCP_NOTSENT_LOWAT = 16 كيلوبايت:

[ مخازن التطبيق ] ── (محفوظة في فضاء المستخدم لتحديد الأولويات)
          │
          │ 1. حلقة الأحداث تنتظر إشعار EPOLLOUT
          ▼
[ طابور المقبس في النواة: sk_write_queue ]
┌──────────────────────────────────────────────┬────────────────────────────┐
│ الرصيد المعلق غير المرسل: مضبوط <= 16 كيلوبايت│ الحزم المنطلقة في المسار   │
│ (حقن تدريجي في النواة عند الحاجة فقط)        │ (استغلال كامل سعة BDP)     │
└──────────────────────────────────────────────┴────────────────────────────┘
          │
          ▼  (انطلاق فوري نحو بطاقة الشبكة)
[ بطاقة الشبكة الفيزيائية ]

1. فصل سعة ذاكرة المقبس عن البيانات غير المرسلة

النقطة الجوهرية هنا هي أن TCP_NOTSENT_LOWAT لا يقلص مساحة sk_sndbuf ولا يعيق نافذة الازدحام (snd_cwnd). يحتفظ مكدس TCP بالقدرة على إطلاق دفقات ضخمة من الحزم لملء المسارات الدولية ذات القيمة العالية لـ BDP.

لكن ما يحدث هو فصل حجم نافذة النقل عن عادة التطبيقات في الشحن المسبق المفرط. تستمر النواة في استقراء التأكيدات وإدارة التدفق، لكن فضاء المستخدم لا يحقن حزماً جديدة في النواة إلا بالتزامن مع مغادرة الحزم السابقة فعلياً نحو الشبكة.

2. إشارات EPOLLOUT الموجهة بالأحداث وتنظيم التدفق

ضمن الكود المصدري لنواة لينكس (net/ipv4/tcp_output.c)، تفحص الدالة الداخلية tcp_write_xmit() عمق الطابور باستمرار:

/* الفحص الداخلي ضمن دالة tcp_stream_memory_free */
static inline bool tcp_stream_memory_free(const struct sock *sk)
{
    const struct tcp_sock *tp = tcp_sk(sk);
    u32 notsent = tp->write_seq - tp->snd_nxt;

    return notsent < tp->notsent_lowat;
}

كلما أطلقت بطاقة الشبكة حزماً جديدة وتقدم مؤشر snd_nxt إلى الأمام، انخفض حجم البايتات العالقة. وحالما ينخفض notsent دون حاجز tp->notsent_lowat، توقظ النواة طابور المقبس المنتظر، مخبرة نداء epoll_wait() بأن المقبس مستعد لاستقبال دفعة جديدة من البيانات.


موازنة زمن الانتقال والذاكرة: الضبط العام عبر Sysctl مقابل تخصيص المقابس

يستطيع مهندسو البنية التحتية فرض هذا القيد شمولياً على كامل الخادم أو ضبطه برمجياً لكل مقبس على حدة.

التسلسل الهيكلي لضبط طوابير الكتابة في بروتوكول TCP:

[ معامل النواة الشامل Sysctl ]
 net.ipv4.tcp_notsent_lowat = 16384 (المعيار الافتراضي للنظام بأكمله)
            │
            ├─► يورث تلقائياً للمقابس العادية
            │
            └─► يعاد ضبطه في التطبيق عبر setsockopt():
                 setsockopt(fd, SOL_TCP, TCP_NOTSENT_LOWAT, &val, sizeof(val))

1. التطبيق الشامل عبر net.ipv4.tcp_notsent_lowat

تعديل معلمات sysctl هو المسار المباشر لحماية الخادم بالكامل من تضخم الذاكرة المؤقتة:

# فحص القيمة الحالية (غالباً ما تكون 0xFFFFFFFF للدلالة على التعطيل)
cat /proc/sys/net/ipv4/tcp_notsent_lowat

# تفعيل حد 16 كيلوبايت شاملاً لكافة اتصالات TCP في الخادم
sudo sysctl -w "net.ipv4.tcp_notsent_lowat = 16384"

يؤدي تثبيت tcp_notsent_lowat = 16384 إلى وضع سقف قدره 16 كيلوبايت للبايتات غير المرسلة لكل مقبس، مما يحول دون استنزاف المهام الخلفية لذاكرة النظام دون ضوابط.

2. التحكم الدقيق في كل مقبس داخل مرحلات التوزيع المشتركة

في خوادم الوساطة التي تجمع أنماطاً متباينة من الحركة، يمنح الضبط البرمجي مرونة متفوقة. يستطيع البرنامج تحديد أسقف ضيقة (مثل 4 أو 8 كيلوبايت) لجلسات التفاعل الفوري، بينما يعتمد 32 أو 64 كيلوبايت لتدفقات تنزيل الوسائط الكبيرة:

int notsent_limit = 16384; /* سقف 16 كيلوبايت للبيانات غير المرسلة */

if (setsockopt(client_fd, SOL_TCP, TCP_NOTSENT_LOWAT, &notsent_limit, sizeof(notsent_limit)) < 0) {
    perror("فشل ضبط خيار TCP_NOTSENT_LOWAT");
    return -1;
}

المخطط التشغيلي: دمج TCP_NOTSENT_LOWAT في خوادم المعالجة الفائقة

تتطلب مواءمة خادم وسيط لا تزامني مع تقنية TCP_NOTSENT_LOWAT انضباطاً متكاملاً بين إدارة الذاكرة وحلقات الأحداث:

  1. إبقاء المخازن في فضاء المستخدم: تجنب الإفراط في الاستدعاء العشوائي لدوال الكتابة. يجب إبقاء البيانات المنتظرة داخل هياكل التطبيق المحلية (كالطوابير الدائرية أو القوائم المقسمة).
  2. الاستجابة الحصرية لإشارة EPOLLOUT: عند ورود الحدث EPOLLOUT، يُكتب جزء محدد بدقة حتى الحد المنطقي التالي ثم يعاد التحكم للحلقة الرئيسية.
  3. مراعاة وضع التحفيز بالحافة (Edge-Triggered): في نمط EPOLLET، تتواصل الكتابة حتى تعيد الدالة الخطأ EAGAIN أو EWOULDBLOCK، بما يؤكد ملامسة المستوى المنخفض المحدد.
مخطط تدفق حلقة أحداث الكتابة لتقليل زمن الانتقال:

[ توفر بيانات جديدة للإرسال ] ──► فحص طابور الأولويات داخل التطبيق
                                                │
                                                ▼
                                   هل المقبس مهيأ للكتابة (EPOLLOUT)؟
                                         /                    \
                                      (نعم)                   (لا)
                                       /                        \
                                      ▼                          ▼
                     كتابة جزء محدد من البيانات       انتظار إشعار الحدث عبر epoll
                                     │
                                     ▼
                          هل أعادت الدالة EAGAIN؟
                               /         \
                            (نعم)        (لا)
                             /             \
                            ▼               ▼
                 إلغاء صفة الجاهزية     مواصلة الكتابة أو العودة للحلقة

فوائد تعدد الإرسال: القضاء على انسداد رأس الطابور

بالنسبة للبروتوكولات التي تجمع عدة مسارات منطقية ضمن اتصال TCP منفرد (مثل HTTP/2، وgRPC، وأنفاق VPN التغليفية)، تحقق ميزة TCP_NOTSENT_LOWAT إنجازاً هيكلياً بارزاً: استئصال انسداد رأس الطابور بين فضاء المستخدم والنواة.

تخيل نفقاً مؤسسياً ينقل ملفات نسخ احتياطي ضخمة بالتزامن مع أوامر جلسة طرفية نصية:

  • في غياب TCP_NOTSENT_LOWAT: تملأ ملفات النسخ الاحتياطي فوراً 8 ميجابايت في مخزن النواة. وعند ضغط مفتاح في الطرفية، تودع حزمته في ذيل طابور الـ 8 ميجابايت، متكبدة 200 مللي ثانية من التأخير غير المبرر.
  • مع تطبيق TCP_NOTSENT_LOWAT: لا تحتفظ النواة بأكثر من 16 كيلوبايت من البيانات في أي لحظة. وعند ظهور حزمة أمر الطرفية، يقدمها جدول التطبيق فوراً إلى رأس طابور فضاء المستخدم. وخلال ميكروثوانٍ معدودة، وبمجرد إفراغ الـ 16 كيلوبايت الجارية، تنطلق الحزمة العاجلة في المسار دون أي تأخير.

معمارية RiceGuard: تحكم تكيفي في الذاكرة المؤقتة لمرحلات السرعة العالية

أدرجت منصة VPNRice ميزة TCP_NOTSENT_LOWAT في صميم محرك توزيع البيانات التابع لبروتوكول RiceGuard. بالنسبة للأجهزة المحمولة المتنقلة عبر شبكات متعددة، ومرحلات الأعمال، ومحطات الوسائط الحية العاملة فوق أنفاق TCP، فإن التخلص من تضخم الذاكرة المؤقتة هو الضمان الأساسي لسلاسة الاتصال.

يحلل مراقب RiceGuard معطيات الاتصال دورياً—بما يشمل تقديرات زمن RTO الفعلي، ومعدلات التدفق، واحتياجات المشترك. وتعدل المنظومة سقف TCP_NOTSENT_LOWAT تلقائياً بين 8 و32 كيلوبايت، مما يضمن تشغيل الخطوط بأقصى طاقتها مع الإبقاء على تأخير الطوابير في حدود أجزاء من الملي ثانية. ومن خلال المزج المتناغم بين خيارات نواة لينكس المتقدمة والخوارزميات البرمجية الذكية، يوفر RiceGuard استجابة فائقة، وثباتاً راسخاً، وحماية أكيدة للخصوصية الرقمية للمستخدمين في شتى بقاع العالم.