Rust A/B टेस्ट के लिए क्यों परफेक्ट है?
\nजब हम A/B टेस्टिंग की बात करते हैं, तो आमतौर पर Python, R या JavaScript का ज़िक्र होता है। लेकिन Rust एक डार्क हॉर्स है जो रियल प्रोडक्शन में तेज़, सुरक्षित और सस्ता साबित होता है। अगर आपका बैकएंड Rust में लिखा है, तो आप माइक्रोसर्विस या Redis के ओवरहेड के बिना सीधे सिस्टम कोर में एक्सपेरिमेंट एम्बेड कर सकते हैं।
\nसोचिए: आप रेकमेंडेशन एल्गोरिदम, "खरीदें" बटन का रंग या पार्सिंग लॉजिक बदल रहे हैं। Python में A/B टेस्ट कोड की एक गलती सर्वर को क्रैश कर सकती है। Rust में — अगर आप कोई कंडीशन ब्रांच मिस करते हैं, तो कोड कंपाइल ही नहीं होगा। कंपाइलर आपका सबसे अच्छा QA है।
\nइस आर्टिकल में हम Rust में A/B टेस्टिंग को आर्किटेक्चर से लेकर प्रोडक्शन तक कवर करेंगे। आप सीखेंगे कि बिना दर्द के स्प्लिट-टेस्ट कैसे करें, सांख्यिकी कैसे कैलकुलेट करें और यूज़र्स को कैसे न खोएं।
\n\nRust में A/B टेस्ट की आर्किटेक्चर: तीन स्तंभ
\nRust एप्लिकेशन में कोई भी एक्सपेरिमेंट तीन घटकों पर टिका होता है:
\n- \n
- रैंडमाइज़र — कौन ग्रुप A में जाएगा और कौन ग्रुप B में? \n
- असाइनमेंट स्टोरेज — ताकि यूज़र हर रिक्वेस्ट पर ग्रुप न बदले। \n
- लॉगिंग और सांख्यिकी — कन्वर्ज़न और p-value की गणना करें। \n
चलिए एक सरल लेकिन काम करने वाला मॉड्यूल लिखते हैं। कोई फ्रेमवर्क नहीं — सिर्फ Rust और इसकी स्टैंडर्ड लाइब्रेरी।
\n\nचरण 1. हैश-आधारित रैंडमाइज़ेशन
\nडिटरमिनिस्टिक रैंडमाइज़ेशन स्थिरता की कुंजी है। अगर आप rand::random() का उपयोग करते हैं, तो हर सर्वर रीस्टार्ट पर यूज़र दूसरे ग्रुप में जा सकता है। इसके बजाय, वेटेड हैश का उपयोग करें जो यूज़र ID और एक्सपेरिमेंट नाम पर आधारित हो।
use std::collections::hash_map::DefaultHasher;\nuse std::hash::{Hash, Hasher};\n\n#[derive(Debug, Clone, Copy, PartialEq)]\npub enum Group {\n Control,\n Treatment,\n}\n\npub fn assign_group(user_id: &str, experiment: &str, treatment_weight: f64) -> Group {\n let mut hasher = DefaultHasher::new();\n user_id.hash(&mut hasher);\n experiment.hash(&mut hasher);\n let hash = hasher.finish();\n\n let normalized = (hash as f64) / (u64::MAX as f64);\n if normalized < treatment_weight {\n Group::Treatment\n } else {\n Group::Control\n }\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn test_deterministic() {\n let g1 = assign_group("user_42", "button_color_v2", 0.5);\n let g2 = assign_group("user_42", "button_color_v2", 0.5);\n assert_eq!(g1, g2);\n }\n}\n\n\nयह कोड गारंटी देता है कि एक ही यूज़र हमेशा एक ही एक्सपेरिमेंट के लिए एक ही ग्रुप में रहेगा। treatment_weight वह ट्रैफ़िक प्रतिशत है जो टेस्ट ग्रुप में जाएगा (जैसे, 0.1 = 10%)।
चरण 2. स्टोरेज: कैश या डेटाबेस?
\nहाई-लोड प्रोजेक्ट्स के लिए, मेमोरी में असाइनमेंट स्टोर करना जोखिम भरा है। अगर सर्वर क्रैश होता है, तो यूज़र अपना ग्रुप खो देगा। Redis या SQLite का उपयोग करना बेहतर है। लेकिन सरलता के लिए, हम TTL के साथ in-memory कैश दिखा रहे हैं।
\n\nuse std::collections::HashMap;\nuse std::time::{Duration, Instant};\n\nstruct AssignmentCache {\n store: HashMap<String, (Group, Instant)>,\n ttl: Duration,\n}\n\nimpl AssignmentCache {\n fn new(ttl_secs: u64) -> Self {\n Self {\n store: HashMap::new(),\n ttl: Duration::from_secs(ttl_secs),\n }\n }\n\n fn get(&mut self, key: &str) -> Option<Group> {\n if let Some((group, time)) = self.store.get(key) {\n if time.elapsed() < self.ttl {\n return Some(*group);\n }\n }\n None\n }\n\n fn set(&mut self, key: String, group: Group) {\n self.store.insert(key, (group, Instant::now()));\n }\n}\n\n\nरियल प्रोजेक्ट में, HashMap को dashmap या Redis से बदलें — इससे थ्रेड-सेफ्टी और डेटा स्थायित्व मिलेगा।