वर्तमान में परीक्षण चल रहा है: पूरा होने पर GitHub कोड खोला जाएगा।

Ticketing क्या है?

Ticketing एक समर्पित लॉक सर्वर है जो ठीक एक ही काम के लिए डिज़ाइन किया गया है — डिस्ट्रिब्यूटेड लॉकिंग। यह एक ही Rust सर्वर बाइनरी है, जिसके साथ आधिकारिक भाषा-वार क्लाइंट जुड़े हैं, जो सभी बाइट दर बाइट एक जैसा बाइनरी प्रोटोकॉल लागू करते हैं। केवल दो ऑपरेशन — अधिग्रहण (acquire) और रिलीज़ — प्रति-कुंजी FIFO कतार, फेंसिंग टोकन, और एक वैकल्पिक Raft क्लस्टर जिसे आप ज़रूरत पड़ने पर चालू करते हैं। एक डिस्ट्रिब्यूटेड लॉक को जो कुछ चाहिए वह सब, और उसके अलावा कुछ नहीं।

यह स्टोर पर बने लॉक से कैसे अलग है?

प्रोडक्शन में अधिकांश डिस्ट्रिब्यूटेड लॉक किसी की-वैल्यू स्टोर पर निर्भर करते हैं — Redis के ऊपर एक Lua स्क्रिप्ट या Redlock, या ZooKeeper या etcd के सेशन और लीज़ पर बना कोई लॉक। Redis एक बेहतरीन स्टोर है, लेकिन यह डिस्ट्रिब्यूटेड लॉकिंग के लिए पुनर्प्रयोजित (repurposed) एक की-वैल्यू स्टोर है, न कि उद्देश्य-निर्मित (purpose-built) लॉक सिस्टम, और यह संरचनात्मक ओवरहेड के रूप में सामने आता है: एक सामान्य-प्रयोजन प्रोटोकॉल की पार्सिंग लागत, और सामान्य-प्रयोजन डेटा संरचनाओं का ओवरहेड, दोनों साथ आते हैं।

Ticketing को पहले दिन से ही विशेष रूप से डिस्ट्रिब्यूटेड लॉकिंग के लिए डिज़ाइन किया गया है, इसलिए हर परत लॉक के इर्द-गिर्द बनाई गई है।

  • प्रोटोकॉल — फ़िक्स्ड-चौड़ाई वाले बाइनरी फ़ील्ड, न कि टेक्स्ट और न ही कोई सामान्य-प्रयोजन सीरियलाइज़ेशन फ़ॉर्मैट। मान बिना किसी पार्सिंग के सीधे बाइट ऑफ़सेट से पढ़े जाते हैं, और पाइपलाइनिंग आपको पिछली प्रतिक्रिया की प्रतीक्षा किए बिना अगला अनुरोध भेजने देती है।
  • सर्वर आंतरिक भाग — पूरी स्थिति (state) एक प्रति-कुंजी FIFO कतार और एक लीज़ टाइमर मात्र है। अनुरोध पथ हीप पर आवंटन (allocate) तक नहीं करता।
  • संचालन — एक सर्वर बाइनरी (या कंटेनर) शुरू करें, इसे मुट्ठी भर एनवायरनमेंट वेरिएबल से कॉन्फ़िगर करें, और आपका काम पूरा हो गया। न कोई स्कीमा डिज़ाइन, न कोई अलग स्टोर, न ही लॉकिंग से असंबंधित कोई परिचालन ज्ञान आवश्यक है।

परफ़ॉर्मेंस

  • 10MB से कम मेमोरी का उपयोग करते हुए प्रति सेकंड 10 लाख से अधिक ऑपरेशन संभालता है
सबसे व्यापक रूप से उपयोग होने वाले डिस्ट्रिब्यूटेड लॉक सिस्टम Redisson (Redis) के साथ गति तुलना।
Ticketing क्लाइंटRedis (Redisson RLock)
JavaScript
210.2 ms · 190,295 ops/s
210.2 ms
Rust
225.9 ms · 177,069 ops/s
225.9 ms
Go
228.4 ms · 175,131 ops/s
228.4 ms
C#
286.9 ms · 139,421 ops/s
286.9 ms
Kotlin
443.5 ms · 90,192 ops/s
443.5 ms
Java
457.8 ms · 87,374 ops/s
457.8 ms
C/C++
659.7 ms · 60,634 ops/s
659.7 ms
Python
739.4 ms · 54,098 ops/s
739.4 ms
Ruby
1,682.4 ms · 23,776 ops/s
1,682.4 ms
Redisआधार रेखा
4,909.1 ms · 8,148 ops/s
4,909.1 ms
बार की लंबाई पूरी प्रक्रिया में लगे समय (ms) को दर्शाती है — जितना छोटा, उतना तेज़।
32 contended keys × 1,250 ops · concurrency 64 · mac mini m4 2024 basic (10 core), single local server, acknowledged release, 1s min-work budget

Ticketing क्यों

🚦 क्रम अखंडता (ऑर्डरिंग इंटीग्रिटी) — प्रत्यक्ष हैंडऑफ़ के साथ FIFO कतार

