Hanzo बीटा में शामिल हों और किसी भी सशुल्क योजना पर 50% की छूट पाएँ

GB10 पर आधा मिलियन RPS: अड़चन नेटवर्क थी, मशीन नहीं

हम देखना चाहते थे कि NVIDIA GB10 पर हमारा क्लाउड डेटा प्लेन कितना तेज़ चल सकता है। जवाब ने अपने कोड से ज़्यादा NIC की रिसीव कतारों के बारे में सिखाया — और उसी तार पर थ्रूपुट दोगुने से भी अधिक हो गया।

इस पृष्ठ पर

हम अपना क्लाउड एक ही Go बाइनरी पर बनाते हैं — एक ऐसा आर्टिफैक्ट जो AI गेटवे, वाणिज्य, IAM और KMS को माउंट करता है और api.hanzo.ai परोसता है। उसकी HTTP सतह zip है, हमारा ZAP-मूल वेब फ्रेमवर्क, fasthttp के ऊपर लिपटा हुआ। इसलिए हमने एक सीधा सवाल पूछा: यह चीज़ असल में कितनी तेज़ चल सकती है, और उसे कौन रोकता है?

ईमानदार जवाब ने हमें तीन बार चौंकाया। यह पूरी जासूसी कहानी है, अंकों समेत। सब कुछ hanzoai/benchmarks में सार्वजनिक है।

पहला: क्या फ्रेमवर्क ही कर है?

नहीं। zip उस कच्चे fiber/fasthttp इंजन के ऊपर, जिसे वह लपेटता है, लगभग 24 नैनोसेकंड का स्थिर भार और एक 48-बाइट आवंटन लेकर चलता है — किसी भी वास्तविक हैंडलर पर 1% से कम। सिरे से सिरे तक, लूपबैक पर, zip कच्चे fasthttp का 0.95× करता है। फ्रेमवर्क शोर है। अच्छा। आगे बढ़ते हैं।

फ्रेमवर्कअनुरोध/सेकंड
कच्चा fasthttp365–400k
zip (HTTP)338–348k

दूसरा: वितरित परिणाम धीमा हो गया

यहीं बात दिलचस्प हुई। हमारे पास सीधे 2.5 GbE तार पर दो मशीनें हैं: spark (एक NVIDIA GB10, 20 कोर) सर्वर के रूप में, evo (32-कोर x86) लोड जनरेटर के रूप में। लोड को मशीन से बाहर ले जाने पर spark के कोर सेवा के लिए मुक्त होने चाहिए थे — हमें उम्मीद थी कि गति ऊपर जाएगी।

वह नीचे गई। 194k अनुरोध/सेकंड। और जब हमने और ज़ोर से धकेलने के लिए कनेक्शन जोड़े, तो हाल बदतर हुआ: 4,000 कनेक्शन पर 167k, 8,000 पर 154k, और p99 विलंबता 5 ms से 52 ms तक चढ़ गई। यह पाठ्यपुस्तक वाला संकुलन पतन है।

पर कड़ी केवल ~70 MB/s पर थी — 2.5 GbE का 22%। बैंडविड्थ नहीं। और spark के 20 कोर ज़्यादातर खाली थे। CPU नहीं। तो फिर क्या?

दोषी: एक रिसीव कतार

ethtool -l ने पकड़ लिया: NIC एक ही हार्डवेयर RX कतार पर चल रहा था। हर आने वाला पैकेट एक कोर के softirq में संसाधित हो रहा था — और वही एक कोर छत था। उससे भी बुरा, ड्राइवर ने कतारें जोड़ने से साफ़ इनकार कर दिया (ethtool -L → "Operation not supported")।

इस तरह हमारी सबसे तेज़ डेवलपर मशीन को एक अकेला कर्नेल थ्रेड पैकेट ढोते हुए रोक रहा था, और उन्नीस कोर तमाशा देख रहे थे।

सुधार: पैकेटों को सॉफ़्टवेयर में मोड़ो

जब हार्डवेयर बहु-कतार न दे, कर्नेल देता है — रिसीव पैकेट स्टीयरिंग प्रवाहों को सॉफ़्टवेयर में कोरों पर हैश करता है। हमने सभी 20 कोर पर RPS (और RFS/XPS) चालू किया, रिसीव बैकलॉग 1,000 से 300,000 किया, सॉकेट बफ़र 256 MiB रखे, और परफॉर्मेंस गवर्नर पिन कर दिया। एक ही आयडम्पोटेंट tune.sh, दोनों मशीनों पर।

