3× अधिक कनेक्शन। समान कोर। FlashProxy के Accept Engine के पीछे Performance Research

कैसे FlashProxy ने एक स्वायत्त AI optimizer का उपयोग करके एक TCP accept engine की खोज की जो Go की तुलना में 3× कम CPU निर्देशों का उपयोग करता है। Open-source और MIT लाइसेंस प्राप्त।
performance समस्याओं की एक श्रेणी है जो केवल scale पर दिखाई देती है, और जब यह होता है तो यह गहराई से विनम्र करने वाला होता है। आप अपने routing logic पर bottleneck नहीं हैं। Encryption पर नहीं। आपके application को वास्तव में करना चाहिए इस पर कुछ भी नहीं। आप इसके आसपास के infrastructure पर bottleneck हैं। Plumbing पर।
यही वास्तव में हमारे साथ हुआ।
लगभग 40,000 कनेक्शन प्रति सेकंड पर, हमारे production Go proxy ने accept path में CPU limits को hit करना शुरू किया। दोषी Go का network services के लिए standard model था: one goroutine per connection, one syscall per operation। हर short-lived connection (एक health check, एक load balancer probe, एक छोटा redirect) kernel को चार बार touch करता है। accept, read, write, close। High connection churn पर, ये चार round-trips overhead होने से निकलकर पूरी cost बन जाती हैं। Application code लगभग free है। Bytes को kernel के अंदर और बाहर प्राप्त करना नहीं है।
Fix जो हर कोई पकड़ता है वह io_uring है, Linux का asynchronous I/O interface जो kernel operations को batch करता है और user/kernel boundary को cross करने की संख्या को नाटकीय रूप से कम करता है। हम यह जानते थे। कठिन प्रश्न यह था कि इसे कैसे tune करें।
हमने अपने आप इसे Tune करने का प्रयास क्यों नहीं किया
io_uring में documented optimizations की एक लंबी सूची है: multishot accept, registered file descriptors, DEFER_TASKRUN, completion chains, MSG_MORE coalescing। समस्या यह है कि ये techniques एक दूसरे के साथ इस तरह interact करती हैं जो genuinely predict करना कठिन है। कुछ combinations compound होते हैं। कुछ एक दूसरे को cancel कर देते हैं। कुछ regressions completely invisible रहती हैं जब तक आप real hardware पर realistic load के तहत benchmark न करें।
एक developer जो perf stat के साथ बैठा हो एक दिन में कुछ combinations को test कर सकता है। लेकिन real search space, जो combinations, orderings, parameter values, और kernel version interactions को cover करती है, वह इससे कहीं बड़ी है। अधिक महत्वपूर्ण रूप से, human intuition यहाँ एक liability है। हम उन theories को hold करते रहते हैं जो paper पर सही दिखते हैं, भले ही data कुछ और कहे।
तो हमने एक closed measurement loop बनाया और AI agent को search problem दिया।
Research Loop कैसे काम करता था
हमने दो server implementations को side by side चलाते हुए set up किया। Control एक Go server था जो हमारे production proxy के accept path को exactly model करता था: goroutine per connection, SO_REUSEPORT fan-out। यह पूरे experiment के लिए frozen था। Treatment एक C server था जो liburing का उपयोग करता था, एक basic single-shot io_uring accept loop से शुरू होकर। यह एकमात्र code था जिसे change किया जा सकता था।
दोनों servers ने एक ही contract serve किया: एक connection को accept करें, request bytes को read करें, एक fixed HTTP 200 response को write करें, close करें। Same hardware, same kernel, same load। एकमात्र variable यह था कि वे इसे कैसे handle करते थे।
Claude Code optimizer के रूप में headless चलता था। हर iteration पर यह current champion को read करता था, previous mutations का पूरा history जो git commits के रूप में stored था, runs में accumulated lessons का एक knowledge base, और profiling data का एक database। यह एक single hypothesis बनाता था और अपना edit करता था। फिर यह पूरी तरह से hand off कर देता था।
एक अलग bash harness, जिसे AI touch नहीं कर सकता था, mutation को build करता था, इसे एक isolated CPU core पर pin करता था, load generator चलाता था, और result को score करता था। Scoring formula था:
score = 1,000,000,000/mean(instructions per connection)
CPU instruction counts एक exact hardware measurement हैं। Throughput numbers के unlike, वे thermal throttling और clock frequency variation से immune हैं। वे आपको precisely बताते हैं कि CPU per connection कितना काम कर रहा है, जो वास्तव में scale पर capacity को determine करता है।
अगर एक mutation current champion से score में 3% से अधिक improve करता था, तो इसे promote किया जाता था। अगर नहीं, तो इसे git reset --hard के साथ delete किया जाता था और loop continue होता था। AI ने hypotheses लिखे। Harness ने हर keep या revert decision लिया। कोई भी एक दूसरे के काम में हस्तक्षेप नहीं करता था।
Optimizer को benchmark को game करने से रोकने के लिए, हर scored run को validate किया गया: reply bytes को exactly सही होना चाहिए, connections को end-to-end complete होना चाहिए, और failure rate को 0.01% से नीचे रहना चाहिए। किसी भी violation को zero score दिया गया।
हमने क्या पाया
Optimizer दो दिन तक चला और छह changes पर converge हुआ जो मिलकर पूरी performance gap को account करते हैं।
DEFER_TASKRUN और SINGLE_ISSUER ring flags। ये completion task-work को worker thread के अपने event loop में move करते हैं, cross-CPU wakeups को eliminate करते हुए। यह entire run में सबसे बड़ी single jump थी, kernel CPU cost per operation में एक direct reduction, throughput trick नहीं।
Registered file descriptors। Direct descriptors के साथ, accepted connections ring के अपने table में रहते हैं न कि process file descriptor table में। यह accept पर fd-table install को skip करता है और हर subsequent operation पर lookup को। Optimizer ने multiple table sizes को test किया और पाया कि 4,096 entries optimal configuration थी।
Multishot accept। हर connection के बाद accept operation को re-arm करने की बजाय, आप इसे एक बार arm करते हैं और kernel हर नए connection के लिए automatically एक completion post करता है। इसने io_uring_enter calls per connection को 0.34 तक cut कर दिया।
Per-worker connection freelist। Per worker 128 connection objects को pre-allocate करने ने hot path पर malloc को पूरी तरह eliminate किया। Libc को जाने वाले CPU instructions का measured share 1.32% से 0.92% तक गिरा।
MSG_MORE reply और FIN fusion। Reply को MSG_MORE के साथ भेजना इसे TCP write queue में रखता है ताकि connection का FIN इसके साथ piggyback कर सके, दोनों को एक single TCP segment के रूप में भेजा जा सके। दो की बजाय एक NIC doorbell। Optimizer ने यह notice करके पाया कि एक low-level kernel write function अपने expected share की दोगुनी CPU instructions consume कर रहा था और इसे unnecessary segment splitting में trace किया।
Batched completions with CQE_SKIP_SUCCESS। Send और close operations को tag करना ताकि वे success पर completion events generate न करें, इसका मतलब है कि केवल accept और receive completions produce करते हैं, चार की बजाय लगभग दो per connection। एक submit-and-wait call कई connections को simultaneously drive करता है।
वह विफलता जिसने सबसे अधिक सिखाया
एक बिंदु पर, optimizer ने receive, send, और close operations को एक chain में link करने का प्रयास किया, एक ऐसी technique जो हर operation को automatically fire करती है जब previous complete होता है। Goal kernel round-trips को reduce करना था, और यह काम करता था: enter calls per connection 1.90 से 1.40 तक गिरे।
Score 34% गिरा। तुरंत reverted।
Knowledge base में जाने वाला lesson: kernel entries को minimize करना lever नहीं है। Chain serialization उन entries की तुलना में अधिक cost करता है जिन्हें यह save करता है।
यह वास्तव में वह result है जो human intuition को break करता है। Metric जो bottleneck दिख रहा था वह actual bottleneck नहीं था। एक engineer शायद उस optimization को अधिक समय तक defend करता। Loop ने इसे measure किया, reject किया, और आगे बढ़ गया।
जहाँ Search रुकी
छह wins के बाद, optimizer ने लगभग एक दर्जन और candidates को explore किया। सभी को revert किया गया, न इसलिए कि वे regress हुए, बल्कि इसलिए कि measurement noise 3% promotion threshold को exceed करता था। खोजने के लिए कुछ भी नहीं बचा था।
Champion configuration पर, बाकी CPU का लगभग 94% Linux kernel के TCP stack को belong करता है। लगभग 1% application code है। लगभग 4% internals को leverage करना है। Meaningfully optimize करने के लिए कोई भी user-space code नहीं बचा है। Research ने सही तरीके से floor को identify किया और वहीं रुकी।
परिणाम
एक pinned core पर, CPU-bound, loopback पर 512 fixed in-flight connections के साथ:
Go goroutine-per-connection: 83,250 instructions per connection
Vanilla io_uring starting baseline: 59,931 instructions per connection
FlashProxy का accept engine: 27,363 instructions per connection
यह Go की तुलना में 3.04× कम CPU instructions per connection है, और io_uring baseline से 2.19× कम है। एक single saturated core पर, यह goroutine model की तुलना में लगभग छह गुना अधिक connection throughput में translate होता है।
ये loopback benchmarks हैं जो CPU cost को cleanly isolate करने के लिए design किए गए हैं। Ratios वह हैं जो matter करते हैं, absolute numbers नहीं। Underlying techniques (multishot accept, DEFER_TASKRUN, registered descriptors) io_uring idioms में documented हैं। जो research ने produce किया वह proof था कि कौन सी combinations actually एक साथ काम करती हैं, और कौन सी paper पर अच्छी दिखती हैं लेकिन practice में cost करती हैं।
Open Source Library
Winning design अब flashaccept, एक open-source C library है जो सभी छह optimizations को एक simple API के पीछे package करता है। आप इसे एक port और एक request handler देते हैं। यह automatically per core एक optimized io_uring accept loop चलाता है।
इसे Linux और liburing 2.3 या later की जरूरत है। Older kernels पर यह gracefully degrade होता है। Intended use case high-churn, short-lived connections हैं: health checks, redirectors, load balancer probes, small RPC responses। Complete research rig, जिसमें baseline, harness, optimizer configuration, और पूरा accumulated knowledge base शामिल है, साथ में है और fully reproducible है।
MIT licensed। अभी github.com/thealonlevi/flashaccept पर उपलब्ध है।
यह research directly FlashProxy के proxy infrastructure को build करने से आई। अगर आप एक high-churn Linux service चला रहे हैं और accept path आपकी bottleneck है, हमने यह बिल्कुल उसी problem के लिए बनाया है। अगर आप इसे extend करना चाहते हैं या research rig को अपने आप चलाना चाहते हैं, repository में आपको वह सब कुछ है जो आपको चाहिए।
FAQ
Flashaccept क्या है?
Flashaccept एक open-source C library है जो FlashProxy द्वारा build की गई है और Linux के लिए एक high-performance TCP accept engine प्रदान करती है। यह under the hood io_uring का उपयोग करती है और एक standard Go goroutine-per-connection server की तुलना में 3.04× कम CPU instructions में connections को accept करती है। यह MIT licensed है और GitHub पर उपलब्ध है।
यह किन workloads के लिए design किया गया है?
Flashaccept high-churn, short-lived connections के लिए built है जहाँ request-reply-close cycle high volume पर होता है: health check endpoints, load balancer probes, HTTP redirectors, और small RPC responses। यह v1 में keep-alive connections या multi-exchange sessions के लिए design नहीं किया गया है।
मुझे कौन सा Linux version और liburing version चाहिए?
आपको liburing version 2.3 या later के साथ Linux की जरूरत है, जो Ubuntu 24.04 और later के साथ ship होता है। Fast path (multishot accept, direct descriptors) को kernel 5.19 या newer की जरूरत है। Older kernels पर library gracefully single-shot accept और regular file descriptors पर fallback करती है।
io_uring क्या है और proxy performance के लिए यह क्यों matter करता है?
io_uring एक Linux kernel interface है जो kernel 5.1 में introduce किया गया था और applications को shared memory ring buffers का उपयोग करके I/O operations को asynchronously submit और receive करने की अनुमति देता है, system calls की संख्या को dramatically reduce करते हुए। एक proxy infrastructure के लिए जो प्रति सेकंड tens of thousands of short-lived connections handle करता है, user/kernel boundary को repeatedly cross करने की cost dominant CPU expense बन जाती है। io_uring उस cost को significantly collapse करता है।
क्या flashaccept वह है जो FlashProxy production में चलाता है?
Research directly हमारे production scaling work से आई। FlashProxy का proxy infrastructure 190+ countries में operate होता है और substantial connection volume को handle करता है। Accept path optimization एक real engineering requirement था, research exercise नहीं। flashaccept उस work का distilled result है, open-sourced ताकि दूसरे लोग इसे use कर सकें।
क्या मैं flashaccept को आज use कर सकता हूँ?
हाँ। यह version 1.0.1 है, MIT licensed है, और GitHub, vcpkg, Conan, और Arch AUR के via उपलब्ध है। Library को AddressSanitizer और UBSan के तहत सभी चार configuration paths में 400,000+ connections पर test किया गया है।
मैं FlashProxy के infrastructure के बारे में और अधिक कहाँ जान सकता हूँ?
आप FlashProxy blog पर proxy infrastructure को कैसे build और scale करते हैं इसके बारे में अधिक पढ़ सकते हैं या directly हमारे proxy network को explore कर सकते हैं।


