در معماری‌های توزیع‌شده، تبادلات میان میکروسرویس‌ها و تونل‌های رمزنگاری‌شده VPN، مهندسان شبکه مکرراً با یک پدیده تأخیر شگفت‌انگیز روبه‌رو می‌شوند: برخی فراخوانی‌های رویه راه دور (RPC) یا تایپ در نشست‌های تعاملی خط فرمان، دقیقاً به مدت ۴۰ میلی‌ثانیه (یا ۲۰۰ میلی‌ثانیه در برخی سیستم‌ها) دچار مکث می‌شوند؛ آن هم در حالی که پهنای باند شبکه آزاد است و پینگ فیبر نوری زیر یک میلی‌ثانیه ثبت می‌شود. این تأخیر تصنعی ناشی از ترافیک سنگین یا صف‌های سخت‌افزاری نیست، بلکه حاصل برخورد ناخواسته دو روش بهینه‌سازی مستقل است که در دهه‌های آغازین اینترنت شکل گرفته‌اند.

این دو مکانیزم—الگوریتم نِاگل (Nagle) که در سال ۱۹۸۴ تحت استاندارد RFC 896 تدوین شد و تأییدیه با تأخیر (TCP Delayed ACK) که در سال ۱۹۸۹ در RFC 1122 رسمیت یافت—هر دو با هدفی هوشمندانه برای کاهش مصرف پهنای باند ابداع شدند. با این حال، زمانی که یک برنامه چندین قطعه داده کوچک را پشت سر هم در یک جریان ارسال می‌کند، فرستنده و گیرنده در یک بن‌بست دوسویه گرفتار می‌شوند. شناخت سرچشمه این توقف ۴۰ میلی‌ثانیه‌ای در پشته شبکه لینوکس و تسلط بر گزینه‌های کنترلی مانند TCP_NODELAY و TCP_QUICKACK برای حذف تکانه‌های تأخیر در تونل‌های شبکه امری حیاتی است.

برخورد دو رویکرد بهینه‌سازی در پشته انتقال

برای درک منشأ توقف ۴۰ میلی‌ثانیه‌ای، باید هدف طراحی هر یک از این دو الگوریتم را جداگانه بررسی کنیم.

الگوریتم ناگل (RFC 896): مهار ریزبسته‌ها (Tinygrams)

در دوران اولیه آرپانت، برنامه‌های متنی تعاملی مانند تل‌نت بسته‌های کوچکی تولید می‌کردند که تنها شامل ۱ بایت داده واقعی بود اما ۴۰ بایت سربرگ پروتکل (۲۰ بایت IPv4 به همراه ۲۰ بایت TCP) را به دنبال داشت. این ریزبسته‌ها سوئیچ‌های شبکه را با بار پردازشی اضافی اشباع می‌کردند.

جان ناگل برای رفع این مشکل قاعده‌ای در سمت فرستنده وضع کرد:

  1. اگر برنامه داده کافی برای تشکیل یک قطعه استاندارد کامل با اندازه MSS (معمولاً ۱۴۶۰ بایت در اترنت) مهیا کند، بسته بلافاصله ارسال می‌شود.
  2. اگر حجم داده کمتر از MSS باشد، سیستم بررسی می‌کند که آیا در حال حاضر بسته‌ای در شبکه در حال حرکت است که هنوز تأییدیه ACK دریافت نکرده باشد (snd_una < snd_nxt).
  3. در صورت وجود داده‌های تأییدنشده در جریان، داده‌های کوچک در بافر محلی متوقف می‌شوند تا داده‌های جدید به حجم یک MSS برسد یا بسته تأییدیه ACK از گیرنده برای تمام داده‌های قبلی واصل گردد.

تأییدیه با تأخیر Delayed ACK (RFC 1122): کاهش تراکم پیام‌های پاسخ

