अनेक आठवड्यांपासून, Elevare Digital मधील एक cron job वेळेवर सुरू होत असे, त्याची queue तपासत असे आणि यशस्वीरित्या लॉग (log) होत असे. त्याने नेमके शून्य मसुदे (drafts) मंजूर केले. एकोणीस मजकूर (content) वाट पाहत बसले होते. ही शांतता जेव्हा एका विचित्र गोष्टीकडून लहान बॅकलॉगमध्ये (backlog) रूपांतरित झाली, तेव्हा टीमला याची जाणीव झाली. काहीही क्रॅश झाले नव्हते. कोणतेही पेजिंग अलर्ट (paging alerts) आले नव्हते. तांत्रिकदृष्ट्या सिस्टम निरोगी होती, पण कार्यात्मकदृष्ट्या ती मृत होती.

हे स्वायत्त पाइपलाइनचे (autonomous pipelines) शांत भयावह रूप आहे. जेव्हा तुम्ही मानवी हस्तक्षेप (human in the loop) काढून टाकता, तेव्हा तुम्ही अशी व्यक्ती देखील काढून टाकता जी काहीही घडत नाहीये हे लक्षात घेऊ शकेल.

स्वतःहून चालणारी पाइपलाइन

Elevare Digital पूर्णपणे स्वयंचलित कंटेंट वर्कफ्लो चालवते. सॉफ्टवेअर एजंट्स मसुदे (drafts) तयार करतात. एक शेड्युल केलेला 'approver cron' गेटकीपर म्हणून काम करतो, जो त्या मसुद्यांचे पुनरावलोकन करतो आणि मंजूर केलेल्या गोष्टी थेट पब्लिशिंगसाठी पाठवतो. प्रत्येक बॅच मंजूर करण्यासाठी कोणताही माणूस डॅशबोर्ड उघडत नाही. मुख्य उद्देश हाच आहे की, टीम इतर समस्यांवर लक्ष केंद्रित करत असताना मशीन कंटाळवाणा काम (drudgery) हाताळेल.

या मॉडेलमध्ये, 'विश्वास' हा तुमचा प्राथमिक इंटरफेस बनतो. तुम्ही शेड्युलर वेळेवर सुरू होईल यावर विश्वास ठेवता. जॉब रन होईल यावर विश्वास ठेवता. तुम्ही एक्झिट कोडवर (exit code) विश्वास ठेवता. जेव्हा लॉग्समध्ये 200 OK रिस्पॉन्सचा स्थिर 'हार्टबीट' दिसतो, तेव्हा तुम्हाला वाटते की काम सुरू आहे. अनेक आठवड्यांपासून, तो हार्टबीट अगदी अचूक होता. cron प्रत्येक वेळी वेळेवर सुरू होत होता. पण त्याने प्रत्यक्षात कधीच काम केले नाही.

एकोणीस मसुदे आणि कोणताही अलार्म नाही

ही गोष्ट चुकून लक्षात आली. कोणाचे तरी लक्ष गेले की पब्लिशिंग क्यू (publishing queue) शांत झाली आहे, किंवा कदाचित त्यांनी डाउनस्ट्रीम मेट्रिक तपासले आणि तिथे कोणताही बदल न thấy (flatline) दिसून आला. त्यांना एकोणीस मसुद्यांचा साठा सापडला जो पूर्णपणे न हाताळता तसाच पडला होता. अप्रूव्हर (approver) नियमितपणे काम करत होता, दररोज यशस्वीरित्या लॉग करत होता, पण त्याने त्यातील एकही मसुदा प्रोसेस केला नव्हता.

मॅन्युअल वर्कफ्लोमध्ये, मानवी रिव्ह्यूअरने पहिल्याच दिवशी रिकामी इनबॉक्स किंवा प्रलंबित गोष्टींचा ढीग पाहिली असती. स्वयंचलित आवृत्तीमध्ये, हालचालींचा अभाव म्हणजे कामाचा अभाव असाच वाटत होता. त्या cron ला निराश करण्यासाठी कोणताही मॅनेजर नव्हता. तो फक्त वेळेवर कामावर येत होता आणि लवकर घरी जात होता.

दोन बग्स, एक रिकामे निकाल

या अपयशाची दोन कारणे होती. त्यातील कोणतेही सिंटॅक्स एरर (syntax error), टाइमआउट (timeout) किंवा डिपेंडन्सी आउटेज (dependency outage) नव्हते. दोन्ही 'सिमँटिक' (semantic) चुका होत्या, ज्यामुळे क्वेरी इंजिनच्या (query engine) दृष्टीने एकोणीस वैध रो (rows) शून्य झाले.

