2 पॉइंट द्वारा GN⁺ 2024-05-10 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • distributed systems में latency की समस्या अक्सर सिर्फ TCP_NODELAY enable करने से हल हो जाती है, और TCP का default व्यवहार आधुनिक workloads से मेल नहीं खा सकता
  • Nagle algorithm 1984 के RFC896 में छोटे TCP packets की header cost घटाने के लिए बनाया गया था, और इसका तरीका ACK मिलने से पहले नया segment भेजने से रोकना है
  • delayed ACK के साथ इस्तेमाल होने पर एक पक्ष ACK का इंतज़ार करता है और दूसरा पक्ष response data या timer का, जिससे latency-sensitive pipelined applications को नुकसान होता है
  • data center के अंदर RTT करीब 500μs ही क्यों न हो, आधुनिक servers उस समय में काफी काम कर सकते हैं, इसलिए एक RTT जितना transmission delay करने का लाभ स्पष्ट नहीं है
  • आधुनिक distributed systems में TLS, encoding, serialization और application message size की वजह से single-byte packet की समस्या कम हो गई है, और latency-sensitive environments में Nagle algorithm को disable करना ज़्यादा स्वाभाविक है

latency debugging में सबसे पहले देखा जाने वाला setting

  • distributed systems में latency की समस्या आने पर सबसे पहले यह जांचा जाता है कि TCP_NODELAY enable है या नहीं
  • कई distributed systems developers ने ऐसे issues पर समय गंवाया है जो इस एक सरल socket option से हल हो जाते हैं
  • यह दोहराव दिखाता है कि TCP का default व्यवहार आज के distributed systems के अनुकूल नहीं है, या Nagle algorithm खुद पुराना पड़ चुका हो सकता है

Nagle algorithm जिस समस्या को हल करना चाहता था

  • RFC896 1984 में छोटे packets की समस्या पर लिखा गया document है
  • उस समय keyboard input जैसे एक-एक character में आने वाले data को TCP से भेजते समय 1 byte data के हर हिस्से पर 40 byte header जुड़ने की अक्षमता पैदा होती थी
    • 1 byte उपयोगी data पर 40 byte header जुड़ता था, जिससे 4000% overhead होता था
    • हल्के load में यह सहन किया जा सकता था, लेकिन network throughput के लिए यह नुकसानदेह था
  • Nagle algorithm का उद्देश्य TCP header cost को बेहतर तरीके से amortize कर throughput बढ़ाना था
    • छोटे packets मुख्यतः shell जैसी human-interaction applications में, या कई write calls के जरिए थोड़ा-थोड़ा data kernel को देने वाली implementations में आते थे
  • इसका मुख्य व्यवहार यह है कि जब पहले भेजे गए data को अभी ACK नहीं मिला हो, तो नए outgoing data को तुरंत अलग TCP segment के रूप में न भेजा जाए
  • Nagle algorithm को अक्सर timer के साथ समझाया जाता है, लेकिन RFC896 खुद network round-trip time (RTT) के अलावा कोई अलग timer इस्तेमाल नहीं करता

delayed ACK के साथ जुड़ने पर होने वाली latency

  • delayed ACK में packet receipt acknowledgement तुरंत नहीं भेजा जाता; वह तब तक इंतज़ार करता है जब तक वापस भेजने के लिए data न बन जाए या timer expire न हो जाए
  • RFC813 1982 का शुरुआती document है जिसने ACK delay का प्रस्ताव रखा था, और बताता है कि कुछ स्थितियों में receiver ACK भेजना टाल सकता है और बाद में भेजने के लिए timer set कर सकता है
  • RFC1122 delayed ACK को और अधिक औपचारिक बनाता है
  • दोनों features अपने-अपने स्तर पर उचित हैं, लेकिन साथ इस्तेमाल होने पर latency पैदा कर सकते हैं
    • Nagle algorithm अधिक data भेजने से पहले ACK receive होने का इंतज़ार करता है
    • delayed ACK response data तैयार होने या timer expire होने तक ACK transmission टालता है
    • यह packets को भरने में मदद करता है, लेकिन latency-sensitive pipelined applications के लिए अच्छा नहीं है
  • John Nagle की Hacker News comment भी समस्या को tinygram रोकथाम नहीं, बल्कि ACK delay और fixed timer के संयोजन के रूप में देखती है
  • यह दो तार्किक protocol features के मिलकर अनचाहा व्यवहार बनाने का उदाहरण है, और ऐसे interactions protocol design को कठिन बनाते हैं