پنج سال بعد، استاندارد RFC 1122 قابلیت تأییدیه با تأخیر را معرفی کرد تا بار پیام‌های تأیید خالص ارسالی از سمت گیرنده کاهش یابد. گیرنده به جای ارسال فوری یک سربرگ ۴۰ بایتی برای هر بسته دریافتی، پاسخ را تحت دو شرط به تعویق می‌اندازد:

  1. ارسال همراه با داده (Piggybacking): گیرنده یک زمان‌سنج کوتاه (معمولاً ۴۰ میلی‌ثانیه در لینوکس و تا ۲۰۰ میلی‌ثانیه در ویندوز) به راه می‌اندازد به این امید که برنامه داخلی به زودی پاسخی ارسال کند و بتوان نشانه ACK را روی بسته داده برگشتی سوار کرد.
  2. قاعده هر دو بسته یک پاسخ: اگر در حین شمارش زمان‌سنج، بسته کامل دومی دریافت شود، تأییدیه بلافاصله ارسال می‌گردد.

سناریوی قفل متقابل ۴۰ میلی‌ثانیه‌ای

هنگامی که یک نرم‌افزار دو داده کوچک متوالی می‌نویسد—مانند درخواست‌های HTTP/1.1 که سربرگ و بدنه در دو دستور جداگانه ارسال می‌شوند، یا فریم‌های gRPC—دو الگوریتم در یک چرخه انتظار متوقف می‌شوند:

فرستنده (الگوریتم ناگل فعال)                  گیرنده (تأخیر ACK فعال)
      │                                                     │
      │ ─── نوشتن ۱ (سربرگ: ۳۰۰ بایت) ────────────────────► │ (دریافت ۳۰۰ بایت)
      │     [ارسال فوری: کانال در ابتدا خالی بود]           │ شروع زمان‌سنج تأخیر ۴۰ میلی‌ثانیه
      │                                                     │
[نوشتن ۲: بدنه ۵۰۰ بایت]                                    │
داده < MSS و بسته در جریان تأییدنشده                        │
      │                                                     │
      ▼                                                     ▼
(بافر شدن نوشتن ۲ در فرستنده)                         (گیرنده منتظر داده برگشتی
 منتظر دریافت ACK از گیرنده                           یا دریافت بسته دوم می‌ماند)
      │                                                     │
      │                 ════ بن‌بست ۴۰ میلی‌ثانیه‌ای ════     │
      │                                                     │
      │                                              پایان ۴۰ میلی‌ثانیه زمان‌سنج!
      │ ◄── بسته تأییدیه خالص (۴۰ بایت) ──────────────────── │ ارسال تأییدیه تأخیریافته
      │                                                     │
      ▼                                                     │
[دریافت ACK و باز شدن صف]                                   │
فرستنده بسته ۲ (۵۰۰ بایت) را ارسال می‌کند ─────────────────► │

طی این بازه ۴۰ میلی‌ثانیه‌ای، فرستنده ارسال نمی‌کند چون منتظر تأییدیه است، و گیرنده تأییدیه نمی‌فرستد چون منتظر داده‌های بیشتر است. در نتیجه تبادل ترافیک با افت روبه‌رو می‌شود.

سازوکار هسته لینوکس و مثلث گزینه‌های سوکت

پشته شبکه لینوکس سه گزینه مشخص را در اختیار مهندسان قرار می‌دهد تا بر رفتار الحاق و ارسال داده‌ها تسلط یابند.

۱. TCP_NODELAY: غیرفعال کردن الگوریتم ناگل

راهکار مرسوم در سمت کلاینت فعال‌سازی TCP_NODELAY است که به هسته دستور می‌دهد بررسی وضعیت بسته‌های در جریان را نادیده گرفته و داده‌ها را درجا گسیل کند:

int one = 1;
setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));

اگرچه TCP_NODELAY در سامانه‌های تحت کنترل شما به خوبی عمل می‌کند، اما اگر سمت سرور باشید و نتوانید برنامه‌های سمت کاربر را بازنویسی کنید، این گزینه کارایی نخواهد داشت.

۲. TCP_CORK: انباشت اجباری ترافیک

در نقطه مقابل، TCP_CORK لینوکس را وادار می‌سازد تا تمام اطلاعات خروجی را مسدود نگه دارد تا زمانی که داده‌ها به سقف MSS برسند یا برنامه قفل را بردارد. این حالت برای سرورهای دانلود پرونده مفید است اما برنامه‌های تعاملی را دچار وقفه می‌سازد.