वही तार। वही हैंडलर। 518,000 अनुरोध/सेकंड — 2.67× की छलांग, शिखर 696k।

समवर्तिताबिना ट्यूनिंगट्यून्ड
1,000194k518k
4,000154k474k (शिखर 696k)

असली बात: ट्यून्ड नेटवर्क अंक (518k) अब लूपबैक (365–400k) को पीछे छोड़ देता है — क्योंकि तार पर spark के कोर पूरा समय सेवा करते हैं, लोड जनरेटर के साथ बाँटने के बजाय। 518k पर मशीन केवल ~12% खाली रहती है। यह एक ही लोडर से मिलने वाली वास्तविक छत के क़रीब है।

तीसरा: ZAP को तेज़ होना था — इसलिए हमने उसे तेज़ किया

zip ZAP-मूल है — एक खाली लिसन पता हमारे बाइनरी ट्रांसपोर्ट बोलता है; http:// HTTP बोलता है। हमें उम्मीद थी कि बाइनरी ट्रांसपोर्ट जीतेगा। हमने एक ZAP लोड क्लाइंट बनाया (HTTP उपकरण उसे बोल नहीं सकते), और मापा।

ZAP ने 91k किया। HTTP ने 481k किया। मूल ट्रांसपोर्ट हारा — 5× से।

हमने उसका पीछा किया, और बात तार की नहीं — कोडेक की है। विलंबता की पूँछ ने कहानी कह दी (p50 स्वस्थ 303 µs, पर p99 13 ms पर — कचरा-संग्रह का हस्ताक्षर), और GOGC को 100 से 800 करने पर ZAP दोगुने से भी अधिक होकर 203k हो गया। छह सेकंड की दौड़ में ZAP सर्वर ने 2,203 कचरा-संग्रह चलाए; HTTP सर्वर ने 57। तीन आवंटन स्थल इसे ढो रहे थे: मार्शलर हर कॉल पर नया फ़्रेम बफ़र बनाता है, हेडर डिकोड हर स्ट्रिंग को मैप में कॉपी करता है, और — बड़ी बात — zaphttp का सर्वर हर अनुरोध पर नया अनुरोध संदर्भ आवंटित करता था, जहाँ fasthttp का अपना सर्वर उसे हर कनेक्शन पर पूल करता है।

इसलिए हमने उसे ठीक किया। बदलाव (zap-proto/http, शाखा perf/zero-alloc-codec) यांत्रिक है, वास्तुशिल्पीय नहीं: अनुरोध संदर्भ को प्रति कनेक्शन एक बार पूल करें, एक फ़्रेम एन्कोडर हाथ से लिखें जो पूल किए बफ़र में जोड़ता है, और 4-बाइट लंबाई-उपसर्ग को bufio.Peek/Discard से निकालें ताकि कोई हेडर ऐरे हीप पर न भागे। सर्व लूप उस पुन:प्रयुक्त बफ़र में AppendResponse से क्रमांकित करता है और हेडर वहीं डिकोड करता है — इसलिए एक गर्म कनेक्शन पर वह प्रति अनुरोध कुछ भी आवंटित नहीं करता — और गोल्डन परीक्षण पुष्टि करते हैं कि तार पहले और बाद में बाइट-दर-बाइट समान है। शुद्ध कोडेक पुनर्लेखन, प्रोटोकॉल में कोई बदलाव नहीं।

परिणाम: ZAP 91k → 570k अनुरोध/सेकंड तक जाता है, 6.3× की उठान जो HTTP के 481k को पार कर जाती है — लूपबैक पर ZAP अब 1.18× से जीतता है, p99 13 ms से 1.19 ms पर सिमट जाता है और GC 2,203 से 62 पर आ जाता है।