पहिले, 'टाइप मिसमॅच' (type mismatch). मसुदे तयार करणाऱ्या एजंटने article म्हणून टॅग केलेले रेकॉर्ड्स लिहिले होते. पण अप्रूव्हर cron ने विशेषतः thread प्रकारांसाठी क्वेरी केली होती. जेव्हा प्रोड्युसर्स (producers) आणि कंज्युमर्स (consumers) समांतर मार्गांवर विकसित होतात, तेव्हा अशा प्रकारचा फरक (drift) निर्माण होतो. एका टीमने—किंवा एका एजंटने—आउटपुट 'article' असल्याचे ठरवले. दुसऱ्याने कंज्युमर लिहिताना तो 'threads' स्वीकारेल असे गृहीत धरले. कोणत्याही टाईप सिस्टमने कंपाईल-टाइम एरर दिला नाही कारण हे बहुधा लूज स्ट्रिंग टॅग्स (loose string tags), कदाचित JSON फील्ड्स किंवा अनएन्फोर्स्ड varchar व्हॅल्यूज असाव्यात. डेटाबेसने फक्त कोणतेही मॅचेस शोधले नाहीत आणि एक रिकामी सेट (empty set) परत केला. इंजिनसाठी ही एररची स्थिती नाही. चुकीच्या प्रश्नाचे हे योग्य उत्तर आहे.

दुसरे, अप्रूव्हरच्या क्वेरीमधील 'inner join' ने शांतपणे सर्व रो (rows) गिळंकृत केले. जर क्वेरीने मसुद्यांच्या टेबलला दुसऱ्या टेबलशी जोडले असेल—कदाचित मेटाडेटा, स्टेटस फ्लॅग्स किंवा राउटिंग नियमांसाठी—आणि जॉइन कंडिशन (join condition) अयशस्वी झाली, तर 'inner join' ने अगदी डिझाइनप्रमाणेच वागले. त्याने मॅच न होणारे रो वगळले. रिझल्ट सेटमध्ये कोणतेही 'orphan rows' दिसले नाहीत. कोणत्याही 'nulls' ने समस्या दर्शवली नाही. एकोणीस मसुदे चाळणीतून पाणी गेल्याप्रमाणे क्वेरीमधून निघून गेले आणि ॲप्लिकेशन लेयरला (application layer) एक स्वच्छ, रिकामी यादी मिळाली.

क्वेरीने कोणतेही रो परत न केल्यामुळे, फंक्शन व्यवस्थितपणे संपले. कोणतेही एक्सेप्शन (exceptions) आले नाहीत. HTTP रिस्पॉन्स 200 OK होता. cron ने यश नोंदवले आणि तो पुन्हा झोपला.

'Processed Zero' चा सापळा

हीच या समस्येची मुख्य बाब आहे. क्यू-आधारित (queue-based) सिस्टममध्ये, कंज्युमरला अनेकदा प्रोसेस करण्यासाठी शून्य रो सापडतात. क्यू रिकामी होते. वर्कर काम लवकर संपवतो. लॉगमध्ये processed: 0 असे दिसते आणि टीमला ती चांगली बातमी वाटते: आपण मागणीनुसार काम करत आहोत. ही एक निरोगी स्थिती आहे.

पण processed: 0 मध्ये दोन पूर्णपणे भिन्न वास्तव दडलेले असतात:

  • निरोगी स्थिती (Healthy state): शून्य प्रोसेस झाले कारण काहीही प्रलंबित नाही. क्यू रिकामी आहे. सिस्टम डिझाइननुसार शांत (idle) आहे.
  • बिघडलेली स्थिती (Broken state): शून्य प्रोसेस झाले कारण कंज्युमरला काम दिसत नाहीये. क्यूमध्ये एकोणीस रो आहेत. सिस्टम अंध आहे, शांत नाही.

क्यू डेप्थवर (queue depth) स्वतंत्र तपासणी केल्याशिवाय, या दोन स्थिती एकसारखीच टेलिमेट्री (telemetry) देतात. डॅशबोर्डवर त्या सारख्याच दिसतात, लॉग अ‍ॅग्रिगेटर्समध्ये (log aggregators) त्या सारख्याच वाटतात आणि PagerDuty मध्ये त्या एकसारखीच शांतता निर्माण करतात. तुम्ही अशी मॉनिटरिंग स्ट्रॅटेजी तयार केली आहे जी वर्कर ओरडतो तेव्हा शोधते, पण जेव्हा तो कामाच्या ढिगाऱ्यातून शांतपणे निघून जातो, तेव्हा नाही.

दरी कमी करणे

Elevare Digital ने ते कशावर लक्ष ठेवतात (monitor करतात) यात बदल करून ही समस्या सोडवली. त्यांनी केवळ एरर रेट्स (error rates) आणि सक्सेस स्टेटसवर (success statuses) अवलंबून राहणे थांबवले. त्याऐवजी, उपलब्ध काम आणि पूर्ण झालेले काम यातील तफावतीवर (gap) अलर्ट देणे सुरू केले.

प्रत्येक बॅचनंतर, ते आता एक साधा 'इनव्हेरिएंट चेक' (invariant check) रन करतात:

  • जर processed 0 असेल आणि pending rows 0 पेक्षा जास्त असतील, तर high severity alert ट्रिगर करा.

