समझें कि ACID गारंटियाँ डेटाबेस डिज़ाइन और ऐप व्यवहार को कैसे प्रभावित करती हैं—atomicity, consistency, isolation, durability, trade-offs और व्यावहारिक उदाहरणों के साथ।

जब आप किराने का भुगतान करते हैं, फ्लाइट बुक करते हैं, या खातों के बीच पैसा स्थानांतरित करते हैं, तो आप परिणाम को स्पष्ट उम्मीद करते हैं: या तो यह सफल हुआ, या नहीं। डेटाबेस भी वही सुनिश्चित करने की कोशिश करते हैं—भले ही बहुत से लोग सिस्टम एक साथ इस्तेमाल कर रहे हों, सर्वर क्रैश हों, या नेटवर्क हिचकियाँ हो।
एक ट्रांज़ैक्शन एक एकल कार्य-इकाई होती है जिसे डेटाबेस एक "पैकेज" की तरह मानता है। इसमें कई चरण हो सकते हैं—इन्वेंट्री घटाना, ऑर्डर रिकॉर्ड बनाना, कार्ड चार्ज करना, और रसीद लिखना—लेकिन इसे एक सुसंगत क्रिया की तरह व्यवहार करना चाहिए।
अगर कोई भी चरण फेल हो, तो सिस्टम को आधे-अधूरे हालात छोड़ने के बजाय सुरक्षित बिंदु पर वापस जाने की जरूरत है।
आंशिक अपडेट सिर्फ तकनीकी गड़बड़ियाँ नहीं हैं; वे कस्टमर सपोर्ट टिकट और वित्तीय जोखिम बन जाते हैं। उदाहरण:
ये फ़ेल्योर डिबग करना मुश्किल होते हैं क्योंकि सब कुछ "ज्यादातर सही" दिखता है, फिर भी संख्याएँ मेल नहीं खातीं।
ACID चार गारंटी का संक्षेप है जो कई डेटाबेस ट्रांज़ैक्शन्स के लिए दे सकते हैं:
यह कोई निश्चित डेटाबेस ब्रांड या एक फीचर नहीं है जिसे आप बस ऑन कर दें; यह व्यवहार के बारे में एक वादा है।
मजबूत गारंटियाँ आमतौर पर डेटाबेस को अधिक काम करवाती हैं: अतिरिक्त समन्वय, लॉक के लिए प्रतीक्षा, वर्ज़न ट्रैकिंग, और लॉग पर लिखना। इससे भारी लोड पर थ्रूपुट कम या लेटेंसी बढ़ सकती है। लक्ष्य यह नहीं है कि "हर समय अधिकतम ACID" मिले, बल्कि ऐसी गारंटी चुनना है जो आपके वास्तविक बिजनेस जोखिमों से मेल खाएं।
Atomicity का मतलब है कि एक ट्रांज़ैक्शन को एक एकल कार्य-इकाई की तरह माना जाए: या तो यह पूरी तरह पूरा होता है या इसका कोई प्रभाव नहीं होता। डेटाबेस में कभी भी "आधा अपडेट" दिखाई नहीं देता।
मान लीजिए ऐलिस से बॉब को $50 ट्रांसफ़र कर रहे हैं। अन्दरुनी तौर पर, यह आमतौर पर कम से कम दो बदलाव करता है:
Atomicity के साथ, ये दोनों बदलाव साथ में सफल होंगे या साथ में विफल। अगर सिस्टम दोनों सुरक्षित रूप से नहीं कर सकता, तो उसे दोनों नहीं करना चाहिए। इससे वह दुःस्वप्न टेली-आउटकम रोका जाता है जहाँ ऐलिस से चार्ज हुआ पर बॉब को पैसे नहीं मिले (या बॉब को मिला पर ऐलिस से चार्ज नहीं हुआ)।
डेटाबेस ट्रांज़ैक्शन्स को दो निकास देते हैं:
एक उपयोगी मानसिक मॉडल है "ड्राफ्ट बनाम पब्लिश"। जब तक ट्रांज़ैक्शन चल रहा होता है, बदलाव अस्थायी होते हैं। केवल एक commit उन्हें प्रकाशित करता है।
Atomicity मायने रखती है क्योंकि फेल्योर सामान्य हैं:
अगर इनमें से कोई भी commit से पहले होता है, तो atomicity यह सुनिश्चित करती है कि डेटाबेस rollback कर सके ताकि आंशिक काम स्थायी स्थिति में न आ जाए।
Atomicity डेटाबेस स्थिति की रक्षा करता है, पर आपका एप अभी भी अनिश्चितता को संभालना चाहिए—विशेषकर जब नेटवर्क ड्रॉप से पता न चले कि commit हुआ या नहीं।
दो प्रायोगिक पूरक:
साथ मिलकर, atomic ट्रांज़ैक्शन और idempotent retries आपको आंशिक अपडेट और आकस्मिक डबल-चार्ज से बचने में मदद करते हैं।
ACID में consistency का अर्थ यह नहीं है कि "डेटा ठीक दिखता है" या "सभी रेप्लिका मेल खाते हैं"। इसका मतलब है कि हर ट्रांज़ैक्शन डेटाबेस को आपके द्वारा परिभाषित नियमों के अनुसार एक वैध स्थिति से दूसरी वैध स्थिति में ले जाए।
डेटाबेस केवल उन्हीं constraints, triggers और invariants के सापेक्ष सुसंगत रख सकता है जो आपने परिभाषित किए हैं—ACID ये नियम नहीं बनाता; यह उन्हें ट्रांज़ैक्शन के दौरान लागू करता है।
सामान्य उदाहरण:
order.customer_id मौजूदा ग्राहक की ओर पॉइंट करे।अगर ये नियम मौजूद हैं, तो डेटाबेस किसी भी ऐसे ट्रांज़ैक्शन को अस्वीकार कर देगा जो उन्हें तोड़ता हो—इसलिए आपको "आधा-वैध" डेटा नहीं मिलेगा।
ऐप-स्तरीय वैलिडेशन महत्वपूर्ण है, पर केवल वही पर्याप्त नहीं है।
एक क्लासिक विफलता मोड है: ऐप में चेक करना ("ईमेल उपलब्ध है") और फिर रो शामिल करना। समकालिकता के अंतर्गत, दो रिक्वेस्ट एक ही समय पर चेक पास कर सकती हैं। डेटाबेस में unique constraint ही गारंटी देती है कि केवल एक insert सफल होगा।
अगर आप "नो नेगेटिव बैलेंस" को constraint के रूप में एनकोड करते हैं (या इसे एक ही ट्रांज़ैक्शन में विश्वसनीय रूप से लागू करते हैं), तो कोई भी ऐसा ट्रांज़ैक्शन जो खाते को ओवरड्रॉ करेगा, पूरे रूप में विफल होना चाहिए। अगर आपने उस नियम को कहीं परिभाषित नहीं किया है, तो ACID उसे नहीं बचा पाएगा—क्योंकि लागू करने के लिए कुछ भी नहीं है।
Consistency अंततः स्पष्ट होने का मामला है: नियम परिभाषित करें, फिर ट्रांज़ैक्शन सुनिश्चित करें कि वे कभी न टूटें।
Isolation सुनिश्चित करती है कि ट्रांज़ैक्शन्स एक दूसरे पर कदम न रखें। जब एक ट्रांज़ैक्शन चल रहा हो, तो अन्य ट्रांज़ैक्शन्स आधे-निष्पन्न काम को न देखें या आकस्मिक रूप से उसे ओवरराइट न करें। लक्ष्य सरल है: हर ट्रांज़ैक्शन को ऐसा व्यवहार करना चाहिए मानो वह अकेले चल रहा हो, भले ही कई उपयोगकर्ता एक साथ सक्रिय हों।
असली सिस्टम व्यस्त होते हैं: ग्राहक ऑर्डर देते हैं, सपोर्ट एजेंट प्रोफाइल अपडेट करते हैं, बैकग्राउंड जॉब्स पेमेंट reconcile करते हैं—सब एक साथ। ये क्रियाएँ समय में ओवरलैप करती हैं, और अक्सर एक ही पंक्तियों को छूती हैं (खाता बैलेंस, इन्वेंटरी काउंट, या बुकिंग स्लॉट)।
Isolation के बिना, टाइमिंग आपकी बिजनेस लॉजिक का हिस्सा बन जाती है। "स्टॉक घटाओ" अपडेट किसी दूसरे चेकआउट के साथ रेस कर सकता है, या रिपोर्ट मध्यान्तर में डेटा पढ़कर ऐसी संख्याएँ दिखा सकती है जो कभी स्थिर स्थिति में मौजूद ही नहीं थीं।
पूरी "अकेले चलो" जैसी isolation महँगी हो सकती है। यह थ्रूपुट कम कर सकती है, प्रतीक्षा (लॉक्स) बढ़ा सकती है, या ट्रांज़ैक्शन retries का कारण बन सकती है। वहीं, कई वर्कफ़्लो सबसे सख्त सुरक्षा की आवश्यकता नहीं रखते—उदा. कल के analytics पढ़ना मामूली असंगतियों को सहन कर सकता है।
इसीलिए डेटाबेस कॉन्फ़िगरेबल isolation levels देते हैं: आप चुनते हैं कि आप प्रदर्शन और संघर्ष में किस हद तक जोखिम स्वीकार करेंगे।
जब isolation आपके वर्कलोड के लिए बहुत कमजोर होता है, तो आप क्लासिक अनोमलियों से मिलेंगे:
इन फेल्योर मोड्स को समझने से सही isolation level चुनना आसान होता है जो आपके प्रोडक्ट के वादों से मेल खाए।
Isolation निर्धारित करती है कि जब आपका ट्रांज़ैक्शन चल रहा हो तो आप किन दूसरों को देख सकते हैं। कमजोर isolation होने पर आप अनोमलियाँ देख सकते हैं—तकनीकी रूप से संभव पर उपयोगकर्ताओं के लिए चौंकाने वाली व्यवहार:
Dirty read तब होता है जब आप किसी ऐसे डेटा को पढ़ते हैं जिसे दूसरे ट्रांज़ैक्शन ने लिखा है पर commit नहीं किया।
परिदृश्य: अलेक्स $500 ट्रांसफ़र करता है, बैलेंस अस्थायी रूप से $200 हो जाता है, और आप वह $200 पढ़ लेते हैं इससे पहले कि अलेक्स का ट्रांसफ़र बाद में फेल हो और rollback हो जाए।
उपयोगकर्ता परिणाम: ग्राहक गलत कम बैलेंस देखता है, फ्रॉड रूल गलत तरीके से ट्रिगर हो सकता है, या सपोर्ट एजेंट गलत जवाब देता है।
Non-repeatable read तब होता है जब आप एक ही पंक्ति दो बार पढ़ते हैं और बीच में किसी अन्य ट्रांज़ैक्शन ने commit कर दिया, इसलिए मान बदल जाता है।
परिदृश्य: आप एक ऑर्डर टोटल ($49.00) लोड करते हैं, फिर तुरंत रीफ़्रेश करते हैं और $54.00 देखते हैं क्योंकि किसी ने डिस्काउंट लाइन हटा दी।
उपयोगकर्ता परिणाम: "मेरी कुल राशि चेकआउट के दौरान बदल गई", जिससे अविश्वास या कार्ट छोड़ना हो सकता है।
Phantom read non-repeatable read जैसा है, पर पंक्तियों के सेट के साथ: दूसरी बार क्वेरी चलाने पर नया या गायब मिलती पंक्ति क्योंकि किसी ने मैचिंग रिकॉर्ड डाला/हटा दिया।
परिदृश्य: होटल सर्च "3 कमरे उपलब्ध" दिखाता है, फिर बुकिंग के दौरान सिस्टम फिर से चेक करता है और पाता है कि अब कोई नहीं है क्योंकि नई reservations जोड़ दी गईं।
उपयोगकर्ता परिणाम: डबल बुकिंग प्रयास, inconsistent availability स्क्रीन, या ओवरसेलिंग।
Lost update तब होता है जब दो ट्रांज़ैक्शन एक ही मान पढ़ते हैं और दोनों अपडेट लिखते हैं, और बाद वाला पिछले वाले को ओवरराइट कर देता है।
परिदृश्य: दो एडमिन एक ही प्रोडक्ट प्राइस एडिट करते हैं। दोनों $10 से शुरू करते हैं; एक $12 सेव करता है, दूसरा $11 बाद में सेव करता है।
उपयोगकर्ता परिणाम: किसी का परिवर्तन गायब हो जाता है; टोटल्स और रिपोर्ट गलत हो जाती हैं।
Write skew तब होता है जब दो ट्रांज़ैक्शन प्रत्येक अलग-अलग वैध परिवर्तन करते हैं, पर मिलकर कोई नियम तोड़ देते हैं।
परिदृश्य: नियम: "कम से कम एक ऑन-कॉल डॉक्टर शेड्यूल में होना चाहिए." दो डॉक्टर स्वतंत्र रूप से खुद को ऑफ-कॉल मार्क कर देते हैं यह देखकर कि दूसरा अभी भी ऑन-कॉल है।
उपयोगकर्ता परिणाम: आपके पास शेड्यूल में शून्य कवरेज बचता है, भले ही हर ट्रांज़ैक्शन ने अपने चेक पास कर लिए हों।
ज़्यादा मजबूत isolation अनोमलियाँ कम करती है पर प्रतीक्षा, retries और लागत बढ़ा सकती है उच्च समकालिकता में। बहुत से सिस्टम पढ़-भारी analytics के लिए कमजोर isolation चुनते हैं, जबकि पैसा-हिलाने, बुकिंग, और अन्य correctness-critical फ्लोज के लिए सख्त सेटिंग्स का उपयोग करते हैं।
Isolation इस बारे में है कि आपका ट्रांज़ैक्शन दूसरों के चलने के दौरान क्या "देख" सकता है। डेटाबेस इसे isolation levels के रूप में एक्सपोज़ करते हैं: ऊँचे स्तर आश्चर्यजनक व्यवहार को कम करते हैं, पर थ्रूपुट या प्रतीक्षा बढ़ा सकते हैं।
टीमें अक्सर Read Committed को यूजर-फेसिंग ऐप्स के लिए डिफ़ॉल्ट चुनती हैं: अच्छा प्रदर्शन, और "कोई dirty reads नहीं" अधिकांश अपेक्षाओं से मेल खाता है।
जब आपको ट्रांज़ैक्शन के अंदर स्थिर परिणाम चाहिए (उदा. लाइन आइटम्स से invoice बनाना) और आप कुछ overhead सहन कर सकते हैं तो Repeatable Read लें।
जब correctness concurrency पर सबसे अधिक मायने रखता है (उदा. oversell रोकना) और आप जटिल रेस कंडीशनों को एप कोड में संभाल नहीं सकते, तब Serializable का उपयोग करें।
Read Uncommitted OLTP सिस्टम में दुर्लभ है; कभी-कभी निगरानी या अनुमानित रिपोर्टिंग के लिए उपयोग होता है जहाँ कभी-कभी गलत पढ़ना सहनीय है।
नाम सामान्यीकृत हैं, पर ठीक गारंटियाँ DB इंजन के अनुसार अलग-अलग हो सकती हैं (और कभी-कभी कॉन्फ़िगरेशन पर भी)। अपने डेटाबेस डॉक्यूमेंटेशन की पुष्टि करें और उन अनोमलियों का परीक्षण करें जो आपके बिजनेस के लिए मायने रखती हैं।
Durability का मतलब है कि एक बार ट्रांज़ैक्शन committed होने पर उसके परिणाम किसी क्रैश—पावर लॉस, प्रोसेस रीस्टार्ट, या अचानक मशीन रीबूट—के बाद भी बरकरार रहने चाहिए। अगर आपकी ऐप कस्टमर को बताती है "पेमेंट सफल हुआ", तो durability का वादा है कि डेटाबेस अगली फेल्योर के बाद उस तथ्य को "भूल" नहीं सकता।
अधिकांश रिलेशनल डेटाबेस write-ahead logging (WAL) के साथ durability हासिल करते हैं। उच्च स्तर पर, डेटाबेस commit मानने से पहले बदलावों की एक क्रमिक "रसीद" लॉग पर डिस्क पर लिख देता है। अगर डेटाबेस क्रैश कर जाता है, तो startup पर यह लॉग replay करके committed बदलावों को बहाल कर सकता है।
रिकवरी समय को सीमित रखने के लिए, डेटाबेस checkpoints भी बनाता है। एक checkpoint वह क्षण होता है जब डेटाबेस सुनिश्चित करता है कि हाल के काफी बदलाव मुख्य डेटा फाइलों में लिखे जा चुके हैं, ताकि रिकवरी अनंत लॉग इतिहास replay न करे।
Durability कोई बाइनरी ऑन/ऑफ स्विच नहीं है; यह इस बात पर निर्भर करती है कि डेटाबेस कितना आक्रामक रूप से डेटा को स्थायी स्टोरेज तक पुश करता है।
fsync) का इंतज़ार करता है पहले कि commit की पुष्टि करे। यह सुरक्षित है पर लेटेंसी बढ़ सकती है।मूलभूत हार्डवेयर भी मायने रखता है: SSDs, RAID कंट्रोलर्स जिनमें write caches हों, और क्लाउड वॉल्यूम फेल्योर के दौरान अलग व्यवहार कर सकते हैं।
बैकअप और रेप्लिकेशन आपको recover करने या downtime घटाने में मदद करते हैं, पर वे durability की वही गारंटी नहीं हैं। कोई ट्रांज़ैक्शन प्राइमरी पर durable हो सकता है भले ही वह replica पर न पहुँचा हो, और बैकअप सामान्यतः प्वाइंट-इन-टाइम स्नैपशॉट होते हैं न कि commit-लागत-by-लागत गारंटी।
जब आप BEGIN करते हैं और बाद में COMMIT, डेटाबेस कई मूविंग पार्ट्स का समन्वय करता है: कौन किस पंक्ति को पढ़ सकता है, कौन उसे अपडेट कर सकता है, और अगर दो लोग एक ही रिकॉर्ड बदलना चाह रहे हों तो क्या होगा।
एक प्रमुख "भीतर का" चुनाव यह है कि conflicts को कैसे संभाला जाए:
कई सिस्टम वर्कलोड और isolation level के अनुसार दोनों विचारों को मिलाते हैं।
आधुनिक डेटाबेस अक्सर MVCC (Multi-Version Concurrency Control) का उपयोग करते हैं: एक पंक्ति की केवल एक प्रति रखने के बजाय, डेटाबेस कई वर्शन रखता है।
यह एक बड़ा कारण है कि कुछ डेटाबेस बहुत सारे पढ़/लिख ऑप्स को कम blocking के साथ संभालते हैं—हालांकि write/write conflicts का समाधान अभी भी जरूरी होता है।
लॉक्स deadlocks पैदा कर सकते हैं: ट्रांज़ैक्शन A उस लॉक का इंतज़ार करता है जो B के पास है, जबकि B उसी लॉक का इंतज़ार करती है जो A के पास है।
डेटाबेस आमतौर पर इस चक्र का पता लगा कर और एक ट्रांज़ैक्शन को abort कर के (deadlock victim) इसे सुलझाते हैं, ताकि एप्लिकेशन retry कर सके।
अगर ACID प्रवर्तन घर्षण पैदा कर रहा है, तो आप प्रायः यह देखेंगे:
ये लक्षण अक्सर बताते हैं कि ट्रांज़ैक्शन साइज, इंडेक्सिंग, या कौन सा isolation/locking रणनीति वर्कलोड के अनुरूप है, इसे फिर से देखना चाहिए।
ACID गारंटियाँ सिर्फ डेटाबेस थीगरी नहीं हैं—वे इस बारे में प्रभावित करती हैं कि आप API, बैकग्राउंड जॉब्स, और यहां तक कि UI फ्लो कैसे डिज़ाइन करते हैं। मूल विचार सरल है: तय करें कौन से स्टेप्स साथ में सफल होने चाहिए, फिर केवल उन्हीं स्टेप्स को ट्रांज़ैक्शन में रखें।
एक अच्छा ट्रांज़ैक्शनल API आमतौर पर एक ही बिजनेस एक्शन से मेल खाता है, भले ही यह कई तालिकाओं को छुए। उदाहरण के लिए, /checkout ऑपरेशन ऑर्डर बनाए, इन्वेंटरी रिज़र्व करे, और पेमेंट इंटेंट रिकॉर्ड करे—उन डेटाबेस राइट्स को आमतौर पर एक ही ट्रांज़ैक्शन में होना चाहिए ताकि वे साथ में commit हों (या साथ में rollback)।
एक सामान्य पैटर्न:
यह atomicity और consistency बनाये रखता है जबकि धीमे, नाजुक ट्रांज़ैक्शन्स से बचाता है।
ट्रांज़ैक्शन सीमाएँ इस बात पर निर्भर करती हैं कि "एक यूनिट ऑफ वर्क" का क्या मतलब है:
ACID मदद करता है, पर आपका एप अभी भी फेल्योर सही तरीके से संभाले:
बचे रहें: लंबे ट्रांज़ैक्शन्स, ट्रांज़ैक्शन के अंदर बाहरी API कॉल करना, और ट्रांज़ैक्शन में यूज़र थिंक टाइम (उदा. "कार्ट रो लॉक करें, फिर यूज़र से कन्फ़र्मेशन का इंतज़ार करें"). ये contention बढ़ाते हैं और isolation conflicts की संभावना बढ़ाते हैं।
अगर आप तेज़ी से transactional system बना रहे हैं, तो सबसे बड़ा जोखिम अक्सर "ACID न जानना" नहीं—बल्कि एक बिजनेस एक्शन को कई endpoints, जॉब्स, या टेबल्स में बिखेर देना है बिना स्पष्ट ट्रांज़ैक्शन सीमा के।
ऐसे प्लेटफ़ॉर्म जैसे Koder.ai आपको तेज़ी से बनाते समय मदद कर सकते हैं: आप एक वर्कफ़्लो (उदा. "इन्वेंटरी रिज़र्वेशन और पेमेंट इंटेंट के साथ चेकआउट") योजना-प्रथम चैट में वर्णन कर सकते हैं, React UI और Go + PostgreSQL बैकएंड जेनरेट कर सकते हैं, और schema/transaction सीमा बदलने पर snapshots/rollback के साथ iterate कर सकते हैं। डेटाबेस अभी भी गारंटियाँ लागू करता है; मूल्य सही डिज़ाइन से काम करने वाले इम्प्लीमेंटेशन तक पहुँचने की गति में है।
एक ही डेटाबेस आमतौर पर एक ट्रांज़ैक्शन सीमा के भीतर ACID गारंटियाँ दे सकता है। जब आप काम कई सर्विसेज़ (और अक्सर कई डेटाबेस) में फैलाते हैं, तो वही गारंटियाँ रखना कठिन और महंगा हो जाता है।
कठोर consistency का मतलब है हर पढ़ने पर "लेटेस्ट committed सच" दिखाई दे। उच्च availability का मतलब है सिस्टम उत्तर देता रहे भले ही कुछ हिस्से धीमे या अनपहुंचयोग्य हों।
मल्टी-सर्विस सेटअप में, अस्थायी नेटवर्क समस्या आपको चुनाव पर मजबूर कर सकती है: हर प्रतिभागी के सहमति तक अनुरोध ब्लॉक/फेल करें (ज़्यादा consistent, कम available), या स्वीकार करें कि सर्विसेज़ थोड़ी देर के लिए असंगत रह सकती हैं (ज़्यादा available, कम consistent)। कोई विकल्प हमेशा सही नहीं है—यह निर्भर करता है कि आपके बिजनेस किस त्रुटि को सहन कर सकता है।
वितरित ट्रांज़ैक्शन्स उन सीमाओं के पार समन्वय मांगती हैं जिन पर आपके पास पूरा नियंत्रण नहीं होता: नेटवर्क देरी, retries, टाइमआउट, सर्विस क्रैश, और आंशिक फेल्योर।
भले ही हर सर्विस सही हो, नेटवर्क ambiguity पैदा कर सकता है: क्या पेमेंट सर्विस commit कर गया पर ऑर्डर सर्विस ने acknowledge नहीं पाया? ऐसे मामलों को सुरक्षित रूप से सुलझाने के लिए सिस्टम समन्वय प्रोटोकॉल (जैसे two-phase commit) इस्तेमाल करते हैं, जो धीमे, फेल्योर के दौरान उपलब्धता घटा सकते हैं और संचालन जटिलता बढ़ा सकते हैं।
Sagas वर्कफ़्लो को स्टेप्स में तोड़ते हैं; हर स्टेप लोकल रूप से commit होता है। अगर बाद का स्टेप फेल हो, तो पहले किये गए स्टेप्स को compensating actions (उदा. चार्ज रिफंड करना) से undo किया जाता है।
Outbox/inbox पैटर्न ईवेंट पब्लिशिंग और उपभोक्ता भरोसेमंद बनाते हैं। एक सर्विस एक ही लोकल ट्रांज़ैक्शन में बिजनेस डेटा और "पब्लिश करने हेतु ईवेंट" रिकॉर्ड (outbox) लिखती है। उपभोक्ता प्रोसेस्ड मैसेज IDs (inbox) रिकॉर्ड करते हैं ताकि retries बिना डुप्लिकेट प्रभावों के संभाले जा सकें।
Eventual consistency अल्प विंडो को स्वीकार करती है जहाँ सर्विसेज़ के बीच डेटा भिन्न हो सकता है, और reconciliation का स्पष्ट प्लान होता है।
ग্যারंटी तब ढीली करें जब:
जोखिम नियंत्रित करें: invariants परिभाषित कर के (क्या कभी नहीं टूटना चाहिए), idempotent ऑपरेशन्स डिज़ाइन करके, टाइमआउट और retries के साथ backoff उपयोग करके, और drift की निगरानी कर के (stuck sagas, repeated compensations, बढ़ती outbox टेबल)। सच में क्रिटिकल invariants के लिए (उदा. "कभी खाते से ज़्यादा न निकलें"), उन्हें एक ही सर्विस और एक ही डेटाबेस ट्रांज़ैक्शन के भीतर रखें।
एक ट्रांज़ैक्शन यूनिट टेस्ट में "सही" लग सकता है और फिर भी असल ट्रैफ़िक, रीस्टार्ट, और समकालिकता में फेल हो सकता है। इस चेकलिस्ट का उपयोग करें ताकि ACID गारंटियाँ आपके प्रोडक्शन व्यवहार के साथ संरेखित रहें।
सबसे पहले लिखिए कि क्या हमेशा सच होना चाहिए (आपके डेटा invariants)। उदाहरण: "खाता बैलेंस कभी नेगेटिव न हो", "ऑर्डर टोटल लाइन आइटम्स के योग के बराबर हो", "इन्वेंटरी शून्य से नीचे न जाए", "एक पेमेंट ठीक एक ऑर्डर से जुड़ा हो"। इन्हें प्रोडक्ट नियम मानें, न कि केवल डेटाबेस ट्रिविया।
फिर तय करिए क्या एक ट्रांज़ैक्शन में होना चाहिए और क्या बाद में किया जा सकता है।
ट्रांज़ैक्शन्स को छोटा रखें: कम पंक्तियाँ छुएँ, कम काम करें (कोई बाहरी API कॉल्स नहीं), और जल्दी commit करें।
समकालिकता को टेस्ट का पहला दर्जा बनाइए।
अगर आप retries सपोर्ट करते हैं, तो एक स्पष्ट idempotency key जोड़ें और "सफलता के बाद अनुरोध दोहराया गया" पर भी परीक्षण करें।
उन संकेतकों को मॉनिटर करें जो बताते हैं कि आपकी गारंटियाँ महंगी या नाज़ुक हो रही हैं:
ट्रेन्ड्स पर अलर्ट सेट करें, सिर्फ स्पाइक्स पर नहीं, और इन्हें उन endpoints या जॉब्स से जोड़ें जो कारण हैं।
आपके invariants को बचाने वाली सबसे कमजोर isolation का उपयोग करें; डिफ़ॉल्ट रूप से उसे "max कर देना" मत कीजिए। जब आपको छोटे क्रिटिकल सेक्शन के लिए सख्त correctness चाहिए (पैसा स्थानांतरित करना, इन्वेंटरी घटाना), तो ट्रांज़ैक्शन को सिर्फ उसी सेक्शन तक सीमित रखें और बाकी सब बाहर रखें।
ACID एक सेट ट्रांज़ैक्शनल गारंटी है जो फेलियर और समकालिकता के दौरान डेटाबेस को अनुमाननीय बनाती है:
एक ट्रांज़ैक्शन डेटाबेस द्वारा एक "यूनिट ऑफ वर्क" के रूप में माना जाने वाला पैकेज होता है। भले ही यह कई SQL स्टेटमेंट चलाए (जैसे ऑर्डर बनाना, इन्वेंटरी घटाना, पेमेंट इंटेंट रिकॉर्ड करना), इसके केवल दो संभावित परिणाम होते हैं:
क्योंकि आंशिक अपडेट वास्तविक दुनिया में विरोधाभास पैदा करते हैं जो बाद में ठीक करना महंगा होता है—उदाहरण:
ACID (विशेषकर atomicity + consistency) इन “आधा-पुरा” राज्यों को सच के रूप में दिखाई देने से रोकता है।
Atomicity यह सुनिश्चित करती है कि डेटाबेस कभी भी "आधा-पूरा" ट्रांज़ैक्शन एक्सपोज़ न करे। अगर commit से पहले कुछ भी फेल होता है—एप क्रैश, नेटवर्क ड्रॉप, DB रीस्टार्ट—तो ट्रांज़ैक्शन rollback हो जाता है ताकि पहले किये गए कदम स्थायी स्थिति में लीक न हों।
व्यवहार में, atomicity वह है जो दो बैलेंस अपडेट जैसे मल्टी-स्टेप बदलावों को सुरक्षित बनाती है।
कभी-कभी क्लाइंट को पता नहीं चलता कि commit हुआ या नहीं (उदा. नेटवर्क टाइमआउट ठीक बाद)। इसलिए ACID के साथ भी आप को:
इनका组合 आंशिक अपडेट और आकस्मिक डबल-चार्ज/डबल-राइट से बचाता है।
ACID में “consistency” का मतलब यह नहीं कि डेटा "ठीक दिखे" या "सभी रेप्लिका मेल खाते हों"। इसका मतलब है कि हर ट्रांज़ैक्शन डेटाबेस को एक वैध स्थिति से दूसरी वैध स्थिति में ले जाना चाहिए—आपके द्वारा परिभाषित नियमों के अनुसार।
यदि आपने कोई नियम (उदा. बैलेंस नकारात्मक न हो) को कहीं एनकोड नहीं किया, तो ACID उसे अपने आप नहीं बना सकता; डेटाबेस को उसे लागू करने के लिए स्पष्ट constraints चाहिए।
एप्लिकेशन-स्तरीय वैलिडेशन UX के लिए अच्छा है और जटिल नियम लागू कर सकता है, पर समकालिकता में विफल हो सकता है (दो अनुरोध एक साथ चेक पास कर लें)।
डेटाबेस constraints अंतिम गेटकीपर होते हैं:
दोनों का उपयोग करें: ऐप में जल्दी जांच, डेटाबेस में निर्णायक प्रवर्तन।
Isolation नियंत्रित करता है कि आपका ट्रांज़ैक्शन दूसरों के चलने के दौरान क्या देख सकता है। कमजोर isolation से निम्न जैसी अनोमनियाँ हो सकती हैं:
Isolation levels आपको प्रदर्शन और रक्षा के बीच trade-off चुनने देते हैं।
अधिकांश OLTP एप के लिए व्यावहारिक बेसलाइन Read Committed है—यह dirty reads रोकता है और प्रदर्शन अच्छा रखता है। ऊपर की ओर जाएँ जब आवश्यक हो:
हमेशा अपने DB इंजन में व्यवहार की पुष्टि करें—विवरण प्लेटफ़ॉर्म-विशिष्ट होते हैं।
Durability का वादा है कि जब डेटाबेस किसी commit की पुष्टि कर दे, तो वह बदलाव क्रैश के बाद भी रहेगा। अधिकांश रिलेशनल डेटाबेस यह write-ahead logging (WAL) से हासिल करते हैं: बदलावों की क्रमिक "रसीद" लॉग पर डिस्क पर commit से पहले लिख दी जाती है ताकि क्रैश के बाद लॉग को replay कर सके।
सिंक्रोनस सेटिंग्स ज्यादा सुरक्षित पर लेटेंसी बढ़ा सकती हैं; असिंक्रोनस सेटिंग्स तेज़ पर हाल की committed ट्रांज़ैक्शन क्रैश पर खो सकती हैं। बैकअप और रेप्लिकेशन recovery/availability में मदद करते हैं पर वे durability की वही गारंटी नहीं हैं।
जब आप BEGIN और बाद में COMMIT करते हैं, डेटाबेस कई हिस्सों का समन्वय करता है: किसे कौन सी पंक्तियाँ पढ़नी/लिखनी हैं, कब लॉक लगाना है, और समकालिक अपडेट्स का क्या होगा।
कई मुख्य तकनीकें हैं:
ACID गारंटियाँ आपके API, बैकग्राउंड जॉब्स और UI फ्लो को प्रभावित करती हैं। मूल विचार: तय करें कौन से स्टेप्स साथ में सफल होने चाहिए, और केवल उन्हीं को ट्रांज़ैक्शन में रखें।
अच्छा पैटर्न:
ट्रांज़ैक्शन सीमाएँ अनुरोध, जॉब या सर्विस के अनुसार अलग हों: यूजर रिक्वेस्ट में छोटे रखें; बैच जॉब्स में बैच-commits का उपयोग करें; सर्विस-बाउंड्रीज़ पार करने पर अलग पैटर्न (outbox, saga) सोचें।
त्रुटि हैंडलिंग: error पर rollback, अस्थायी त्रुटियों पर retry, और idempotency सुनिश्चित करें ताकि replays सुरक्षित हों।
एक ही डेटाबेस के भीतर ACID सामान्यतः काम करता है; जैसे ही आप काम कई सर्विसेज/डेटाबेस में फैलाते हैं, गारंटियाँ रखना कठिन और महंगा हो जाता है।
Distributed transactions नेटवर्क देरी, partial failures और coordination की चुनौतियाँ लाते हैं; दो-फेज़ कमिट जैसे प्रोटोकॉल सही पर धीमे और जटिल हो सकते हैं।
प्रैक्टिकल पैटर्न:
ट्रांज़ैक्शन यूनिट सही होने पर भी असल ट्रैफ़िक, रीस्टार्ट और समकालिकता के तहत फेल हो सकती है। एक प्रैक्टिकल चेकलिस्ट:
लॉक्स deadlocks पैदा कर सकते हैं; DB आमतौर पर साइकिल detect कर एक ट्रांज़ैक्शन abort कर देता है (deadlock victim) ताकि एप retry कर सके।
जो इनवेरिएन्ट बेहद क्रिटिकल हैं (उदा. "कभी खाते से अधिक पैसे न निकले"), उन्हें एक ही सर्विस और एक ही DB ट्रांज़ैक्शन में रखें।
नियम: weakest isolation चुनें जो आपके invariants को बचाए; क्रिटिकल सेक्शन छोटा रखें और बाकी बाहर रखें।