दो बातें थीं जिन्हें मुझे खुद सुधारना पड़ा — और दोनों सुधार के बाद भी टिकती हैं, इसलिए उन्हें साफ़ कहना चाहिए। पहली, जीत CPU की है, तार की नहीं। ZAP फ़्रेम वास्तव में HTTP से बड़ा है (/health अनुरोध के लिए 124 बनाम 75 बाइट); ZAP कम बाइट खर्च करके नहीं, प्रति बाइट कम चक्र खर्च करके जीतता है। दूसरी, zaphttp JSON को छोड़ता नहीं। वह दोनों ट्रांसपोर्ट पर HTTP बॉडी को अपारदर्शी बाइटों की तरह ले जाता है, इसलिए हैंडलर दोनों ओर समान मार्शल लागत चुकाता है — चैट-आकार के JSON पेलोड पर ZAP वास्तव में HTTP का 0.95× है, फ़्रेम की पूँछ में बॉडी कॉपी करने से हल्की हार। JSON-लोप मूल ZAP टाइप्ड RPC का गुण है, एक अलग अक्ष जिसे zaphttp छूता ही नहीं। यहाँ की लूपबैक जीत ईमानदार है और विशुद्ध CPU/आवंटन की जीत है। और असली 2.5 GbE तार पर? दोनों ट्रांसपोर्ट ~530k पर बराबरी करते हैं — कड़ी संतृप्त है और कोडेक अब अड़चन नहीं रहा।

चौथा: तो क्या ZAP सच में JSON छोड़ता है?

वह आख़िरी पंक्ति — «JSON-लोप एक मूल-ZAP गुण है जिसे zaphttp नहीं छूता» — एक वचनपत्र है, और पाठक को मुझसे उसे भुनवाना चाहिए। zaphttp बॉडी को अपारदर्शी ले जाता है, इसलिए वह उस चीज़ की परीक्षा नहीं करता जिसमें बाइनरी प्रोटोकॉल को जीतना चाहिए: पाठ पार्स किए बिना संरचित डेटा पढ़ना। इसलिए मैंने वह परीक्षण बनाया जो यही करता है।

एक साझा रिकॉर्ड — eth_getBalance के आकार की कॉल, हर दिशा में चार फ़ील्ड (account, block, id → balance, nonce, blockHash) — चार तरीकों से आगे-पीछे: मानक पुस्तकालय का encoding/json, goccy/go-json और sonic (Go की दो सबसे तेज़ JSON पुस्तकालय), और zap-proto/go का टाइप्ड बाइनरी। वही फ़ील्ड अंदर, वही गणना, वही फ़ील्ड बाहर; एक राउंड-ट्रिप तुल्यता परीक्षण बाइट-सटीक पुनः एन्कोडिंग लागू करता है ताकि कोई पक्ष वह काम न छोड़ सके जो दूसरा करता है। और मैंने JSON को उसके सबसे तेज़ कार्यान्वयन पर रखा, मानक पुस्तकालय पर नहीं, ताकि कोई परिणाम को हाथ हिलाकर टाल न सके।

डिकोड एक संहार है। चार-फ़ील्ड अनुरोध पढ़ना:

कोडेकडिकोडआवंटन
JSON मानक2334 ns13
JSON sonic845 ns8
JSON goccy473 ns4
ZAP टाइप्ड13 ns0

तेरह नैनोसेकंड, शून्य आवंटन — सबसे तेज़ JSON से 35× तेज़, मानक पुस्तकालय से 174× तेज़। कोई पार्स नहीं: फ़ील्ड ज्ञात ऑफ़सेट पर बैठा है, आप उसे पढ़ते हैं। टाइप्ड बाइनरी तार का पूरा दावा यही है, और वह टिकता है।

पर मैंने वादा किया था कि जो हिस्से ZAP की तारीफ़ नहीं करते उन्हें भी बताऊँगा, इसलिए:

  • एन्कोड बराबरी है, और वह अधिक आवंटित करता है। ZAP 629 ns में 12 आवंटनों के साथ क्रमांकित करता है, goccy के 426 ns / 4 के मुक़ाबले। प्रकाशित zap-proto/go v1.3.0 बिल्डर चर-लंबाई वाली पूँछों को एक कॉपी के ज़रिए टालता है और अपने ऑब्जेक्ट बिल्डर को हीप पर भेज देता है; जो पूल्ड स्थिर-चौड़ाई API उसे ठीक करती है वह जारी टैग में नहीं है, इसलिए यही ईमानदार अंक है। शुद्ध हिसाब में, पूरा सर्वर हैंडलर (डिकोड + गणना + एन्कोड) ZAP को ही पक्ष देता है — 274 ns बनाम goccy के 431 — क्योंकि डिकोड की जीत एन्कोड की बराबरी को डुबो देती है। पर बोझ डिकोड उठा रहा है।
  • सिरे से सिरे तक, 174× सिकुड़कर ~1.2× रह जाता है। एक वास्तविक /rpc एंडपॉइंट पर: ZAP 535k अनुरोध/सेकंड, sonic 483k (1.11×), goccy 452k (1.18×), मानक पुस्तकालय 407k (1.31×)। वास्तविक, पर सूक्ष्म-माप की खाई के क़रीब भी नहीं — क्योंकि आधा मिलियन अनुरोध प्रति सेकंड पर सॉकेट और शेड्यूलर ही लागत हैं, और क्रमांकन उसका पतला टुकड़ा है। कोडेक अड़चन बनना बहुत पहले बंद कर देता है, इससे पहले कि आपको उसकी तेज़ी दिखे।