۳. TCP_QUICKACK: وادار کردن گیرنده به تأیید آنی

برای برطرف ساختن وقفه ۴۰ میلی‌ثانیه‌ای از سمت میزبان گیرنده، هسته لینوکس گزینه TCP_QUICKACK را فراهم آورده است. با فعال‌سازی این مورد، هسته زمان‌سنج تأخیر را نادیده گرفته و به بسته‌های ورودی فوراً پاسخ تأیید می‌دهد:

#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>

int faal_sazi_quickack(int sockfd)
{
    int one = 1;
    return setsockopt(sockfd, IPPROTO_TCP, TCP_QUICKACK, &one, sizeof(one));
}

نکته حساس معماری: ویژگی QuickACK دائمی نیست

یکی از جزئیات فنی مهم این است که TCP_QUICKACK وضعیت پایدار سوکت نیست. در هسته لینوکس، فعال‌سازی این گزینه اعتباری عددی (inet_csk(sk)->icsk_ack.quick) به سوکت اختصاص می‌دهد:

/* کدهای مبدأ هسته لینوکس net/ipv4/tcp_input.c */
void tcp_enter_quickack_mode(struct sock *sk, unsigned int max_quickacks)
{
    struct inet_connection_sock *icsk = inet_csk(sk);
    icsk->icsk_ack.quick = max_quickacks;
    icsk->icsk_ack.pingpong = 0;
}

با ادامه ارتباط، هسته لینوکس مشخصات جریان را می‌سنجد. اگر مشخص شود تعامل به صورت رفت و برگشتی تعاملی است، مقدار icsk_ack.quick به صفر کاهش یافته و سوکت خودکار به شیوه با تأخیر بازمی‌گردد.

به همین دلیل، سامانه‌های پروکسی پیشرفته یا باید پس از هر عملیات خواندن داده TCP_QUICKACK را مجدداً اعمال نمایند یا این سازوکار را در جداول مسیریابی سراسری تعریف کنند.

بازتاب این پدیده در تونل‌های رمزگذاری‌شده VPN و رابط TUN

در شبکه‌های خصوصی مجازی، اثرات تأخیر به دلیل قرارگیری لایه‌های متعدد کپسوله‌سازی تشدید می‌گردد. هنگامی که ترافیک برنامه از رابط مجازی (tun0 یا wg0) عبور می‌کند، مراحل زیر طی می‌شود:

[ جریان تعاملی TCP برنامه ] ──── محموله درونی (مستعد توقف ناگل و تأخیر ACK)
             │
             ▼
[ رابط مجازی شبکه: tun0 ] ────── پردازش فریم‌ها و تنظیم MTU
             │
             ▼
[ حامل رمزنگاری بیرونی ] ─────── WireGuard UDP / IPsec ESP / DTLS

چندبرابر شدن تأخیر در گذر از تونل

وقتی جریان برنامه در داخل تونل VPN به وقفه ۴۰ میلی‌ثانیه‌ای برخورد کند، عواقب آن در کل مسیر بازتاب می‌یابد:

  1. انباشت داده‌های تعاملی: دستورات پایگاه داده و تعاملات SSH پشت سد عدم دریافت تأیید متوقف می‌شوند.
  2. محاسبه اشتباه ازدحام: الگوریتم‌های تنظیم ترافیک این درنگ را نشانه ازدحام واقعی تلقی کرده و پهنای تبادل را کاهش می‌دهند.
  3. تأخیر مضاعف: در ساختارهای قدیمی VPN بر پایه TCP، امکان دارد لایه بیرونی و درونی هم‌زمان زمان‌سنج تأخیر را روشن کرده و وقفه‌هایی بین ۸۰ تا ۲۴۰ میلی‌ثانیه بیافرینند.

رویکرد مهندسی RiceGuard در خنثی‌سازی افت سرعت