आधुनिक distributed systems से मेल न खाने वाली बातें

  • delayed ACK न भी हो, तो भी Nagle algorithm का व्यवहार आधुनिक distributed systems की अपेक्षित शैली से अलग हो सकता है
  • आज के environment में RTT खुद एक ऐसा cost है जिसे नज़रअंदाज़ करना मुश्किल है
    • data center के अंदर single RTT आमतौर पर करीब 500μs होता है
    • उसी region के data centers के बीच RTT कुछ ms होता है
    • दुनिया भर के paths में यह सैकड़ों ms तक जा सकता है
  • आधुनिक servers सैकड़ों μs में भी बहुत काम कर सकते हैं, इसलिए data को एक RTT जितना देर से भेजना स्पष्ट लाभ वाला विकल्प नहीं लगता
  • Nagle algorithm का मूल औचित्य single-byte packets में होने वाले 40x header overhead को घटाना था
  • आधुनिक distributed databases और distributed systems आम तौर पर single-byte packets नहीं भेजते
    • application द्वारा भेजा जाने वाला data खुद बड़ा होता है
    • TLS जैसे protocol overhead जुड़ते हैं
    • encoding और serialization overhead भी जुड़ता है
  • छोटे messages से बचना अभी भी महत्वपूर्ण मुद्दा है, लेकिन इसकी जिम्मेदारी प्रभावी रूप से application layer पर चली गई है
  • JSON में wrapped data को एक-एक byte करके भेजना Nagle algorithm से अलग भी efficient नहीं है

TCP_NODELAY को default choice मानने की वजह

  • अगर आप आधुनिक data center-grade hardware पर latency-sensitive distributed system बना रहे हैं, तो TCP_NODELAY enable करके Nagle algorithm को disable कर सकते हैं
  • आधुनिक systems के traffic, application structure और hardware performance को देखते हुए Nagle algorithm अब जरूरी नहीं रह गया हो सकता है
  • यह रुख भी संभव है कि TCP_NODELAY default value होनी चाहिए
  • हर byte पर write call करने वाला code TCP_NODELAY default होने पर धीमा हो सकता है
  • अगर efficiency महत्वपूर्ण है, तो ऐसे code को Nagle algorithm पर निर्भर रहने के बजाय application implementation ठीक करनी चाहिए

TCP_QUICKACK अधिकतर एक सहायक विकल्प है

  • TCP_QUICKACK को विकल्प के रूप में उठाया जा सकता है, लेकिन portability की कमी और असामान्य semantics की वजह से इसे पहली choice बनाना मुश्किल है
  • Linux tcp man page का अर्थ सीधे जांचना जरूरी है
  • बड़ी समस्या यह है कि TCP_QUICKACK उस मूल समस्या को हल नहीं करता कि kernel program की मंशा से अधिक देर तक data रोककर रखता है
  • अगर program ने write() call किया है, तो वह उम्मीद करता है कि सच में write() execute हो

