बेंचमार्कचा सापळा

बेंचमार्क दाखवणारे लोक कधीही पूर्ण चित्र दाखवत नाहीत. ते अशा हँडलरचे मोजमाप करतात जो जवळजवळ काहीच करत नाही. एक JSON Hello World किंवा एक स्टॅटिक स्ट्रिंग. अशा पोकळीत (vacuum), Fiber स्पष्टपणे जिंकते. ते Gin पेक्षा प्रति सेकंद अधिक विनंत्या (requests) हाताळते आणि कमी मेमरी वापरते. ते खरे इंजिनिअरिंग आहे. पण तुमचे ॲप्लिकेशन ही पोकळी नसते.

मी केनियामध्ये एक रिअल-टाइम सुरक्षा ॲप तयार करत आहे. ते नैरोबी आणि मोम्बासातील वापरकर्त्यांकडून GPS कोऑर्डिनेट्स घेते आणि गुन्हेगारीचे हॉटस्पॉट्स किंवा रस्ते अपघातांबद्दल अलर्ट पाठवते. याचा अर्थ PostgreSQL writes, Firebase Cloud Messaging calls आणि reverse geocoding lookups यांचा समावेश होतो. जेव्हा तुमच्या स्टॅक मध्ये नेटवर्क हॉप्स (network hops) आणि डेटाबेस राऊंड-ट्रिप्स (database round-trips) यांचा समावेश असतो, तेव्हा राउटिंगवर नॅनोसेकंद वाचवणारे फ्रेमवर्क अदृश्य ठरते. अडथळा (bottleneck) कधीही राउटर नसतो. तो SMS गेटवेचा टाइमआउट असतो. तो इन्सिडेंट रिपोर्टच्या टेबल स्कॅन करणारी PostGIS क्वेरी असते. जोपर्यंत तुमचा ॲप प्रोडक्शनमध्ये येत नाही, तोपर्यंत कच्चा थ्रूपुट (raw throughput) मोहक वाटतो.

स्टँडर्ड लायब्ररीबद्दलची निष्ठा

Gin थेट net/http वर आधारित आहे. हे ऐकायला जितके साधे वाटते, त्यापेक्षा जास्त महत्त्वाचे आहे.

प्रत्येक Go डेव्हलपरला net/http माहित असते. डीबगिंग करताना परिचित स्टॅक ट्रेस (stack traces) मिळतात. पाच वर्षांपूर्वी लिहिलेले मिडलवेअर (middleware) आजही कोणत्याही विशेष बदलाशिवाय वापरता येते. रिक्वेस्ट आणि रिस्पॉन्स ऑब्जेक्ट्स अगदी तशाच प्रकारे वागतात जसे Go डॉक्युमेंटेशनमध्ये सांगितले आहे. Gin अधिक प्रेडिक्टेबल (predictable) राहते कारण ते स्वतः भाषेच्या (language) जवळ राहते.

Fiber त्या पायाची जागा fasthttp ने घेते, जे झिरो-अलोकेशन परफॉर्मन्ससाठी ऑप्टिमाइझ केलेले एक कस्टम HTTP इंजिन आहे. हे साध्य करण्यासाठी, ते Request/Response Pooling वापरते. प्रत्येक रिक्वेस्टनंतर गार्बेज कलेक्टरला (garbage collector) मेमरी रिक्लेम करू देण्याऐवजी, Fiber मेमरी ब्लॉक्स रिसायकल करते. ते ब्लॉक्स साफ करते आणि पुढील येणाऱ्या कनेक्शनला सोपवते. हीच युक्ती वेगाचा स्रोत आहे. आणि हाच धोक्याचा स्रोत देखील आहे.

पुन्हा वापरल्या जाणाऱ्या मेमरीचा छुपा धोका

सिद्धांतानुसार पूलिंग (Pooling) सुरक्षित वाटते. पण प्रत्यक्षात, ते एक सूक्ष्म करार (contract) आणते: तुमचा हँडलर रिटर्न झाल्यानंतर तुम्ही रिक्वेस्ट डेटाचा संदर्भ (reference) कधीही धरून ठेवू नये. जर एखादा goroutine रिक्वेस्टपेक्षा जास्त काळ टिकला, किंवा जर तुम्ही नंतरच्या प्रक्रियेसाठी रिक्वेस्ट बॉडीचा स्लाईस (slice) कॅप्चर केला, तर Fiber ती मेमरी पुन्हा घेईल आणि ती पुढील वापरकर्त्याला देईल.