एक आश्चर्य ZAP के पक्ष में काटता है: एक वास्तविक संरचित रिकॉर्ड पर उसका तार छोटा होता है, बड़ा नहीं — 162 बनाम 233 बाइट — क्योंकि JSON 0x-हेक्स स्ट्रिंग, दशमलव पूर्णांक, उद्धरण और फ़ील्ड नामों पर बाइट खर्च करता है। फ़्रेमिंग परीक्षण की «ZAP बड़ा है» वाली चेतावनी केवल तुच्छ /health-आकार के पेलोड पर टिकती है; उसे वास्तविक टाइप्ड फ़ील्ड दीजिए और बाइनरी आकार में भी जीतता है।

तो, ईमानदारी से: क्या ZAP JSON छोड़ता है? हाँ — और पढ़ने के पथ पर फ़ासला क़रीब नहीं। आप उसे महसूस करते हैं या नहीं, यह इस पर निर्भर है कि क्रमांकन आपकी अड़चन है या नहीं। छोटे रिकॉर्ड के 500k rps पर नहीं है। मोटे नेस्टेड पेलोड या डिकोड-भारी फैन-आउट लगाइए, और वह 174× वहीं दिखने लगता है जहाँ आप रहते हैं।

हमने क्या सीखा

  • पूरा पथ मापो, और उपकरण पर सवाल करो। hey ने हमें ~100k पर रोक दिया — वह क्लाइंट था, सर्वर नहीं। bombardier ने 4× अधिक दिखाया।
  • मशीन शायद ही पहली अड़चन हो। एक NIC RX कतार ने हमें 2.67× की कीमत चुकवाई, इससे पहले कि हम अनुप्रयोग कोड की एक पंक्ति छूते।
  • केवल थ्रूपुट नहीं, आवंटन मापो। ZAP का «धीमा होना» एक GOGC झंडे की दूरी पर साफ़ कोडेक समस्या था — और जब हमने संदर्भ पूल किया और आवंटन शून्य किए, मूल ट्रांसपोर्ट HTTP से आगे निकल गया।
  • सूक्ष्म-माप और लोड परीक्षण अलग सवालों के जवाब देते हैं। टाइप्ड डिकोड अकेले JSON से 174× तेज़ है और सिरे से सिरे तक 1.2× तेज़ — दोनों अंक सत्य हैं, और केवल एक को उद्धृत करना चुप्पी का झूठ होगा।
  • उसे निष्पक्ष और वास्तविक बताओ। वही हैंडलर, गर्म कनेक्शन, चुना हुआ GOGC नहीं, JSON को उसकी सबसे धीमी नहीं सबसे तेज़ लाइब्रेरी पर रखा, और स्क्रिप्ट प्रकाशित करो। जब ईमानदार अंक हार था — एन्कोड की बराबरी, दबी हुई एंड-टू-एंड मार्जिन — हमने हार भी भेजी।

शून्य-आवंटन ZAP कोडेक पूरा हो चुका है, और वह जीतता है। बची रुकावटें भौतिक हैं: दो NIC वाला लोडर (सात अंकों की ओर समानांतर रिसीव पथ), तेज़ इंटरकनेक्ट (10 GbE / RDMA), एक writev विभाजन ताकि बड़े पेलोड पर ZAP की फ़्रेम पूँछ एक बॉडी-कॉपी छोड़ दे, और वे पोस्ट-क्वांटम ट्रांसपोर्ट जो Go 1.26 हमें मूल रूप से देता है — PQ-TLS 1.3, ZAP-over-PQ-TLS, और PQ-QUIC — जहाँ हम ML-KEM हैंडशेक की लागत को स्थिर-अवस्था थ्रूपुट से अलग करेंगे।

हम इसे उतनी दूर ले जा सकते हैं जितनी ज़रूरत हो।

टिप्पणियाँ

टिप्पणी के लिए अपने Hanzo ID से साइन इन करें।

टिप्पणियाँ लोड हो रही हैं…

और पढ़ें