1 टिप्पणियां

 
GN⁺ 2024-05-10
Hacker News टिप्पणियाँ
  • अपने करियर के दौरान मैंने Nagle algorithm की वजह से होने वाली latency समस्याएँ कई बार ठीक की हैं, और अब यह उन चीज़ों में है जिन पर सबसे पहले शक करता हूँ
    इसका तर्क अपने आप में सही है, लेकिन कुछ workloads के लिए उपयुक्त नहीं है, और मेरा मानना है कि socket बनाते समय engineers को इसे स्पष्ट रूप से चुनना चाहिए, न कि इसे operating system के default पर छोड़ देना चाहिए
    समस्या यह नहीं है कि यह अच्छा option है या बुरा, बल्कि यह है कि data transfer के तरीके को काफ़ी आक्रामक रूप से बदल देने वाली setting मौजूद है और बहुत से लोगों को उसके अस्तित्व का ही पता नहीं है

    • मेरा भी कुछ ऐसा ही हाल है; जब भी कोई नया RPC framework देखता हूँ, तो GitHub issue में यह पूछने का शौक़ है: “क्या आपने TCP_NODELAY पर विचार किया है, या यह framework सिर्फ़ 20 calls per second ही कर सकता है?”
      अब तक हर बार कोई न कोई bug मिला है
      उदाहरण: https://cloud-haskell.atlassian.net/browse/DP-108 या https://github.com/agentm/curryer/issues/3
      हालाँकि, “यह अच्छा/बुरा option नहीं है” वाली बात से मैं सहमत नहीं हूँ
      यह गलत तरीके से लिखे गए applications को “जादुई तरीके से ठीक” करने के लिए kernel-side heuristic है, और जैसा लेख में कहा गया है, सामान्य applications 1-byte network write() system call नहीं करते
      ऐसे software को ठीक किया जाना चाहिए
      मुझे यह feature सिर्फ़ उस दुर्लभ स्थिति में समझ में आता है जब आप kernel system administrator हों और team politics जैसी वजहों से machine पर चल रहे software को बदल न सकें
      इसके अलावा यह सामान्य software को और जटिल बनाता है
      यानी खराब लिखे software का throughput थोड़ा बढ़ाने के लिए डाली गई अजीब magic को आपको स्पष्ट रूप से बंद करना पड़ता है, और सही तरह से लिखे software के लिए यह बड़ी और चौंकाने वाली latency पैदा करता है
      John Nagle ने यहाँ linked thread में कहा है कि delayed ACK इससे भी बदतर है, और मैं इससे सहमत हूँ
      लेकिन Send/Send/Receive pattern, जिसे Nagle algorithm और बदतर बना देता है, पूरी तरह वैध और आम use case है, और यह TCP के ऊपर pipelined RPC करने वाली हर चीज़ पर लागू होता है
      मेरा मानना है कि delayed ACK और Nagle algorithm दोनों default रूप से बंद होने चाहिए
      इसका नाम भी शायद TCP_DELAY जैसा होना चाहिए, और इसे सिर्फ़ तब चालू करना चाहिए जब आप basic user-space buffering खुद implement नहीं करना चाहते
      लोगों को ऐसी चीज़ें जानने की ज़रूरत ही नहीं होनी चाहिए, और default behavior ऐसा होना चाहिए जो चौंकाने वाला न लगे
    • अगर इसका मुख्य उद्देश्य खराब write behavior वाले applications को ठीक करना है, तो TCP_DELAY को चालू करने वाला option काफ़ी विचित्र हो जाता है
      इसका मतलब है कि आपको ऐसा software engineer चाहिए जो इस option के बारे में जानने लायक़ तो होशियार हो, लेकिन write calls को सही तरह बाँटने या अपनी application के लिए बेहतर Nagle-जैसी buffering खुद बनाने लायक़ होशियार न हो
    • सहमत। high-frequency/low-latency trading की दुनिया में Nagle algorithm को बंद करना काफ़ी समय से, शायद 15 साल से भी ज़्यादा, अच्छी तरह जाना-पहचाना अभ्यास रहा है, और यह उन चीज़ों में से है जिन्हें मैं सबसे पहले जाँचता हूँ
    • असल में जो चाहिए वह latency को n microseconds पर रखना है, लेकिन system call से पहले सीधे user-space buffering जोड़ने के अलावा इसका कोई अच्छा तरीका नहीं है
      io_uring जैसी कोई चीज़ जो system call की लागत को offset कर दे, उसके बिना user space वाला तरीका बेहतर काम करता है
    • यह तर्क मूल रूप से Telnet session जैसी चीज़ों के लिए बनाया गया था
      जहाँ तक मुझे याद है, वही इसकी मुख्य प्रेरणा थी
  • निष्कर्ष थोड़ा अजीब है। Nagle algorithm साफ़ तौर पर batching write की कोशिश थी, और hardware, network, application, या use case चाहे जो हो, कुछ स्थितियों में batching बेहतर होती है
    आज भी बहुत-सी computing batching का उपयोग करती है, और network applications भी इससे लाभ उठाते हैं
    QUIC जैसे नए higher-level protocols writes को batch करते हैं, और TCP की independent connection तथा error handling को लगभग user space में ले जाते हैं, ताकि protocol data को application तक जितनी जल्दी हो सके पहुँचा सके, जबकि individual streams की connection और error handling host TCP/IP stack या routers के बजाय application संभाले
    अगर पहले की तरह network फिर से saturated होने लगें, तो Nagle algorithm QUIC के किसी संशोधित रूप में वापस आएगा, शायद application code के और अंदर, जहाँ कुछ मानदंड पूरे होने तक QUIC packets भेजने का इंतज़ार किया जाएगा
    तकनीक में हर चीज़ तब फिर से आविष्कृत होती है जब hardware या software bottleneck तक पहुँच जाता है। दोनों का performance एक ही गति से नहीं बढ़ता, इसलिए अंत में हमेशा ऐसा ही होता है
    bandwidth के अलावा, जब छोटे packets की वजह से packets per second saturate हो जाते हैं, तब भी Nagle algorithm उपयोगी होता है

    • QUIC और TCP के बीच का अंतर TCP और उसके पूर्वजों के मूल पाप में है: यानी message layer के बिना दिखने वाला asynchronous serial port connection की नकल करना
      इसकी वजह से भौतिक teletypewriter से services से जुड़ना संभव था, लेकिन TCP message boundaries नहीं जानता, और आज भले हम उस जानकारी का कुछ हिस्सा वापस डाल सकें, शुरुआती software ऐसा नहीं कर सकता था
      इसके विपरीत QUIC, SCTP, TP4 जैसे कई non-TCP protocols message boundaries को स्पष्ट रूप से उपलब्ध कराते हैं
      system के साथ interface emulated serial port नहीं, बल्कि message-based होता है, जिसे ज़्यादा से ज़्यादा reassembly की ज़रूरत पड़ती है
    • सही है, लेकिन यह खास implementation batching का तरीका तय करने के लिए heuristic पर निर्भर थी, और लगता है कि उसकी assumptions सही नहीं बैठीं
    • batching को protocol नहीं, application को control करना चाहिए
      protocol के पास सही batching करने लायक़ context नहीं होता
  • इसके उलट delayed ACK को बंद करना कैसा रहेगा
    समस्या तब पैदा होती है जब small packet avoidance और delayed ACK एक-दूसरे के साथ इंटरैक्ट करते हैं और pathological behavior बनता है
    small packet avoidance को बंद करने का exposed option TCP_NODELAY है, लेकिन delayed ACK को कैसे बंद किया जाए
    मतलब जब आप चारों combinations को benchmark करके देखना चाहते हों कि सबसे अच्छा क्या बैठता है
    थोड़ा खोजने पर पता चला कि Linux में TCP_QUICKACK socket option है, लेकिन उसे हर बार receive करते समय सेट करना पड़ता है
    /proc/sys/net/ipv4/tcp_delack_min और /proc/sys/net/ipv4/tcp_ato_min भी हैं
    FreeBSD में net.inet.tcp.delayed_ack और net.inet.tcp.delacktime हैं

    • TCP_QUICKACK सबसे खराब रूप को तो ठीक करता है, लेकिन पूरी समस्या हल नहीं करता
      Nagle algorithm अब भी data भेजने से पहले अधिकतम एक round-trip time तक इंतज़ार कर सकता है, और RFC के अनुसार देखें तो इससे लगभग कोई लाभ नहीं मिलता, सिर्फ latency बढ़ती है
    • सही कहा। TCP_QUICKACK को हर receive पर सेट करना पड़े, यह किस सोच के साथ बनाया गया होगा
      कोई इसे सिर्फ कुछ समय के लिए ही क्यों बंद रखना चाहेगा
    • CentOS/RedHat में route के अंत में quickack 1 जोड़कर उस path के लिए delayed ACK बंद किया जा सकता है
  • जिस दुनिया में bandwidth सीमित थी, packet का minimum size 64 bytes था, और inter-frame gap भी चाहिए था, वहाँ हर byte पर एक TCP packet भेजना bandwidth की बहुत बड़ी बर्बादी था
    ज़्यादातर Ethernet networks में आज भी minimum size वही है, और empty ACK भेजना भी वैसा ही है
    फिर भी मेरी default position यह है: यह TCP_NODELAY नहीं, बस TCP है

    • अच्छा होता अगर कोई ऐसा protocol होता जिसमें यह पता लगाने का built-in mechanism हो कि दूसरी तरफ़ की pipe किसी भी कारण से टूट गई है
    • क्या QUIC(https://en.wikipedia.org/wiki/QUIC) को latency जैसी TCP समस्याएँ हल नहीं करनी चाहिए
  • यह तर्क कि Nagle की अब ज़रूरत नहीं रही, मुझे पूरी तरह विश्वसनीय नहीं लगता
    Telnet आज भले महत्वपूर्ण न हो, लेकिन अब भी शायद ऐसे बहुत से applications होंगे जो इस तरह काम करते हों
    write(fd, "Host: "), write(fd, hostname), write(fd, "\r\n"), write(fd, "Content-type: ") आदि
    यह 40x overhead न भी हो, तब भी 5x तक हो सकता है

    • application को ठीक कर देना चाहिए
      file पर लिखते समय भी कोई ऐसा करके जादुई performance की उम्मीद नहीं करता। OS के पास अपना buffering भी होता है, फिर भी नहीं
      socket पर लिखते समय अलग उम्मीद रखने की कोई वजह नहीं है, और Nagle वैसे भी system call overhead से बचाता नहीं है
    • Telnet की बात देखकर मुझे जिज्ञासा हुई कि OpenSSH क्या करता है, और यह interactive sessions समेत सभी connections पर TCP_NODELAY सेट करता है
      मैंने code पढ़कर और strace behavior देखकर दोनों तरह से इसकी पुष्टि की
    • अगर इसे asynchronous I/O के नज़रिए से देखें, तो हर छोटे write(2) पर block होने के बजाय buffer में डालना ही एकमात्र समझदारी वाली बात है, इसलिए यह pattern अब इतना आम नहीं होगा
      servers में अच्छी scalability के लिए asynchronous I/O आमतौर पर ज़रूरी होता है, और clients में भी network calls पर block होना खराब अनुभव देता है
      खासकर आज के माहौल में, जहाँ network changes अक्सर होते हैं और out-of-range होना भी आम है
    • कुछ developers घटिया code लिखते हैं, इसका मतलब यह नहीं कि पूरे Internet को सज़ा मिले
    • शुरुआत से ही ऐसा नहीं करना चाहिए
      networking पहलू से अलग भी, system calls काफ़ी महँगे होते हैं, इसलिए performance के लिए बुरे हैं
  • जब application source तक पहुँच न हो, तब socket पर TCP_NODELAY चालू करने का कोई अच्छा तरीका पता है क्या
    मुझे कोई स्थायी kernel setting या बाद में बदलने वाला command नहीं मिला
    routing table में quickack 1 डालकर delayed ACK तो बंद किया जा सका, लेकिन application के बाहर से TCP_NODELAY चालू करना खास तौर पर मुश्किल लगता है
    हाल ही में मैं ठीक इसी समस्या से गुज़रा हूँ, मेरे मालिकाना application और उसके साथ interact करने वाले proprietary-source application के बीच, जैसा यहाँ बताया गया है

    • क्या socket(2) के लिए LD_PRELOAD interception जैसा कुछ काम नहीं करेगा
      असली function को call करने के बाद setsockopt जैसा कुछ किया जाए, और फिर modified socket वापस कर दिया जाए
    • खास स्थिति के अनुसार बीच में socat डालना संभव हो सकता है
      अगर मूल रूप से your_app —> server है, तो उसे your_app -> localhost_socat -> server बनाया जा सकता है
      socat में tcp_nodelay सेट करने के लिए command-line option है
      बस proprietary-source app को localhost से connect करने के लिए मनाना होगा
      अगर वह DNS lookup करता है, तो /etc/hosts entry से उसे localhost पर भेजना संभव हो सकता है
      app local socket के ज़रिए socat से बात करेगा, इसलिए app side का tcp_nodelay असर नहीं डालेगा
    • debugger attach करके ptrace से setsockopt call करवा देना कैसा रहेगा
    • /proc//fd/ खोलकर socket option सेट करने का तरीका काम कर सकता है। मैंने इसे test नहीं किया है
    • LD_PRELOAD
  • लगभग 15 साल पहले मैं एक बहुत real-time MMO खेलता था, और उसका सारा communication TCP पर था
    button click करने पर response packet लौटने तक मेरी action स्क्रीन पर दिखती ही नहीं थी
    आखिरकार इस game को खेलने वाले बच्चे, मैं भी उनमें था, सबने पता लगा लिया कि TCP_NODELAY चालू करने से game बहुत smooth हो जाता है
    खासकर California के वे players, जो game server के काफ़ी करीब थे, उन्हें इसका बड़ा फायदा मिला

    • पता नहीं आप WoW की बात कर रहे हैं या नहीं, लेकिन लगभग उसी समय एक game update ने ठीक यही बदलाव किया था, और शायद कुछ और चीज़ें भी बदली थीं
      एक दिलचस्प side effect यह था कि बदलाव से पहले, अगर TCP stream रुक जाती थी, तो game थोड़ी देर के लिए freeze हो जाता था और फिर छूटे हुए receive events को बहुत तेज़ी से replay करता था
      आमतौर पर वे events मेरे मरने के दृश्य होते थे
      बदलाव के बाद उसकी जगह connection बस टूट जाता था
  • संबंधित Oxide and Friends podcast episode: https://www.youtube.com/watch?v=mqvVmYhclAg

    • यह शानदार episode था, और इसने visualization की अहमियत बहुत ज़ोरदार ढंग से दिखाई
  • अगर आप Go जैसी आधुनिक भाषाएँ इस्तेमाल करते हैं, जो डिफ़ॉल्ट रूप से TCP_NODELAY चालू करती हैं, तो यह लागू नहीं होता :-)

  • हर बार ऐसा नहीं होता। कभी-कभी समस्या DNS होती है

    • एक बार राउटर की खराब line card IPv4 address के आख़िरी bit को 0 बना रही थी, इसलिए “सिर्फ even IPv4 addresses ही accessible हैं” वाला ticket बना
    • मेरे मामले में एक बार काँच गंदा था
      निर्माण स्थल के पास एक राउटर में धूल laser और optical fiber के बीच की दरार में जम गई, जिससे signal इतना attenuate हो गया कि 40~50% packet loss दिखा
      loss point ढूँढने के बाद NOC ने उस transit provider को email भेजा, और एक दिन बाद भेजे गए technician ने जवाब में पूरी कहानी बताई
    • 50 साल में एक बार, 2 अरब km दूर, यह कोई खराब memory chip भी हो सकती है
      फिर भी आम तौर पर workaround patch किया जा सकता है, इसलिए बहुत बड़ी बात नहीं होती
    • BGP, या बिना किसी alert के disk का भर जाना, इसे भी मत भूलिए
    • अगर fail हो तो DNS है, और अगर बस चलना बंद हो जाए तो TCP_NODELAY या stream buffering है
      Web जैसा सचमुच जटिल सिस्टम cache की वजह से भी fail होता है