याचा परिणाम म्हणजे डेटा दूषित होणे (contamination). हा बग युनिट टेस्टमध्ये दिसत नाही. तो नैरोबीमधील ड्रायव्हरचा GPS कोऑर्डिनेट अचानक किसूमधील नकाशावर दिसण्यासारखा दिसतो. तो एका वापरकर्त्याचा JWT टोकन दुसऱ्या वापरकर्त्याच्या कॉन्टेक्स्टमध्ये लीक होण्यासारखा दिसतो. हे तात्पुरते दोष (temporal failures) आहेत. जेव्हा तुम्ही लॉगिंग (logging) जोडता तेव्हा ते गायब होतात, कारण लॉगिंगची क्रिया नवीन मेमरी अलोकेट करते आणि वेळेचे गणित (timing) बदलते. शेवटी तुम्ही केवळ भुतांचा पाठलाग करत राहता.

Gin मध्ये, स्टँडर्ड लायब्ररी एक नवीन रिक्वेस्ट ऑब्जेक्ट अलोकेट करते. तिथे दूषित करण्यासाठी कोणतेही पूल (pool) नसते. जेव्हा तुमचे ॲप प्रत्यक्ष सुरक्षिततेशी संबंधित वास्तविक ठिकाणांची हाताळणी करते, तेव्हा ही सुरक्षा महत्त्वाची ठरते.

इकोसिस्टम ग्रॅव्हिटी

सुसंगततेचा (compatibility) एक शांत कर (tax) देखील असतो.

बहुतेक Go मिडलवेअर net/http गृहीत धरतात. JWT व्हॅलिडेटर्स, OpenTelemetry एक्सपोर्टर्स, CORS हँडलर्स आणि रेट लिमिटर्स हे सर्व स्टँडर्ड इंटरफेस वापरतात. Gin तो इंटरफेस मूळ स्वरूपात (natively) समजते. एखादे मिडलवेअर वापरा आणि ते लगेच काम करते.

Fiber चा स्वतःचा कॉन्टेक्स्ट प्रकार (context type) आणि स्वतःची हँडलर सिग्नेचर (handler signature) आहे. तुम्हाला अडॅप्टर्सची (adapters) गरज पडते. कधीकधी अडॅप्टर अधिकृत असतो, तर कधीकधी तो मूळ लायब्ररीपेक्षा एक वर्ष मागे असतो. कधीकधी लोड असताना तो थोडे वेगळे वागतो. जेव्हा तुम्ही एक लहान टीम चालवत असता, तेव्हा एखादे ऑथेंटिकेशन मिडलवेअर तुमच्या लॅपटॉपवर का चालते पण सर्व्हरवर वैध टोकन्स का नाकारते, हे डीबग करण्यासाठी तुमच्याकडे अतिरिक्त वेळ नसतो. तुम्हाला go get हे कंटाळवाणे (boring) असावे असे वाटते. Gin ते कंटाळवाणे ठेवते, आणि रात्री ३ वाजता तुम्हाला नेमके तेच हवे असते.

ॲपला प्रत्यक्षात कशाची गरज आहे

माझी सुरक्षा सेवा एकाच वेळी अनेक वापरकर्त्यांकडून येणारे लोकेशन पिंग्स (location pings) दर काही सेकंदांनी प्रोसेस करते. ती उच्च-धोका असलेल्या रस्त्यांसाठी जिओफेंसिंग (geofencing) करते. ती नोटिफिकेशन्स ट्रिगर करते. उशिरा येणारा अलर्ट त्रासदायक असतो, तर चुकीचा अलर्ट धोकादायक असतो. दूषित पूल्ड मेमरीमुळे (corrupted pooled memory) एखाद्या व्यक्तीला सक्रिय अपघात स्थळाकडे पाठवणे अस्वीकार्य आहे.

येथे थ्रूपुटपेक्षा विश्वासार्हता (reliability) महत्त्वाची आहे. जेव्हा मी pprof जोडतो, तेव्हा मला अर्थपूर्ण स्टॅक ट्रेस हवे आहेत. मला असे मेमरी प्रोफाइल्स हवे आहेत ज्यासाठी मला कस्टम मेमरी पूलच्या अंतर्गत जीवनचक्र समजून घेण्याची गरज पडणार नाही. या प्रोजेक्टचा वारसा स्वीकारणाऱ्या पुढच्या डेव्हलपरला fasthttp ऑब्जेक्ट मॉडेल शिकण्याशिवाय एका दुपारी कामावर रुजू होता यावे असे मला वाटते. Gin मला ते देते. ऑपरेशनल सॅनिटी (Operational sanity) बेंचमार्कमध्ये दिसून येत नाही, पण तीच सेवा टिकवून ठेवते