- distributed systems में latency की समस्या अक्सर सिर्फ
TCP_NODELAYenable करने से हल हो जाती है, और 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_NODELAYenable है या नहीं - कई 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 में, या कई
writecalls के जरिए थोड़ा-थोड़ा data kernel को देने वाली implementations में आते थे
- छोटे packets मुख्यतः shell जैसी human-interaction applications में, या कई
- इसका मुख्य व्यवहार यह है कि जब पहले भेजे गए 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_NODELAYenable करके Nagle algorithm को disable कर सकते हैं - आधुनिक systems के traffic, application structure और hardware performance को देखते हुए Nagle algorithm अब जरूरी नहीं रह गया हो सकता है
- यह रुख भी संभव है कि
TCP_NODELAYdefault value होनी चाहिए - हर byte पर
writecall करने वाला codeTCP_NODELAYdefault होने पर धीमा हो सकता है - अगर 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 टिप्पणियां
Hacker News टिप्पणियाँ
अपने करियर के दौरान मैंने Nagle algorithm की वजह से होने वाली latency समस्याएँ कई बार ठीक की हैं, और अब यह उन चीज़ों में है जिन पर सबसे पहले शक करता हूँ
इसका तर्क अपने आप में सही है, लेकिन कुछ workloads के लिए उपयुक्त नहीं है, और मेरा मानना है कि socket बनाते समय engineers को इसे स्पष्ट रूप से चुनना चाहिए, न कि इसे operating system के default पर छोड़ देना चाहिए
समस्या यह नहीं है कि यह अच्छा option है या बुरा, बल्कि यह है कि data transfer के तरीके को काफ़ी आक्रामक रूप से बदल देने वाली setting मौजूद है और बहुत से लोगों को उसके अस्तित्व का ही पता नहीं है
अब तक हर बार कोई न कोई 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 ऐसा होना चाहिए जो चौंकाने वाला न लगे
writebehavior वाले applications को ठीक करना है, तो TCP_DELAY को चालू करने वाला option काफ़ी विचित्र हो जाता हैइसका मतलब है कि आपको ऐसा software engineer चाहिए जो इस option के बारे में जानने लायक़ तो होशियार हो, लेकिन
writecalls को सही तरह बाँटने या अपनी application के लिए बेहतर Nagle-जैसी buffering खुद बनाने लायक़ होशियार न होio_uringजैसी कोई चीज़ जो system call की लागत को offset कर दे, उसके बिना user space वाला तरीका बेहतर काम करता हैजहाँ तक मुझे याद है, वही इसकी मुख्य प्रेरणा थी
निष्कर्ष थोड़ा अजीब है। 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 उपयोगी होता है
इसकी वजह से भौतिक teletypewriter से services से जुड़ना संभव था, लेकिन TCP message boundaries नहीं जानता, और आज भले हम उस जानकारी का कुछ हिस्सा वापस डाल सकें, शुरुआती software ऐसा नहीं कर सकता था
इसके विपरीत QUIC, SCTP, TP4 जैसे कई non-TCP protocols message boundaries को स्पष्ट रूप से उपलब्ध कराते हैं
system के साथ interface emulated serial port नहीं, बल्कि message-based होता है, जिसे ज़्यादा से ज़्यादा reassembly की ज़रूरत पड़ती है
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_QUICKACKsocket 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 पर सेट करना पड़े, यह किस सोच के साथ बनाया गया होगाकोई इसे सिर्फ कुछ समय के लिए ही क्यों बंद रखना चाहेगा
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 हैयह तर्क कि Nagle की अब ज़रूरत नहीं रही, मुझे पूरी तरह विश्वसनीय नहीं लगता
Telnet आज भले महत्वपूर्ण न हो, लेकिन अब भी शायद ऐसे बहुत से applications होंगे जो इस तरह काम करते हों
write(fd, "Host: "),write(fd, hostname),write(fd, "\r\n"),write(fd, "Content-type: ")आदियह 40x overhead न भी हो, तब भी 5x तक हो सकता है
file पर लिखते समय भी कोई ऐसा करके जादुई performance की उम्मीद नहीं करता। OS के पास अपना buffering भी होता है, फिर भी नहीं
socket पर लिखते समय अलग उम्मीद रखने की कोई वजह नहीं है, और Nagle वैसे भी system call overhead से बचाता नहीं है
मैंने code पढ़कर और
stracebehavior देखकर दोनों तरह से इसकी पुष्टि कीwrite(2)पर block होने के बजाय buffer में डालना ही एकमात्र समझदारी वाली बात है, इसलिए यह pattern अब इतना आम नहीं होगाservers में अच्छी scalability के लिए asynchronous I/O आमतौर पर ज़रूरी होता है, और clients में भी network calls पर block होना खराब अनुभव देता है
खासकर आज के माहौल में, जहाँ network changes अक्सर होते हैं और out-of-range होना भी आम है
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 वापस कर दिया जाएअगर मूल रूप से
your_app —> serverहै, तो उसेyour_app -> localhost_socat -> serverबनाया जा सकता हैsocat में
tcp_nodelayसेट करने के लिए command-line option हैबस proprietary-source app को localhost से connect करने के लिए मनाना होगा
अगर वह DNS lookup करता है, तो
/etc/hostsentry से उसे localhost पर भेजना संभव हो सकता हैapp local socket के ज़रिए socat से बात करेगा, इसलिए app side का
tcp_nodelayअसर नहीं डालेगाptraceसेsetsockoptcall करवा देना कैसा रहेगा/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 के काफ़ी करीब थे, उन्हें इसका बड़ा फायदा मिला
एक दिलचस्प side effect यह था कि बदलाव से पहले, अगर TCP stream रुक जाती थी, तो game थोड़ी देर के लिए freeze हो जाता था और फिर छूटे हुए receive events को बहुत तेज़ी से replay करता था
आमतौर पर वे events मेरे मरने के दृश्य होते थे
बदलाव के बाद उसकी जगह connection बस टूट जाता था
संबंधित Oxide and Friends podcast episode: https://www.youtube.com/watch?v=mqvVmYhclAg
अगर आप Go जैसी आधुनिक भाषाएँ इस्तेमाल करते हैं, जो डिफ़ॉल्ट रूप से TCP_NODELAY चालू करती हैं, तो यह लागू नहीं होता :-)
https://github.com/golang/go/issues/57530
यह नहीं पता था
क्या बस कोई “modern” networking library इस्तेमाल नहीं की जा सकती?
हर बार ऐसा नहीं होता। कभी-कभी समस्या DNS होती है
निर्माण स्थल के पास एक राउटर में धूल laser और optical fiber के बीच की दरार में जम गई, जिससे signal इतना attenuate हो गया कि 40~50% packet loss दिखा
loss point ढूँढने के बाद NOC ने उस transit provider को email भेजा, और एक दिन बाद भेजे गए technician ने जवाब में पूरी कहानी बताई
फिर भी आम तौर पर workaround patch किया जा सकता है, इसलिए बहुत बड़ी बात नहीं होती
TCP_NODELAYया stream buffering हैWeb जैसा सचमुच जटिल सिस्टम cache की वजह से भी fail होता है