हा नियम हेतुपुरस्सर कारणाबद्दल तटस्थ (agnostic) आहे. ती चूक एखाद्या चुकीच्या फिल्टरमुळे, तुटलेल्या join मुळे किंवा चुकीच्या पद्धतीने टाईप केलेल्या enum string मुळे झाली आहे का, याने त्याला फरक पडत नाही. काम उपलब्ध आहे पण काहीही काम झाले नाही, या गोष्टीवर त्याचा लक्ष केंद्रित आहे. यामुळे मॉनिटरिंगचे स्वरूप “प्रक्रियेने तक्रार केली का?” कडून “काम पुढे सरकले का?” याकडे बदलते.

याला आधार देण्यासाठी, ते queue depth ला केवळ एक स्पॉट-चेक म्हणून न वापरता, काळाच्या ओघात ट्रॅक केल्या जाणाऱ्या एका महत्त्वाच्या मेट्रिक (first-class metric) प्रमाणे मानतात. जर producer सतत rows जोडत असेल आणि consumer सतत success रिपोर्ट करत असेल, तर depth चा कल (trend) हा मुख्य पुरावा (smoking gun) ठरतो. एक static snapshot कदाचित खोटे बोलू शकतो, पण वाढत जाणारा backlog कधीच खोटे बोलत नाही.

Autonomous Systems साठी धडे

Elevare च्या घटनेतून hands-off pipelines चालवणाऱ्या प्रत्येकासाठी काही व्यावहारिक नियम मिळतात.

Scanned rows हे processed rows पासून वेगळे लॉग करा. Consumer ने अशी query एक्झिक्युट केली असू शकते जी चाळीस rows ला स्पर्श करते, पण चुकीच्या निकषांमुळे त्या सर्वांना फिल्टर करून टाकते आणि processed: 0 रिपोर्ट करते. जर तुम्ही फक्त अंतिम संख्या लॉग केली, तर तुम्ही त्या 'ghost interaction' कडे दुर्लक्ष करता. Scanned-rows मेट्रिक हे दर्शवते की वर्कर तिथे आला, कामाकडे पाहिले आणि गोंधळून निघून गेला. Scanned आणि processed मधील ती तफावत अनेकदा तुमचा सर्वात पहिला संकेत असते.

Queue depth ला time-series म्हणून ट्रॅक करा. तात्पुरती रिकामी असलेली queue ठीक आहे. पण वर्कर 'green' असतानाही queue सातत्याने वाढत असेल, तर ते योग्य नाही. Depth आणि consumer throughput यांचा आलेख काढा. जेव्हा या दोन्ही गोष्टींमध्ये तफावत निर्माण होते, तेव्हा प्रत्येक health check पास होत असला तरीही त्वरित तपासणी करा.

Consumer ची चाचणी केवळ mocks वर न करता, real producer output वर करा. Mocked data सह केलेल्या unit tests मध्ये टेस्टरच्या गृहितकांचा समावेश असतो. जर mock factory thread प्रकार तयार करत असेल आणि consumer thread प्रकारची अपेक्षा करत असेल, तर तुमच्या tests पास होतील पण production मध्ये अपयश येईल. Producer च्या output मधून प्रत्यक्ष रेकॉर्ड्स घेणाऱ्या integration tests रन करा. Producer जे लिहितो ते Consumer ला खरोखर दिसत आहे याची खात्री करा.

Data types आणि enum values ला contracts प्रमाणे माना. JSON blobs मधील सैल string tags सोयीस्कर वाटतात, जोपर्यंत ते अदृश्य failure points बनत नाहीत. Schemas स्पष्टपणे परिभाषित करा. Constants शेअर करा. Producer आणि consumer यांच्यामधील सीम (seam) वर payloads व्हॅलिडेट करा. जर contract मोडले, तर system ने WHERE clause च्या आत शांतपणे न थांबता, बाउंड्रीवर मोठ्याने एरर दाखवून फेल व्हायला हवे.

मुख्य निष्कर्ष

Autonomous systems मानवासारखी फेल होत नाहीत. ते आजारी असल्याचे सांगून सुट्टी घेत नाहीत, प्रत्येक वेळी exceptions थ्रो करत नाहीत किंवा स्पष्ट crash dumps सोडत नाहीत. ते 200 OK रिटर्न करतात आणि inventory कुजवून टाकतात. जर तुमचे alerts फक्त ओरडण्याकडे (screams) लक्ष देत असतील, तर तुम्ही सर्वात महागड्या चुकांकडे दुर्लक्ष कराल—ज्यामध्ये सर्व काही ठीक दिसते पण प्रत्यक्षात काहीही घडत नाही.

तुमची observability ही तफावत (gap) पाहण्यासाठी डिझाइन करा. आत येणाऱ्या कामाची तुलना बाहेर जाणाऱ्या कामाशी करा. जेव्हा या दोन्ही गोष्टी जुळत नाहीत, तेव्हा मशीन तुमच्याशी खोटे बोलत आहे असे समजा. कारण कधीकधी, एक परफेक्ट success log हे पूर्णपणे आंधळी झालेल्या सिस्टमचे एकमेव लक्षण असते.