संक्षेप में
जब कोई Kafka पार्टीशन शांत हो जाता है, तो क्या वह पूरा हो चुका है, या बस धीमा है? बाहर से दोनों एक जैसे दिखते हैं — और इसका जवाब तय करता है कि आप जो डेटा पा चुके हैं उस पर सुरक्षित रूप से कार्रवाई कर सकते हैं या नहीं। Netflix, Uber और Walmart — तीनों ने अपने समाधान प्रकाशित किए हैं, और हर एक कुछ न कुछ छोड़ता है: एक दूसरी पाइपलाइन, समानांतरता की एक लेन, रिप्ले के दौरान शुद्धता। हर बार ठोस इंजीनियरिंग। मगर कोई भी Kafka से वह एक बात नहीं पूछता जो Kafka पहले से जानता है।
मुझे यह समस्या एक Fortune 50 कंपनी की इन्वेंटरी स्ट्रीम पर मिली: तेरह Kafka टॉपिक, हर एक में तीन पार्टीशन, रोज़ाना करोड़ों इवेंट, और हर उत्पाद के सभी इवेंट सख्त इवेंट-टाइम क्रम में चाहिए थे — पहले सात दिनों का इतिहास शून्य से पढ़ना और फिर सीधे लाइव पीक ट्रैफ़िक में उतरना। पहले डिज़ाइन ने सब कुछ के आगे तीस मिनट की विंडो रखी, फिर भी विंडो से छूटे रिकॉर्ड समेटने के लिए एक दूसरी जॉब की ज़रूरत पड़ी, और फिर भी 5.5% रिकॉर्ड गलत क्रम में निकलते रहे।
जो संस्करण अंततः चला, वह घड़ी का इंतज़ार नहीं करता। वह Kafka के ऑफसेट पढ़ता है।
यह कैसे हुआ — और यह विचार Kafka से भी पुराना क्यों है, आगे पढ़िए।
वह सौदा जो हर कोई करता है / §01
यह किसी एक टीम की समस्या नहीं है। चार सशक्त इंजीनियरिंग संगठन, चार ठोस सौदे:
- Walmart हर आइटम–स्टोर जोड़ी को एक ही पार्टीशन से बाँध देता है, ताकि उसे हमेशा एक ही कंज़्यूमर अपडेट करे। कुंजियाँ बाँधकर क्रम बनाए रखना — यह समानांतरता की कीमत चुकाता है।
- Uber देर से और क्रम-भंग इवेंट्स के बाद पुनर्गणना के लिए एक अलग बैकफ़िल पाइपलाइन चलाता है, क्योंकि स्ट्रीमिंग पथ "वॉटरमार्किंग के बाद आने वाले किसी भी इवेंट को गिराकर अशुद्धियाँ पैदा कर सकता है।" यह एक दूसरी पाइपलाइन की कीमत चुकाता है।
- Netflix ने Flink पाइपलाइनों की बैकफ़िलिंग में पाया कि रिप्ले किया गया इतिहास अलग क्रम और बेकाबू स्टेट पैदा करता है। यह शुद्धता और स्टेट की कीमत चुकाता है।
- Confluent का अपना मार्गदर्शन, निष्क्रिय Kafka पार्टीशन के लिए: या तो उसका इंतज़ार करें और ठहर जाएँ, या उसे नज़रअंदाज़ करें और "पार्टीशन के दोबारा सक्रिय होते ही ढेरों विलंबित संदेश" स्वीकार करें। यह ठहराव या विलंबित डेटा की कीमत चुकाता है।
हर एक तर्कसंगत है। साझा सूत्र यह है कि हर कोई पूर्णता का अनुमान समय से लगाता है — एक विंडो, एक टाइमआउट, एक प्रतीक्षा — जबकि पार्टीशन के ऑफसेट ठीक-ठीक बताते हैं कि डेटा कहाँ समाप्त होता है। यही वह खाई है जिसे यह समाधान पाटता है।
एक ही यूनिट के लिए “स्टॉक में” से पहले “बिक गया” पर कार्रवाई नहीं की जा सकती।
स्ट्रीम अस्त-व्यस्त क्यों है / §02
एक Kafka टॉपिक पार्टीशनों में बँटा होता है, और हर पार्टीशन अपना अलग ट्रैक है। पार्टीशन के भीतर क्रम की गारंटी है: पहले रिकॉर्ड एक, फिर रिकॉर्ड ग्यारह, हमेशा। पार्टीशनों के बीच कोई समन्वय नहीं — उन्हें पढ़ने वाले कंज़्यूमर कभी एक ही समय पर एक ही जगह नहीं होते। Kafka ठीक वही करता है जिसका वह वादा करता है, कंज़्यूमर ठीक वही करते हैं जिसका वे वादा करते हैं, और फिर भी संयुक्त आउटपुट उन सभी के लिए अस्त-व्यस्त होता है जिन्हें क्रम की परवाह है।
एक ही यूनिट के लिए "स्टॉक में" से पहले "बिक गया" पर कार्रवाई नहीं की जा सकती। इसलिए कुछ भी रिलीज़ करने से पहले पाइपलाइन को एक सवाल का जवाब देना होता है: क्या इस क्षण तक का सब कुछ मेरे पास आ चुका है? आज प्रचलन में लगभग हर जवाब एक टाइमर है — विंडो, निष्क्रियता टाइमआउट, मिलान जॉब। हर एक मौन के बारे में एक अटकल है।
एक पुराना समाधान: रिपोर्ट करो, इंतज़ार मत करो / §03
इसका एक पूर्वोदाहरण है, और वह कोई बेहतर घड़ी नहीं है। शुरुआती रेलवे समय-अंतराल पद्धति पर चलते थे: एक निश्चित अंतराल बीत जाने पर अगली ट्रेन रवाना हो जाती थी, क्योंकि लाइन "अवश्य" खाली हो चुकी होगी।
जून 1889 में, आयरलैंड के आर्मा के बाहर एक तीव्र ढलान पर एक ट्रेन रुक गई। पिछले डिब्बे अलग कर दिए गए, वे पहाड़ी से नीचे लुढ़के और पीछे आती ट्रेन से टकरा गए। लगभग अस्सी लोगों की जान गई।
पीछे आती ट्रेन आर्मा से तब रवाना हुई जब निर्धारित बीस मिनट बीत चुके थे। बीस मिनट बीत चुके थे, इसलिए लाइन अवश्य खाली होगी। आर्मा में किसी के पास यह जानने का कोई तरीका नहीं था कि ऐसा नहीं है।
ब्रिटेन ने इसका जवाब बेहतर घड़ी से नहीं दिया। संसद ने Board of Trade को यात्री रेलवे पर एब्सोल्यूट ब्लॉक वर्किंग अनिवार्य करने का अधिकार दिया: कोई भी ट्रेन ट्रैक के किसी खंड में तब तक प्रवेश नहीं करेगी जब तक आगे का सिग्नल बॉक्स खंड के खाली होने की पुष्टि न कर दे। क्लियरेंस अब वह अवधि नहीं रही जिसे कोई इंतज़ार करके बिता देता — वह एक तथ्य बन गई जिसकी कोई रिपोर्ट करता है। रुकी हुई ट्रेन और रवाना हो चुकी ट्रेन अब एक जैसी नहीं दिखती थीं, क्योंकि अब कोई मौन से कोई अर्थ नहीं निकाल रहा था।
बस यही पूरी चाल है। Kafka के ऑफसेट पढ़ना ही वह सिग्नल बॉक्स है।
मौन से पूर्णता का अनुमान मत लगाओ। उस सिस्टम से पूछो जो जानता है।
क्लियरेंस एक अवधि है जिसका आप इंतज़ार करते हैं। रुकी हुई ट्रेन और रवाना हो चुकी ट्रेन बिल्कुल एक जैसी दिखती हैं।
क्लियरेंस एक तथ्य है जिसकी कोई रिपोर्ट करता है। अब कोई मौन से कोई अर्थ नहीं निकालता।
निर्माण — तीन हिस्से / §04
सिद्धांत संक्षिप्त है: समय तभी आगे बढ़ता है जब प्रमाण सिद्ध कर दे कि वह बढ़ सकता है। इसे साकार करने में तीन हिस्से लगे, और कठिन हिस्सा वह नहीं था जहाँ लोग उम्मीद करते हैं।
पहला: टाइमर कतार ही सॉर्ट है। हर रिकॉर्ड को उसके अपने इवेंट टाइम के तहत कुंजीबद्ध स्टेट में रखा जाता है, उसी सटीक मिलीसेकंड के लिए एक इवेंट-टाइम टाइमर के साथ। अभी कुछ भी उत्सर्जित नहीं होता। जब वॉटरमार्क उस टाइमस्टैम्प तक पहुँचता है, टाइमर चलता है और वहाँ रखा सब कुछ एक साथ निकलता है। Flink किसी कुंजी के टाइमर आरोही टाइमस्टैम्प क्रम में चलाता है, इसलिए हम कभी कुछ सॉर्ट नहीं करते। यह एक मेथड और एक स्टेट का टुकड़ा है — कठिनाई यहाँ कभी नहीं थी।
उसे संग्रहित करें, फिर उसके अपने इवेंट टाइम के लिए एक ट्रिगर पंजीकृत करें।
घड़ी उस तक पहुँचती है, वह कुंजी पढ़ी जाती है, उसके तहत सब कुछ रिलीज़ होता है।
| इवेंट टाइम | रिकॉर्ड | टाइमर |
|---|---|---|
| 10:00:18 | — | चल चुका, हटाया गया |
| 10:00:26 | B, C | अभी देय |
| 10:00:33 | D | प्रतीक्षारत |
| 10:00:41 | E, F, G | प्रतीक्षारत |
◆ Flink की टाइमर कतार टाइमस्टैम्प के अनुसार क्रमबद्ध है: टाइमर आरोही क्रम में चलते हैं। यही सॉर्ट है।
दूसरा: जिस स्ट्रीम में इवेंट टाइम नहीं है, वही घड़ी बन जाती है। तेरह टॉपिकों में से एक में कोई इवेंट टाइम था ही नहीं — वह केवल वैध उत्पाद प्रकाशित करता था जिनसे बाकी सब कुछ समृद्ध किया जाता था। तो उसके पास सॉर्ट करने को कुछ नहीं था, और वह वो कर सकता था जो बाकी नहीं कर सकते थे: प्रोसेसिंग टाइम से वॉटरमार्क उत्सर्जित करना, हमेशा नवीनतम संभव टाइमस्टैम्प। पेंच यह है: पाइपलाइन की घड़ी उसके इनपुट्स का न्यूनतम होती है, इसलिए "अभी" पर चलती एक स्ट्रीम तब तक कुछ नहीं बदलती जब तक इतिहास वाली स्ट्रीमें गिनती में शामिल हैं। मैंने यह हिस्सा सबसे पहले बनाया और इसका शून्य प्रभाव रहा — यह गलत नहीं था, यह गलत क्रम में था। कंट्रोल स्ट्रीम तभी मायने रखती है जब बाकी न्यूनतम से बाहर निकल जाएँ, और एक स्ट्रीम न्यूनतम से तब बाहर निकलती है जब उसे निष्क्रिय चिह्नित किया जाता है।
तीन चैनल तैयार, शून्य रिलीज़। इसे न्यूनतम ही होना है।
आखिरी बची हुई। घड़ी अभी पर छलाँग लगाती है और शेष डेटा एक ही झोंके में निकल जाता है।
तीसरा: Kafka तय करता है कि स्ट्रीम कब पूरी हुई — टाइमआउट नहीं। Flink की अंतर्निहित निष्क्रियता एक टाइमर है: कुछ समय तक डेटा नहीं, तो निष्क्रिय चिह्नित। लेकिन टाइमआउट "शुरू हो रहा है" और "समाप्त हो चुका" में भेद नहीं कर सकता, और स्टार्टअप सबसे खराब स्थिति है — कनेक्शन अलग-अलग गति से बनते हैं, एक टॉपिक शांत पड़ा रहता है, टाइमआउट चल जाता है, बाकी घड़ी को उन इवेंट टाइम से आगे ले जाते हैं जिन्हें उसने अभी पढ़ा ही नहीं है, और उसका पहला रिकॉर्ड पहुँचते ही विलंबित हो जाता है। यह देरी नहीं, उल्लंघन है। इसलिए टाइमआउट को जाना था। स्टार्टअप पर, कोई रिकॉर्ड पढ़ने से पहले, जॉब Kafka से हर पार्टीशन का अंतिम ऑफसेट पूछती है और उसे संभालकर रखती है। कोई टॉपिक तभी निष्क्रिय है जब हर पार्टीशन उस संख्या तक पहुँच चुका हो — अधिकांश नहीं, हर एक। कोई अवधि ट्यून करने की ज़रूरत नहीं, क्योंकि यह द्वार Kafka का एक तथ्य है।
// once, at job startup
Map<TopicPartition, Long> endOffsets = consumer.endOffsets(partitions);
// as records arrive
if (offset + 1 >= startupEndOffsets.get(partition)) markCaughtUp(partition);समाप्त स्ट्रीम का मौन और धीमी स्ट्रीम का मौन एक ही संकेत हैं। कोई भी सीमा उन्हें अलग नहीं कर सकती।
हर पार्टीशन को उस ऑफसेट तक पहुँचना होगा जो स्टार्टअप पर मौजूद था। एक भी पीछे रहा, तो टॉपिक निष्क्रिय नहीं है।
इससे क्या मिला / §05
विंडो हर रिकॉर्ड को बंद होने तक रोके रखती थी — सिर्फ़ इतिहास पढ़ने में तीस मिनट, हर रीस्टार्ट पर फिर से। अब वॉटरमार्क के पार होते ही रिकॉर्ड रिलीज़ हो जाता है: औसतन 100 मिलीसेकंड से कम, जिसमें वे रिकॉर्ड भी शामिल हैं जिन्हें क्रम-भंग से बचाने के लिए रोका गया था। मिनट एक पर आया रिकॉर्ड मिनट एक पर निकलता है। क्रम का भी यही हाल रहा: टाइमआउट निष्क्रियता की दो परतों ने 5.5% रिकॉर्ड क्रम से बाहर निकाले; एक परत ने 1.5%; Kafka के स्टार्टअप ऑफसेट से तय निष्क्रियता ने शून्य। विंडो गई और स्वीप जॉब भी — एक ही जॉब सात दिनों का इतिहास पढ़ती है और फिर लाइव चलती है।
| पहले | बाद में | |
|---|---|---|
| रिकॉर्ड के क्रम में रिलीज़ होने से पहले की प्रतीक्षा | 30 मिनट तक (विंडो) | औसतन < 100 ms* |
| इवेंट-टाइम क्रम से बाहर निकले रिकॉर्ड | 5.5% (टाइमआउट निष्क्रियता) | 0 |
| चलाने योग्य जॉब | स्ट्रीम + स्वीप | एक |
*लेटेंसी क्लाइंट पर एंड-टू-एंड मापी गई, रोज़ाना करोड़ों इवेंट पर। क्रम की माप इस लेख के लिए पुनर्निर्मित रेफ़रेंस इम्प्लीमेंटेशन पर की गई।
इवेंट-टाइम क्रम से बाहर रिलीज़ हुए रिकॉर्ड। इस लेख के लिए पुनर्निर्मित रेफ़रेंस इम्प्लीमेंटेशन।
यह क्या वादा नहीं करता / §06
नियतात्मक होने का मतलब भविष्यदर्शी होना नहीं है। कोई इवेंट पाइपलाइन के लिए तभी अस्तित्व में आता है जब वह Kafka में पहुँच जाए। अगर कोई प्रोड्यूसर किसी रिकॉर्ड को रोके रखे और वह घड़ी के उसके इवेंट टाइम से आगे निकल जाने के बाद पहुँचे, तो वह तुरंत, क्रम से बाहर, निकल जाता है और उल्लंघन काउंटर उसे दर्ज कर लेता है। कोई भी स्ट्रीम प्रोसेसर उस इवेंट को क्रमबद्ध नहीं कर सकता जिसे वह अभी देख ही नहीं सकता। उस सीमा के भीतर गारंटी सटीक है, और वॉटरमार्क लैग तय करता है कि वह सीमा कितनी चौड़ी है।
कोई प्रोसेसर उस इवेंट को क्रमबद्ध नहीं कर सकता जिसे वह अभी देख ही नहीं सकता। सीमा के भीतर गारंटी सटीक है — वॉटरमार्क लैग तय करता है कि सीमा कितनी चौड़ी है।
वही सवाल, हर जगह / §07
एक बार दिख जाए, तो यह हर उस सिस्टम में दिखता है जो असमकालिक रूप से आने वाली स्ट्रीमों से सच तय करता है।
एक नदी गेज बाढ़ के दौरान शांत हो जाता है। क्या नदी स्थिर है, या पानी सेंसर तक पहुँच गया? 2018 में USGS ने रिपोर्ट किया कि हरिकेन फ़्लोरेंस ने स्ट्रीमगेज ऑफलाइन कर दिए — वे ही जो सबसे अधिक मायने रखते थे। उस मौन से "कोई खबर नहीं" का अर्थ निकालना ग़लत है।
एक कैमरा रिग। किसी एथलीट को कई कैमरों से कैप्चर करें और हर कैमरा अपनी अलग स्ट्रीम है, पार्टीशन की तरह; किसी क्षण का 3D पोज़ तब तक नहीं जोड़ा जा सकता जब तक हर कैमरे ने उसे रिपोर्ट न किया हो। मौन कैमरे ने शायद फ़्रेम गिरा दिया हो, या वह किसी स्थिर खड़े व्यक्ति को देख रहा हो। i3d.ai में यही सवाल है, सेकंड में दर्जनों बार।
एक एजेंट। एक AI एजेंट किसी टूल को कॉल करता है और जवाब में कुछ नहीं सुनता। एजेंट मौन को सफलता मानने में बहुत माहिर हैं — कोई त्रुटि वापस नहीं आई, तो मशीन की गति से अगले कदम पर। किसी ग्राहक के खाते की एक घंटे पुरानी स्थिति पर कार्रवाई करता एजेंट टाइमर पर रवाना की गई ट्रेन है। समाधान वही है जो रेलवे ने खोजा: कोई कदम मौन पर आगे नहीं बढ़ता; हर कदम ऐसी पुष्टि पर आगे बढ़ता है जिसकी ओर वह इशारा कर सके।
क्या नदी स्थिर है — या पानी अभी-अभी सेंसर तक पहुँचा?
एक गिरा हुआ फ़्रेम — या पूर्णतः स्थिर खड़ा एथलीट?
क्या वह सफल रही, विफल रही — या अभी भी चल रही है?
कोई कदम मौन पर आगे नहीं बढ़ता। हर कदम ऐसी पुष्टि पर आगे बढ़ता है जिसकी ओर वह इशारा कर सके।
स्ट्रीमिंग में विश्वसनीयता की सबसे सस्ती जीत तेज़ इंजन या बड़ी विंडो नहीं है। वह है मौन से "पूरा हुआ" का अनुमान लगाना बंद करना और उस सिस्टम से पूछना जो पहले से जानता है। विंडो हर रिकॉर्ड के लिए सबसे खराब स्थिति का इंतज़ार करती है; ऑफसेट जाँच ठीक उतना ही इंतज़ार करती है जितना प्रमाण माँगता है, और एक मिलीसेकंड भी ज़्यादा नहीं।
सरलीकृत रेफ़रेंस इम्प्लीमेंटेशन — जेनरेटर, हैंडलर, कैच-अप ट्रैकर — अगली चीज़ है जिसे मैं प्रकाशित करने जा रहा हूँ।
आपकी पाइपलाइन घड़ी का इंतज़ार कर रही है, या मैनिफेस्ट पढ़ रही है?
जेफ़री पैट्रिक कार्टर · NeoSavant
ऐतिहासिक आधार
Walmart, Uber, Netflix और Confluent के समझौते नीचे दिए इंजीनियरिंग स्रोतों से उद्धृत हैं। एब्सोल्यूट ब्लॉक वर्किंग का पूर्वोदाहरण (Regulation of Railways Act 1889) और वह समय-अंतराल प्रणाली जिसकी जगह इसने ली, सार्वजनिक अभिलेखों से ली गई है। Flink और Kafka की कार्यप्रणालियाँ परियोजनाओं के अपने डिज़ाइन दस्तावेज़ों और स्रोत का हवाला देती हैं। क्रम के आँकड़े प्रोडक्शन जॉब के पुनर्निर्माण पर मापे गए; लेटेंसी का आँकड़ा मूल प्रोडक्शन सिस्टम पर क्लाइंट पर एंड-टू-एंड है।
स्रोत
Uber Engineering — Designing a Production-Ready Kappa Architecture for Timely Data Stream Processing
Netflix — Backfill Streaming Data Pipelines in Kappa Architecture (Data + AI Summit 2022)
Confluent — Why Is My Apache Flink Job Not Producing Results?
Apache Flink — FLIP-180: Adjust StreamStatus and Idleness definition · FLINK-35886: Idleness incorrectly detected · Generating Watermarks (DataStream) · InternalTimerServiceImpl (टाइमर चलने का क्रम)
Apache Kafka — KIP-32: Add timestamps to Kafka message (CreateTime / LogAppendTime)
Wikipedia — Regulation of Railways Act 1889 (एब्सोल्यूट ब्लॉक वर्किंग) · आर्मा रेल दुर्घटना
USGS — Working to keep vital flood information flowing (हरिकेन फ़्लोरेंस, 2018)
Library of Congress — Jack Delano — Indiana Harbor Belt RR switchman with a fusee, 1943 (आइटम 2017878076)
Jeffrey Patrick Carter
Principal Streaming Engineer, NeoSavant
Jeffrey Patrick Carter is a Principal Streaming Engineer at NeoSavant, with 10+ production Flink and Kafka implementations — including Allstate and i3d.ai.
