इवेंट सोर्सिंग (Event sourcing) आपसे अपने डेटा को ओवरराइट करना बंद करने के लिए कहती है। एक पारंपरिक CRUD एप्लिकेशन में, किसी उपयोगकर्ता के शिपिंग पते को अपडेट करने का अर्थ है उस रो (row) को खोजना, मान (value) बदलना और पिछले स्टेट (state) को हटा देना। इवेंट सोर्सिंग एक अलग रास्ता अपनाती है। यह प्रत्येक परिवर्तन को एक अपरिवर्तनीय तथ्य (immutable fact) के रूप में संग्रहीत करती है: जैसे कि एक उपयोगकर्ता ने खाता बनाया, अपना पता अपडेट किया, या अपना ईमेल सत्यापित किया। सिस्टम की वर्तमान स्थिति सीधे संग्रहीत नहीं की जाती है। इसे इन इवेंट्स को क्रम से फिर से चलाकर (replaying) निकाला जाता है।
यह पैटर्न वास्तविक समस्याओं का समाधान करता है। ऑडिट ट्रेल्स (audit trails) इसके मुफ्त उप-उत्पाद (byproducts) बन जाते हैं। आप किसी भी पिछले क्षण में एक ऑर्डर की स्थिति को फिर से बना सकते हैं। आप ठीक वही चीज़ फिर से चलाकर डिबग कर सकते हैं जो हुई थी। इसका ट्रेड-ऑफ (trade-off) जटिलता है। अब आप साधारण रो (rows) के बजाय तथ्यों के स्ट्रीम (streams of facts), रीड मॉडल्स (read models) और इवेंचुअल कंसिस्टेंसी (eventual consistency) को मैनेज करते हैं।
PostgreSQL आपके इवेंट स्टोर के रूप में कार्य कर सकता है। अधिकांश टीमें पहले से ही इसका उपयोग कर रही हैं। यह ACID ट्रांजेक्शन, लचीले पेलोड (payloads) के लिए JSONB और प्रमाणित बैकअप टूल प्रदान करता है। आपको पहले दिन से ही Kafka, Cassandra, या किसी विशेष इवेंट-स्टोर डेटाबेस को शामिल करने की आवश्यकता नहीं है। एक मानक Postgres इंस्टेंस आपको अपने इंफ्रास्ट्रक्चर फुटप्रिंट को बढ़ाए बिना वे ट्रांजेक्शनल गारंटी और ऑडिट ट्रेल्स प्रदान करता है जिनकी इवेंट सोर्सिंग मांग करती है।
एक Postgres इवेंट स्टोर का स्वरूप
स्कीमा लगभग आश्चर्यजनक रूप से सरल हो सकता है। कम से कम, आपको एक ऐसी टेबल की आवश्यकता है जो इवेंट्स को जोड़ती (append) है और उन्हें कभी भी इन-प्लेस अपडेट नहीं करती है। एक व्यावहारिक डिज़ाइन ऐसा दिखता है:
idएक bigserial या UUID के रूप में, जो ग्लोबल ऑर्डरिंग का काम करता है।stream_idसंबंधित इवेंट्स को समूहबद्ध करने के लिए, जैसे कि किसी एक उपयोगकर्ता या ऑर्डर के लिए सभी परिवर्तन।event_typeसादे टेक्स्ट के रूप में:UserEmailChanged,PaymentReceived,InventoryAdjusted।payloadJSONB के रूप में, जो उस घटना के लिए विशिष्ट डेटा रखता है।occurred_atटाइमज़ोन सटीकता के साथ।versionप्रत्येक स्ट्रीम के लिए, जो ऑप्टिमिस्टिक कन्करेंसी (optimistic concurrency) को लागू करता है।
आप एप्लिकेशन कोड में या डेटाबेस कंस्ट्रेंट के साथ अपरिवर्तनीयता (immutability) के नियम को लागू करते हैं। (stream_id, version) पर एक यूनिक इंडेक्स दो राइटर्स को एक ही सीक्वेंस नंबर जोड़ने से रोकता है। जब कोई कमांड आती है, तो आप उस स्ट्रीम के लिए वर्तमान वर्शन पढ़ते हैं, उसे बढ़ाते हैं, और एक ट्रांजेक्शन के भीतर नया इवेंट डालते हैं। यदि कोई अन्य प्रक्रिया आपसे पहले इसे कर लेती है, तो यूनिक कंस्ट्रेंट विफल हो जाता है, और आप कमांड को फिर से प्रयास करते हैं या अस्वीकार कर देते हैं।
एक ठोस उदाहरण पर विचार करें। आप एक इन्वेंट्री सिस्टम चलाते हैं। quantity कॉलम वाली एक एकल inventory रो के बजाय, आप inventory_events टेबल में इवेंट्स जोड़ते हैं। ItemReceived दस यूनिट जोड़ता है। ItemReserved दो हटाता है। ItemShipped तीन हटाता है। SKU-42 के लिए वर्तमान स्टॉक जानने के लिए, आप संबंधित इवेंट पेलोड का योग करते हैं। तीन दिन पहले का स्टॉक जानने के लिए, आप केवल उस टाइमस्टैम्प तक का योग करते हैं। यदि पिछले मंगलवार को आपके शिपिंग लॉजिक में किसी बग के कारण त्रुटि हुई थी, तो आप वास्तविक स्थिति प्राप्त करने के लिए सुधारे गए कोड के माध्यम से इवेंट्स को फिर से चला सकते हैं। आप एक साधारण UPDATE स्टेटमेंट के साथ ऐसा नहीं कर सकते।
वे सिद्धांत जो आपको परेशानी से बचाते हैं
Postgres पर निर्माण करने का अर्थ अनुशासन की आवश्यकता को समाप्त करना नहीं है। निम्नलिखित सिद्धांत सीधे इवेंट-सोर्स्ड सिस्टम पर लागू होते हैं।
इसे सरल रखें। जटिलता विश्वसनीयता को खत्म कर देती है। एक भी वर्किंग फ्लो (working flow) शिप करने से पहले एक जेनेरिक इवेंट फ्रेमवर्क बनाने की इच्छा से बचें। वैल्यू साबित करने के लिए एक सिंगल टेबल, इवेंट्स जोड़ने के लिए एक रिपॉजिटरी फंक्शन और रीड मॉडल्स बनाने के लिए एक प्रोजेक्शन वर्कर ही काफी है। टूल्स तभी जोड़ें जब कोई ठोस समस्या सामने आए।
छोटी शुरुआत करें। अपने पूरे मोनोलिथ (monolith) को फिर से न लिखें। एक ऐसा बाउंडेड कॉन्टेक्स्ट (bounded context) चुनें जहाँ ऑडिट ट्रेल ओवरहेड की भरपाई कर दे। बिलिंग लेजर, वर्कफ़्लो इंजन, या इन्वेंट्री रिजर्वेशन सिस्टम अच्छे विकल्प हैं। उस एक पाइपलाइन को एंड-टू-एंड बनाएं। इसे प्रोडक्शन में चलने दें। फिर तय करें कि विस्तार करना है या नहीं।
पहले सफलता को परिभाषित करें। इवेंट सोर्सिंग कोई डिफॉल्ट आर्किटेक्चर नहीं है; यह विशिष्ट आवश्यकताओं का एक समाधान है। यदि आपकी आवश्यकता केवल नवीनतम स्थिति को ट्रैक करने की है, तो CRUD तेज़ और सस्ता है। यदि आपको टेम्पोरल क्वेरीज़ (temporal queries), सख्त ऑडिटेबिलिटी, या मांग पर रीड मॉडल्स को फिर से बनाने की क्षमता की आवश्यकता है, तो इवेंट्स का उपयोग करना समझदारी है। प्रतिबद्ध होने से पहले जानें कि आप किस समस्या का समाधान कर रहे हैं।
ऑप्टिमाइज़ करने से पहले मापें। साधारण हार्डवेयर पर आधुनिक PostgreSQL एक साधारण अपेंड-ओनली (append-only) टेबल के साथ प्रति सेकंड हजारों इवेंट्स को ग्रहण कर सकता है। अपने इवेंट स्टोर को शार्ड (shard) न करें या जटिल पार्टिशनिंग स्कीम्स तब तक पेश न करें जब तक कि आपका मॉनिटरिंग यह साबित न कर दे कि आपने सरल समाधानों को आज़मा लिया है। उन फ़ील्ड्स को इंडेक्स करें जिन्हें आप क्वेरी करते हैं। अपेंड-ओनली वर्कलोड के लिए autovacuum को ट्यून करें। फिर से मापें।
सब कुछ टेस्ट करें। अपने इवेंट हैंडलर्स का यूनिट टेस्ट करें। अपेंड पाथ (append path) का इंटीग्रेशन टेस्ट करें। सबसे महत्वपूर्ण बात, फेलियर सिनेरियो (failure scenarios) का टेस्ट करें। क्या होता है जब दो नोड्स एक ही समय में एक ही स्ट्रीम में डेटा अपेंड करते हैं? क्या होता है जब एक प्रोजेक्शन वर्कर बैच के बीच में क्रैश हो जाता है? ऐसे टेस्ट लिखें जो आपकी ऑप्टिमिस्टिक कन्करेंसी (optimistic concurrency) और आपकी 'एट-लीस्ट-वन्स' (at-least-once) डिलीवरी गारंटी को सत्यापित करें।
प्रोडक्शन में मॉनिटर करें। इवेंट्स टेबल बढ़ती जाएगी। एक नॉर्मलाइज्ड स्कीमा के विपरीत, जहाँ अपडेट्स रो काउंट को स्थिर रखते हैं, इवेंट सोर्सिंग जानबूझकर एडिटिव (additive) होती है। टेबल का साइज़, डिस्क I/O, और अपने राइट मॉडल और रीड मॉडल प्रोजेक्शन के बीच के लैग (lag) को ट्रैक करें। अपने यूजर्स के पुराना डेटा (stale data) नोटिस करने से पहले ही प्रोजेक्शन लैग पर अलर्ट सेट करें।
मैनुअल कार्यों को ऑटोमेट करें। मैनुअल स्कीमा बदलाव, मैनुअल प्रोजेक्शन रीबिल्ड और मैनुअल इवेंट रिप्ले टिक-टिक करते टाइम बम हैं। अपनी माइग्रेशन रणनीति को स्क्रिप्ट करें। यदि आप किसी इवेंट स्कीमा को विकसित करते हैं, तो अपकास्टिंग (upcasting) या ट्रांसफॉर्मेशन को ऑटोमेट करें ताकि पुराने इवेंट्स को आधी रात को मानवीय हस्तक्षेप के बिना नए लॉजिक के माध्यम से रिप्ले किया जा सके।
अपने विकल्पों का दस्तावेजीकरण करें। लिखें कि विशिष्ट स्ट्रीम्स क्यों मौजूद हैं, प्रत्येक इवेंट टाइप का क्या अर्थ है, और टीम को इवेंट्स के बजाय CRUD कब चुनना चाहिए। इवेंट सोर्सिंग कॉग्निटिव लोड (cognitive load) बढ़ाती है। अच्छा दस्तावेजीकरण एक नए इंजीनियर को गलत अनुमान लगाने और किसी महत्वपूर्ण स्ट्रीम में गलत तरीके से बने (malformed) इवेंट्स को अपेंड करने से रोकता है।
वे जाल जो महीनों बर्बाद कर देते हैं
इवेंट सोर्सिंग डायग्राम में सुनने में बहुत शानदार लगती है, लेकिन प्रोडक्शन में यह काफी कष्टदायक हो सकती है। इन जालों से सावधान रहें।
जटिलता को कम आंकना। स्टेट को फिर से बनाने के लिए इवेंट्स को रिप्ले करना वैचारिक रूप से सरल है। लेकिन आइडम्पोटेंसी (idempotency) को मैनेज करना, परफॉरमेंस के लिए स्नैपशॉटिंग करना और एग्रीगेट्स के बीच कंपेंसेटिंग ट्रांजेक्शन (compensating transactions) संभालना सरल नहीं है। अपने सिस्टम को छोटे टुकड़ों में बाँटें। एक बार में एक स्ट्रीम हल करें।
ओवर-इंजीनियरिंग। केवल इसलिए मल्टी-नोड Kafka क्लस्टर न बनाएँ क्योंकि आपको लगता है कि एक दिन आपके इवेंट वॉल्यूम को इसकी आवश्यकता होगी। Postgres आपको आश्चर्यजनक रूप से काफी आगे तक ले जा सकता है। नया इंफ्रास्ट्रक्चर तभी लाएँ जब आपके पास कोई मापा हुआ बॉटलनेक (bottleneck) हो जिसे आप अपने वर्तमान सेटअप के भीतर ठीक नहीं कर सकते।
टेक्निकल डेब्ट (technical debt) को नज़रअंदाज़ करना। पुराने इवेंट स्कीमा हमेशा बने रहते हैं। यदि आप अपने OrderCreated पेलोड को बदलते हैं, तो आपके पास अभी भी पुराने स्वरूप में दस मिलियन ऐतिहासिक इवेंट्स होंगे। इस कर्ज को ट्रैक करें। बैकवर्ड-कंपैटिबल रीडर्स या माइग्रेशन स्क्रिप्ट की योजना बनाएं। लेगेसी इवेंट्स के बोझ को हर नए फीचर की गति धीमी न करने दें।
ऐसे टूल्स चुनना जिन्हें टीम चला न सके। सबसे अच्छा आर्किटेक्चर भी विफल हो जाता है यदि उसे केवल एक व्यक्ति समझता है। यदि आपकी टीम Postgres और SQL जानती है, तो वहीं से शुरुआत करें। यदि आप एक विशेष इवेंट स्टोर लाते हैं, तो सुनिश्चित करें कि आपके पास रात के दो बजे उसे डीबग करने की ऑपरेशनल विशेषज्ञता हो।
एक व्यावहारिक शुरुआती बिंदु
यदि यह दृष्टिकोण आपकी समस्या के लिए उपयुक्त है, तो पांच-तिमाही रीराइट (rewrite) का इंतज़ार न करें। इसी सप्ताह से शुरू करें।
अपने वर्तमान सिस्टम का ऑडिट करें। ऐसी जगह खोजें जहाँ ऑडिट ट्रेल (audit trail) वास्तविक समस्या का समाधान कर सके। शायद यह एक ऑर्डर स्टेट मशीन हो जो वर्तमान में केवल एक status कॉलम बनाए रखती है। शायद यह एक वित्तीय लेजर हो जहाँ बैलेंस सुधार के लिए मैनुअल डेटाबेस पैच की आवश्यकता होती है। एक ऐसा गैप चुनें जहाँ स्टेट को ओवरराइट करने से आपको नुकसान हुआ हो।
फिर एक छोटा सुधार चुनें जो आप आज कर सकते हैं। एक इवेंट टेबल बनाएँ। एक स्ट्रीम को मॉडल करें। एक प्रोजेक्शन लिखें जो उन इवेंट्स से रीड मॉडल बनाता है। इसे एक फीचर फ्लैग (feature flag) के पीछे तैनात करें। इसे वास्तविक ट्रैफिक संभालते हुए देखें।
PostgreSQL के साथ इवेंट सोर्सिंग कोई जादू नहीं है। यह उन टीमों के लिए एक व्यावहारिक उपकरण है जिन्हें न केवल यह जानने की आवश्यकता है कि चीजें कहाँ हैं, बल्कि यह भी कि वे वहाँ कैसे पहुँचीं। धीरे-धीरे निर्माण करें, ईमानदारी से मापें, और अपनी वास्तविक आवश्यकताओं को आर्किटेक्चर का मार्गदर्शन करने दें।