معماری پیشرفته RiceGuard با طراحی همگام لایه‌ها از بروز این دست‌اندازهای ارتباطی جلوگیری می‌کند:

  • ارسال از بستر UDP: پروتکل RiceGuard برای لایه بیرونی از UDP بهره می‌گیرد تا هیچ تأخیری در لایه تونل رخ ندهد.
  • تزریق پویای QuickACK: در گره‌های پروکسی خروجی، فیلترهای هدایت مسیر اعتبار تأیید فوری بسته‌ها را پیوسته تمدید می‌کنند.
  • بهینه‌سازی لایه سوم Core L3 VPN: با تنظیم دقیق ابعاد واحدهای انتقال و حذف ادغام‌های بی‌مورد، جریان‌های SSH و بازی‌های تحت وب بدون کوچک‌ترین وقفه از میان تونل عبور داده می‌شوند.

پایش کارگاهی و پیکربندی در سطح مسیرهای لینوکس

مدیران شبکه می‌توانند به سادگی اثرات تأخیر ACK را در محیط لینوکس رصد و رفع نمایند.

ثبت اثر ۴۰ میلی‌ثانیه‌ای در بسته‌های شبکه

در بررسی فایل‌های ضبط‌شده با tcpdump یا Wireshark، الگوی این وقفه کاملاً برجسته است:

شماره   فاصله زمانی     مبدأ          مقصد         توضیحات
1      0.000000        کلاینت        سرور         PSH, ACK طول=۳۰۰ (بسته سربرگ)
2      0.040182        سرور          کلاینت       ACK (انقضای زمان‌سنج تأخیر ACK)
3      0.040215        کلاینت        سرور         PSH, ACK طول=۵۰۰ (بسته بدنه)
4      0.040310        سرور          کلاینت       ACK

اختلاف حدود ۴۰٫۱ میلی‌ثانیه میان دو بسته نخست، گواه روشن تأخیر عمدی گیرنده در زمان کارکرد الگوریتم ناگل است.

ارزیابی سوکت با دستور ss

ابزار ss پارامترهای داخلی سوکت را از عمق هسته نمایان می‌سازد:

# بررسی اطلاعات زمانی سوکت روی درگاه 443
ss -tin '( sport = :443 or dport = :443 )'

نشانه‌های مهم در خروجی:

  • ato:<ms>: مقدار زمان‌سنج تأخیر ACK (معمولاً در لینوکس روی ۴۰ میلی‌ثانیه).
  • quickack: حضور این شناسه نشان‌دهنده فعالیت در حالت پاسخ‌دهی آنی است.
  • delack: نشان می‌دهد زمان‌سنج تأخیر پاسخ در حال حاضر فعال است.

اعمال دائمی QuickACK در جدول مسیرها

برای رهایی از دستکاری کدهای برنامه، می‌توان با استفاده از دستور ip route پاسخ‌دهی بدون درنگ را روی یک درگاه فعال کرد:

# اعمال پاسخ‌دهی فوری به تمام ترافیک ورودی تونل VPN
sudo ip route change 10.8.0.0/24 dev wg0 proto kernel scope link src 10.8.0.1 quickack 1

با اضافه کردن quickack 1 به مدخل مسیریابی، هسته لینوکس سازوکار تأخیر ACK را برای تمامی بسته‌های ورودی این رابط به طور کامل متوقف می‌سازد.

فرجام: گامی مهم در تضمین کمترین زمان تأخیر

تلاقی الگوریتم ناگل و سازوکار تأخیر ACK گواهی است بر اینکه چگونه ترکیب دو راه‌حل بهینه‌سازی جداگانه ممکن است کارایی شبکه را کاهش دهد. با این که الگوریتم ناگل مانع از هدررفت پهنای باند می‌شود، مکانیزم تأخیر ACK وقفه‌های نامطلوبی ایجاد می‌کند.

با هماهنگی TCP_NODELAY در سمت فرستنده، به‌کارگیری TCP_QUICKACK در سمت گیرنده و بهره‌گیری از زیرساخت‌های نوین مانند RiceGuard بر بستر UDP، می‌توان این مانع ۴۰ میلی‌ثانیه‌ای را از میان برداشت و شبکه‌ای پویا و باثبات را تجربه کرد.