पहले आओ, पहले पाओ। जब धारक (holder) रिलीज़ करता है, तो लॉक बिना किसी पुनर्स्पर्धा (re-contention) के सीधे कतार के सबसे आगे वाले को सौंप दिया जाता है। न पोलिंग-और-रिट्राई लॉक स्पर्धा से भुखमरी (starvation) होती है, न ही रिट्राई तूफ़ान।

🛟 मृत क्लाइंट लॉक को फँसाए नहीं रखता — लीज़

यदि लॉक रखने वाली प्रक्रिया (process) मर जाती है या उसका कनेक्शन टूट जाता है, तो अधिग्रहण के समय तय किया गया lease समय बीतने के बाद सर्वर स्वचालित रूप से लॉक वापस ले लेता है और उसे अगले प्रतीक्षक को सौंप देता है। रिलीज़ करना भूल जाने से पूरा सिस्टम रुक नहीं जाता।

🔑 गलत क्रम में काम होने से रोकना — फेंसिंग टोकन

एक विफलता ऐसी है जिसे अकेला लॉक नहीं रोक सकता: एक धारक जो देर से जागता है — यह जाने बिना कि उसका lease पहले ही समाप्त हो चुका है — और फिर भी अपना काम पूरा करने का अनुरोध करता है। Ticketing हर अधिग्रहण के साथ एक क्रमिक रूप से बढ़ने वाला फेंसिंग टोकन जारी करता है। जब तक संरक्षित संसाधन (protected resource) एक ही नियम लागू करता है — "अंतिम देखे गए टोकन से कम किसी भी टोकन को अस्वीकार करें" — तब तक यह आखिरी अंतर भी बंद रहता है। पूरी व्याख्या के लिए यह कैसे काम करता है देखें।

🗳️ Raft पर बना एक रुकावट-रहित क्लस्टर

एक अकेला सर्वर खुद में पर्याप्त है, लेकिन जब आपको शून्य डाउनटाइम चाहिए, तो 3 (या 5) नोड का क्लस्टर बनाएं। हर लॉक स्थिति परिवर्तन तभी अंतिम रूप से लागू होता है जब अधिकांश नोड Raft सर्वसम्मति (consensus) के ज़रिए सहमत होते हैं, इसलिए वही लॉक कभी भी एक साथ दो बार जारी नहीं होता, भले ही लीडर मर जाए या नेटवर्क विभाजित हो जाए। यदि लीडर विफल हो जाता है, तो लगभग एक सेकंड में एक नया लीडर चुना जाता है और क्लाइंट स्वचालित रूप से स्विच हो जाते हैं। जैसे-जैसे नोड एक-एक करके बंद होते हैं — जैसे क्लाउड इंस्टेंस के रीबूट या क्रमिक रखरखाव के दौरान — सेवा तब भी चलती रहती है।

🔒 अंतर्निहित (बिल्ट-इन) प्रमाणीकरण और TLS

हर कनेक्शन, कनेक्ट होने के तुरंत बाद चैलेंज-रिस्पॉन्स टोकन प्रमाणीकरण से गुज़रता है, और टोकन कभी भी वायर पर प्लेनटेक्स्ट में नहीं भेजा जाता। जब भी ज़रूरत हो, पूरे कनेक्शन को TLS से एन्क्रिप्ट करें।

🌐 हर प्रमुख भाषा में, बाइट दर बाइट, वही प्रोटोकॉल

Rust, Java/Kotlin, JavaScript/TypeScript, Python, C#, Go, Ruby, C/C++ — हर आधिकारिक क्लाइंट वही बाइनरी प्रोटोकॉल लागू करता है, एक ऐसे API के साथ जो हर भाषा के इकोसिस्टम के लिए स्वाभाविक लगे (async या ब्लॉकिंग)। किसी भी भाषा से अधिग्रहीत लॉक, किसी भी अन्य भाषा में लिखे क्लाइंट के साथ निष्पक्ष रूप से कतार में लगता है। इंस्टॉल निर्देशों और उदाहरणों के लिए लाइब्रेरी देखें।

केवल दो ऑपरेशन

एक लॉक-ओनली सर्वर के अनुरूप, इसका सतह क्षेत्र (surface area) भी न्यूनतम है।

  1. जिस संसाधन की आप सुरक्षा करना चाहते हैं उससे एक कुंजी जोड़ें — जैसे order-1234, account-77, daily-batch
  2. काम करने से पहले उस कुंजी को अधिग्रहित (acquire) करें — यदि कोई और उसे पकड़े हुए है, तो आप FIFO कतार में अपनी बारी की प्रतीक्षा करते हैं।
  3. काम पूरा होने पर उसे रिलीज़ करें — अगला प्रतीक्षक बिना किसी पुनर्स्पर्धा के तुरंत काम संभाल लेता है।

इन दो ऑपरेशनों के ऊपर केवल यही विकल्प हैं lease (स्वचालित पुनर्ग्रहण) और waitTimeout (प्रतीक्षा छोड़ना) — यही पूरा API है।

आगे क्या पढ़ें