प्रतिनिधि इंटरव्यू विषय

ट्रांज़ैक्शन आइसोलेशन के साथ राइट स्क्यू (Write Skew) को रोकें

बैकएंडकठिन
Offer.cc संपादकीय टीमप्रकाशित अपडेट किया गया

प्रश्न

PostgreSQL में, प्रत्येक शिफ्ट में कम से कम 1 डॉक्टर ऑन कॉल होना अनिवार्य है। दो समवर्ती (concurrent) ट्रांज़ैक्शन 2 सक्रिय डॉक्टरों को रीड करते हैं, और फिर अलग-अलग डॉक्टरों को ऑन कॉल से हटा देते हैं। समझाएं कि ट्रांज़ैक्शन अभी भी नियम का उल्लंघन क्यों कर सकते हैं, और एक सुरक्षित कार्यान्वयन डिज़ाइन करें जिसका परीक्षण और रिट्राई किया जा सके।

प्रांप्ट और लागू संदर्भ

तालिका doctor_shifts(team_id, shift_date, doctor_id, on_call) ऑन-कॉल स्थिति को संग्रहीत करती है। व्यावसायिक नियम कहता है कि प्रत्येक टीम और शिफ्ट में कम से कम 1 ऑन-कॉल डॉक्टर होना चाहिए। Alice और Bob दोनों सक्रिय हैं, और दो अनुरोध समवर्ती रूप से अलग-अलग डॉक्टरों को ऑन-कॉल से हटाने का प्रयास करते हैं। प्रत्येक ट्रांज़ैक्शन पहले सक्रिय डॉक्टरों की गणना करता है। यदि गणना 1 से अधिक है, तो यह अपने संबंधित डॉक्टर की पंक्ति को अपडेट करता है।

यह प्रश्न PostgreSQL 18 के दायरे में है। प्रारंभिक सक्रिय गणना 2 है। दोनों ट्रांज़ैक्शन किसी भी ट्रांज़ैक्शन के कमिट होने से पहले अपना रीड पूरा कर लेते हैं, और फिर वे अलग-अलग पंक्तियों को अपडेट करते हैं। संख्याएँ और स्कीमा साक्षात्कार के लिए मानी गई स्थितियाँ हैं, कोई सार्वभौमिक स्वास्थ्य सेवा डेटा मॉडल नहीं। मूल समस्या मल्टी-रो प्रेडिकेट count(on_call) >= 1 है; अनुमोदन वर्कफ़्लो, समय क्षेत्र और ऑथराइजेशन इसके दायरे से बाहर हैं।

यही पैटर्न अन्य नियमों में भी दिखाई देता है जैसे कि इन्वेंट्री कभी नकारात्मक न होना, किसी खाते में कम से कम एक स्वीकृतकर्ता का होना, या किसी क्लस्टर में कम से कम एक प्राथमिक नोड का होना। केवल यह कहना कि "इसे ट्रांज़ैक्शन में डाल दें" अधूरा है। एटॉमिसीटी (Atomicity) किसी एक ट्रांज़ैक्शन को एक इकाई के रूप में सफल या विफल बनाती है। आइसोलेशन स्तर यह निर्धारित करता है कि समवर्ती ट्रांज़ैक्शन क्या देख सकते हैं और क्या डेटाबेस ऐसे परिणाम को अस्वीकार करता है जिसका कोई वैध क्रमिक (serial) स्पष्टीकरण नहीं है।

साक्षात्कारकर्ता क्या मूल्यांकन करता है

पहला संकेत राइट स्क्यू (write skew) को पहचानना है। दोनों ट्रांज़ैक्शन समान प्रेडिकेट को पढ़ते हैं लेकिन अलग-अलग पंक्तियों पर लिखते हैं, इसलिए वे एक ही पंक्ति पर सामान्य राइट विरोध उत्पन्न नहीं करते हैं। प्रत्येक ट्रांज़ैक्शन आइसोलेशन में अपनी जाँच पास कर लेता है, फिर भी उनका संयुक्त परिणाम शून्य सक्रिय डॉक्टर छोड़ता है। इसे डर्टी रीड या साधारण लॉस्ट अपडेट कहना गलत समाधान की ओर ले जाता है।

दूसरा संकेत मानक नामों को डेटाबेस कार्यान्वयन से अलग करना है। PostgreSQL Read Committed प्रत्येक स्टेटमेंट के लिए एक नया स्नैपशॉट लेता है। Repeatable Read एक स्थिर ट्रांज़ैक्शन स्नैपशॉट का उपयोग करता है और इसे स्नैपशॉट आइसोलेशन के रूप में लागू किया जाता है, जो अभी भी सीरियलाइज़ेशन विसंगतियों की अनुमति देता है। Serializable समान स्नैपशॉट-शैली के रीड के शीर्ष पर रीड/राइट निर्भरताओं को ट्रैक करता है और जब परिणाम किसी भी सीरियल क्रम से मेल नहीं खा सकता है तो ट्रांज़ैक्शन को निरस्त (abort) कर देता है। समान नाम वाला आइसोलेशन स्तर किसी अन्य डेटाबेस में विभिन्न लॉकिंग और स्नैपशॉट सेमेंटिक्स का उपयोग कर सकता है।

तीसरा संकेत इनवेरिएंट से समवर्ती सीमा (concurrency boundary) का चयन करना है। जब इनवेरिएंट कई पंक्तियों में फैला होता है, तो "जिस डॉक्टर को मैं अपडेट करने जा रहा हूँ" उसे लॉक करने से दोनों ट्रांज़ैक्शन आपस में नहीं टकराते। एक सुरक्षित डिज़ाइन को उन्हें एक लॉक करने योग्य ऑब्जेक्ट पर प्रतिस्पर्धी बनाना चाहिए, नियम को एक पंक्ति पर एक एटॉमिक शर्त में बदलना चाहिए, या Serializable को संघर्ष का पता लगाने देना चाहिए।

अंत में, साक्षात्कारकर्ता विफलता प्रबंधन की तलाश करता है। Serializable और स्पष्ट लॉकिंग दोनों ट्रांज़ैक्शन को निरस्त कर सकते हैं। सामान्य PostgreSQL सीरियलाइज़ेशन कोड 40001 है; असंगत लॉक क्रम 40P01 भी उत्पन्न कर सकता है। एक मजबूत उत्तर पूर्ण ट्रांज़ैक्शन का पुनः प्रयास (retry) करता है, प्रयासों को सीमित करता है, और सफल कमिट के बाद ईमेल या अन्य बाहरी प्रभावों को एक इडेम्पोटेंट पथ पर ले जाता है।

उत्तर देने से पहले स्पष्ट करने योग्य प्रश्न

  • किस डेटाबेस और संस्करण का उपयोग किया जा रहा है? यह उत्तर PostgreSQL 18 को लक्षित करता है। MySQL, SQL Server और वितरित डेटाबेस को उनके समान नाम वाले आइसोलेशन स्तरों और रिट्राई त्रुटियों की नए सिरे से जाँच की आवश्यकता होती है।
  • क्या नियम में ठीक एक शिफ्ट शामिल है? (team_id, shift_date) द्वारा कुंजीबद्ध इनवेरिएंट में प्रति शिफ्ट एक स्थिर प्रतिस्पर्धा बिंदु हो सकता है। क्षेत्रों या डेटाबेस में फैले नियम को केवल एक डेटाबेस में पंक्ति लॉक द्वारा संरक्षित नहीं किया जा सकता है।
  • कौन से पथ ऑन-कॉल स्थिति को बदल सकते हैं? छुट्टी के अनुरोध, अदला-बदली, आयात और प्रशासनिक मरम्मत सभी को समान प्रोटोकॉल का पालन करना चाहिए। केवल एक API को ठीक करने से बाईपास राइट्स रह जाते हैं जो काउंटर को दूषित कर सकते हैं या लॉक को छोड़ सकते हैं।
  • क्या संघर्ष कभी-कभार होते हैं या लगातार अत्यधिक होते हैं? कम प्रतिस्पर्धा में रिट्राई के साथ Serializable अक्सर सबसे स्पष्ट होता है। सिंगल-रो कंडीशनल अपडेट व्यस्त शिफ्ट में व्यर्थ काम से बचा सकता है, लेकिन यह उस शिफ्ट को एक सीरियलाइज़्ड हॉटस्पॉट में भी बदल देता है।
  • कोई अनुरोध कितनी देर तक प्रतीक्षा कर सकता है? निराशावादी (Pessimistic) लॉक धारक की प्रतीक्षा करते हैं। एक सख्त विलंबता बजट के लिए छोटे ट्रांज़ैक्शन, लॉक-प्रतीक्षा सीमा और एक स्पष्ट "स्थिति बदल गई, पुनः प्रयास करें" परिणाम की आवश्यकता होती है।
  • क्या ट्रांज़ैक्शन बाहरी दुष्प्रभाव (side effects) उत्पन्न करता है? एक रिट्राई ट्रांज़ैक्शन लॉजिक को फिर से चलाता है। ईमेल, रोस्टर-सेवा कॉल, या गैर-ट्रांज़ैक्शनल संदेश कमिट के बाद होने चाहिए या इडेम्पोटेंट खपत के साथ एक ट्रांज़ैक्शनल आउटबॉक्स का उपयोग करना चाहिए।

30-सेकंड का उत्तर ढांचा

"यह राइट स्क्यू (write skew) है: दोनों ट्रांज़ैक्शन एक ही ऑन-कॉल सेट से निर्णय लेते हैं लेकिन अलग-अलग डॉक्टर पंक्तियों को अपडेट करते हैं, इसलिए एक स्थिर Repeatable Read स्नैपशॉट भी दोनों को कमिट करने की अनुमति दे सकता है। PostgreSQL Serializable उस रीड/राइट निर्भरता को ट्रैक करता है और कम से कम एक ट्रांज़ैक्शन को निरस्त करता है; एप्लिकेशन को 40001 पर पूरा निर्णय फिर से चलाना होगा। Serializable के बिना, मैं प्रत्येक शिफ्ट को एक रोस्टर पंक्ति में मैप करूँगा, Read Committed के तहत केवल तभी एटॉमिक रूप से घटाऊँगा जब active_count > 1 हो, और उसी ट्रांज़ैक्शन में डॉक्टर को अपडेट करूँगा। दूसरा विकल्प दोबारा गणना करने से पहले रोस्टर पंक्ति को लॉक करना है। मैं दो कनेक्शनों और दोनों रीड के बाद एक बैरियर के साथ परीक्षण करूँगा, यह साबित करते हुए कि कमजोर आइसोलेशन शून्य तक पहुँच जाता है जबकि सुरक्षित डिज़ाइन ठीक एक डॉक्टर को बनाए रखता है।"

चरण-दर-चरण गहन उत्तर

समवर्ती इतिहास के साथ शुरुआत करें। प्रारंभ में, Alice=true, Bob=true:

text
T1: read count(on_call) = 2
T2: read count(on_call) = 2
T1: update Alice to false
T2: update Bob to false
T1: commit
T2: commit

यदि T1 एक सीरियल निष्पादन में पहले चलता, तो T2 1 को पढ़ता और परिवर्तन को अस्वीकार कर देता। यदि T2 पहले चलता, तो T1 इसे अस्वीकार कर देता। वास्तविक परिणाम शून्य है, जो किसी भी सीरियल क्रम के बराबर नहीं है और इसलिए यह एक सीरियलाइज़ेशन विसंगति है। लॉस्ट अपडेट से मुख्य अंतर यह है कि T1 और T2 कभी भी एक ही पंक्ति को अधिलेखित (overwrite) नहीं करते हैं, इसलिए सामान्य पंक्ति-स्तरीय राइट लॉक स्वाभाविक रूप से संघर्ष नहीं करते हैं।

Read Committed के तहत, प्रत्येक काउंट स्टेटमेंट उस स्टेटमेंट के शुरू होने पर कमिट किए गए डेटा को देखता है। प्रांप्ट में दिए गए बैरियर के साथ, दोनों स्टेटमेंट किसी भी अपडेट से पहले 2 पढ़ते हैं। UPDATE स्टेटमेंट अलग-अलग पंक्तियों को लक्षित करते हैं और दोनों कमिट हो सकते हैं। बाद के स्टेटमेंट को एक नया स्नैपशॉट प्राप्त होगा, लेकिन PostgreSQL पहले से लिए गए एप्लिकेशन निर्णय को पूर्वव्यापी रूप से अमान्य नहीं करता है।

PostgreSQL Repeatable Read ट्रांज़ैक्शन के लिए एक स्नैपशॉट रखता है। यह उस कार्यान्वयन के भीतर नॉन-रिपीटेबल रीड और फैंटम रीड को रोकता है, लेकिन सीरियलाइज़ेशन विसंगतियों की अनुमति देता है। दो ट्रांज़ैक्शन अलग-अलग संस्करण श्रृंखलाओं को अपडेट करते हैं, इसलिए एक ही पंक्ति का कोई समवर्ती अपडेट रोलबैक को ट्रिगर नहीं करता है और दोनों कमिट हो सकते हैं। MVCC बताता है कि रीडर्स राइटर्स को ब्लॉक करने से कैसे बचते हैं। यह Serializable का पर्याय नहीं है, और यह इस व्यावसायिक नियम का निष्कर्ष नहीं निकालता है कि 'कम से कम एक डॉक्टर ऑन कॉल बना रहे।'

पहला सुरक्षित विकल्प Serializable है:

sql
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;

SELECT count(*) AS active_count
FROM doctor_shifts
WHERE team_id = 42
  AND shift_date = DATE '2026-07-20'
  AND on_call;

-- Reject when active_count <= 1.
UPDATE doctor_shifts
SET on_call = false
WHERE team_id = 42
  AND shift_date = DATE '2026-07-20'
  AND doctor_id = 101
  AND on_call;

COMMIT;

जब अनुरोध ओवरलैप होते हैं, तो PostgreSQL पता लगाता है कि उनके प्रेडिकेट रीड और समवर्ती राइट्स ऐसी निर्भरताएँ बनाते हैं जिन्हें सीरियलाइज़ नहीं किया जा सकता है। यह दोनों ट्रांज़ैक्शन को कमिट करने की अनुमति नहीं दे सकता है। विफल ट्रांज़ैक्शन SQLSTATE 40001 लौटाता है। एक रिट्राई BEGIN से पहले शुरू होना चाहिए और गणना व अपडेट करने के निर्णय को फिर से चलाना चाहिए। केवल अंतिम UPDATE का पुनः प्रयास करना पुराने निर्णय को बनाए रखता है। जिटर्ड बैकऑफ़, अधिकतम प्रयास गणना और समग्र समय सीमा का उपयोग करें क्योंकि उच्च प्रतिस्पर्धा के तहत एक रिट्राई फिर से संघर्ष कर सकता है।

Serializable एप्लिकेशन को प्रत्येक इनवेरिएंट के लिए मैन्युअल रूप से लॉक का आविष्कार करने के बजाय अपने वास्तविक प्रेडिकेट के चारों ओर नियम को व्यक्त करने की अनुमति देता है। इसकी लागत डिपेंडेंसी-ट्रैकिंग ओवरहेड और संघर्ष रिट्राई है। एक बड़ा रीड सेट, एक लंबा ट्रांज़ैक्शन, और केंद्रित प्रतिस्पर्धा आमतौर पर ओवरलैप के अधिक अवसर पैदा करते हैं। (team_id, shift_date) पर एक इंडेक्स, छोटे ट्रांज़ैक्शन और कमिट से पहले नेटवर्क प्रतीक्षा न होना उस विंडो को कम करते हैं।

दूसरा विकल्प मल्टी-रो इनवेरिएंट को एक पंक्ति, on_call_rosters(team_id, shift_date, active_count), पर प्रोजेक्ट करता है और Read Committed के तहत एक एटॉमिक अपडेट का उपयोग करता है:

sql
BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;

UPDATE on_call_rosters
SET active_count = active_count - 1
WHERE team_id = 42
  AND shift_date = DATE '2026-07-20'
  AND active_count > 1
RETURNING active_count;

-- Continue only when exactly one roster row was returned.
UPDATE doctor_shifts
SET on_call = false
WHERE team_id = 42
  AND shift_date = DATE '2026-07-20'
  AND doctor_id = 101
  AND on_call;

-- Commit only when both updates changed exactly one row; otherwise roll back.
COMMIT;

दोनों अनुरोध अब एक ही रोस्टर पंक्ति पर प्रतिस्पर्धा करते हैं। PostgreSQL Read Committed के तहत समवर्ती अपडेट की प्रतीक्षा करने के बाद, एक अपडेटर नए पंक्ति संस्करण के विरुद्ध active_count > 1 की पुन: जाँच करता है। पहला अनुरोध 2 को 1 में बदलता है; दूसरी शर्त विफल हो जाती है और कोई पंक्ति वापस नहीं आती है। पंक्ति-स्तरीय CHECK (active_count >= 1) एक अंतिम सुरक्षा प्रदान कर सकता है। इसकी लागत यह है कि प्रत्येक सक्रियण, निष्क्रियता, आयात और मरम्मत पथ को उसी ट्रांज़ैक्शन में काउंटर को बनाए रखना चाहिए। सिस्टम को विवरण पंक्तियों के विरुद्ध काउंटर का मिलान भी करना चाहिए और इसे चुपचाप अधिलेखित करने के बजाय विचलन पर चेतावनी देनी चाहिए।

यदि काउंटर बनाए रखना अवांछनीय है, तो प्रत्येक शिफ्ट के लिए एक स्थिर रोस्टर पंक्ति रखें। Read Committed ट्रांज़ैक्शन के पहले चरण के रूप में, उस पंक्ति पर SELECT ... FOR UPDATE चलाएँ, फिर विवरण पंक्तियों की गणना करें और डॉक्टर को अपडेट करें। दूसरे अनुरोध की प्रतीक्षा के बाद, इसका अगला SELECT एक स्नैपशॉट प्राप्त करता है जिसमें पहला कमिट शामिल होता है। प्रत्येक राइट पथ को पहले उसी रोस्टर पंक्ति को लॉक करना चाहिए, और ट्रांज़ैक्शन छोटा रहना चाहिए। एक सूक्ष्म असुरक्षित संस्करण एक पुराना Repeatable Read स्नैपशॉट स्थापित करना है और फिर एक गार्ड पंक्ति को लॉक करना है जिसे कोई संशोधित नहीं करता है: FOR UPDATE प्राप्त करने से वह ट्रांज़ैक्शन स्नैपशॉट रीफ़्रेश नहीं होता है।

वर्तमान में सक्रिय सभी डॉक्टर पंक्तियों को सीधे लॉक करने के लिए भी सावधानी की आवश्यकता है। लॉक सेट एक प्रेडिकेट से आता है, और नई पंक्तियाँ, विभिन्न क्वेरी क्रम, या बाईपास राइट्स सुरक्षा तर्क को बदल सकते हैं। एकाधिक पंक्ति लॉकों में असंगत क्रम डेडलॉक का कारण बन सकता है। यदि इस डिज़ाइन की आवश्यकता है, तो स्थिर प्राथमिक-कुंजी क्रम में लॉक प्राप्त करें और 40P01 पर पूर्ण ट्रांज़ैक्शन को फिर से चलाएँ। केवल "जिस डॉक्टर को मैं ऑन कॉल से हटा रहा हूँ" उसे लॉक करना निश्चित रूप से अपर्याप्त है क्योंकि अनुरोध अभी भी अलग-अलग पंक्तियों को लॉक करते हैं।

दो API को क्रमिक रूप से कॉल करके इसे मान्य न करें। दो स्वतंत्र डेटाबेस कनेक्शन और एक परीक्षण बैरियर का उपयोग करें। दोनों सत्र Repeatable Read शुरू करते हैं, पंक्तियों की गणना करते हैं, पुष्टि करते हैं कि प्रत्येक ने 2 देखा, और उसके बाद ही अपने अपडेट और कमिट के लिए आगे बढ़ते हैं। बेसलाइन को विश्वसनीय रूप से दो कमिट और शून्य की अंतिम गणना उत्पन्न करनी चाहिए। Serializable के तहत, पुष्टि करें कि दो सफल परिणाम असंभव हैं: एक ट्रांज़ैक्शन 40001 प्राप्त करता है, और इसका पूर्ण पुनः प्रयास 1 देखता है और परिवर्तन को अस्वीकार करता है। रोस्टर डिज़ाइन के तहत, पुष्टि करें कि ठीक एक कंडीशनल अपडेट एक पंक्ति लौटाता है और विवरण तथा active_count दोनों 1 पर समाप्त होते हैं।

फेरबदल किए गए कमिट क्रम के साथ समवर्ती मामले को दोहराएं। एक ही डॉक्टर के लिए डुप्लिकेट अनुरोध, एक तीसरे डॉक्टर का शामिल होना, दो अपडेट के बीच रोलबैक, लॉक-प्रतीक्षा टाइमआउट और डेडलॉक मामलों को जोड़ें। उत्पादन में, 40001, 40P01, रिट्राई प्रयास, लॉक प्रतीक्षाएं, ट्रांज़ैक्शन अवधि, टर्मिनल विफलता दर, और रोस्टर काउंटरों और विवरण पंक्तियों के बीच अंतर की निगरानी करें। संघर्ष दर में अचानक वृद्धि आमतौर पर एक हॉटस्पॉट, लंबे ट्रांज़ैक्शन, या सीमा में प्रवेश करने वाले नए राइट पथ का संकेत देती है; असीमित रिट्राई केवल इसे छिपाते हैं और बढ़ाते हैं।

उच्च-गुणवत्ता वाला नमूना उत्तर

"मैं पहले इसे राइट स्क्यू (write skew) के रूप में पहचानूँगा। दोनों ट्रांज़ैक्शन समान क्रॉस-रो नियम की जाँच करते हैं, लेकिन Alice और Bob अपनी पंक्तियों को अपडेट करते हैं, इसलिए पंक्ति-स्तरीय राइट लॉक संघर्ष नहीं करते हैं। उस क्रम को देखते हुए जहाँ दोनों पहले 2 पढ़ते हैं, Read Committed दोनों को कमिट करने की अनुमति दे सकता है। PostgreSQL Repeatable Read प्रत्येक ट्रांज़ैक्शन को एक स्थिर स्नैपशॉट देता है, फिर भी यह अंतिम मान शून्य उत्पन्न कर सकता है, जिसे कोई भी सीरियल क्रम स्पष्ट नहीं कर सकता है।

मेरी पहली पसंद नियम को एक Serializable ट्रांज़ैक्शन में व्यक्त करना है। मैं शिफ्ट के लिए सक्रिय डॉक्टरों की गणना करता हूँ और केवल तभी अपडेट करता हूँ जब गणना 1 से अधिक हो। PostgreSQL प्रेडिकेट रीड और समवर्ती राइट्स के बीच निर्भरता को ट्रैक करता है। इस मामले में, दोनों ट्रांज़ैक्शन कमिट नहीं हो सकते; एक को 40001 प्राप्त होता है। एप्लिकेशन उस त्रुटि को पकड़ता है और एक सीमित रिट्राई नीति के साथ शुरुआत से गणना और अपडेट को फिर से चलाता है। मैं सूचनाओं को पुनः प्रयास योग्य ट्रांज़ैक्शन से बाहर रखता हूँ और कमिट के बाद उन्हें एक इडेम्पोटेंट आउटबॉक्स से संसाधित करता हूँ।

निरंतर प्रतिस्पर्धा वाली शिफ्ट के लिए, मैं एक on_call_rosters पंक्ति पर विचार करूँगा। Read Committed के तहत, छुट्टी ट्रांज़ैक्शन उस एक पंक्ति पर प्रतिस्पर्धा करने के लिए UPDATE ... SET active_count = active_count - 1 WHERE active_count > 1 RETURNING ... का उपयोग करता है, फिर डॉक्टर विवरण को अपडेट करता है। यदि कोई भी स्टेटमेंट ठीक एक पंक्ति को बदलने में विफल रहता है, तो पूरा ट्रांज़ैक्शन रोलबैक हो जाता है। दूसरा अनुरोध नवीनतम पंक्ति संस्करण पर अपनी स्थिति की पुन: जाँच करता है और विफल हो जाता है। इसका समझौता यह है कि प्रत्येक राइट पथ को काउंटर बनाए रखना होगा।

मैं दो कनेक्शनों और एक बैरियर के साथ डिज़ाइन को सत्यापित करूँगा जो इंटरलीविंग को ठीक करता है। पहले मैं साबित करूँगा कि Repeatable Read दोनों सत्रों को 2 पढ़ने और शून्य तक पहुँचने की अनुमति दे सकता है। फिर मैं साबित करूँगा कि Serializable ठीक एक ट्रांज़ैक्शन को निरस्त करता है और इसका पुनः प्रयास परिवर्तन को अस्वीकार करता है। काउंटर डिज़ाइन को केवल एक कंडीशनल अपडेट की अनुमति देनी चाहिए। अंत में, मैं सीरियलाइज़ेशन विफलताओं, डेडलॉक, लॉक प्रतीक्षाओं और काउंटर विचलन की निगरानी करूँगा ताकि सुरक्षा का दावा किसी भाग्यशाली परीक्षण कार्यक्रम पर निर्भर न हो।"

सामान्य गलतियाँ

  • "यह एक ट्रांज़ैक्शन के अंदर है, इसलिए यह सुरक्षित है" → एटॉमिसीटी समवर्ती ट्रांज़ैक्शन में दृश्यता और कमिट क्रम के बारे में कुछ नहीं कहती है → आइसोलेशन स्तर का नाम बताएं और वह इंटरलीविंग लिखें जो इनवेरिएंट को तोड़ती है।
  • राइट स्क्यू को लॉस्ट अपडेट के रूप में मानना → ट्रांज़ैक्शन अलग-अलग पंक्तियों पर लिखते हैं, इसलिए एक पंक्ति पर संस्करण जाँच उनके साझा प्रेडिकेट को कवर नहीं करती है → क्रॉस-रो इनवेरिएंट की पहचान करें और एक सामान्य प्रतिस्पर्धा बिंदु या Serializable का उपयोग करें।
  • यह दावा करना कि Repeatable Read, Serializable के बराबर है → एक स्थिर स्नैपशॉट अभी भी बिना किसी सीरियल स्पष्टीकरण के एक संयुक्त परिणाम उत्पन्न कर सकता है → PostgreSQL Repeatable Read द्वारा अनुमत सीरियलाइज़ेशन विसंगति का वर्णन करें।
  • प्रत्येक ट्रांज़ैक्शन की डॉक्टर पंक्ति को लॉक करना → Alice और Bob के लॉक आपस में संघर्ष नहीं करते हैं → एक रोस्टर पंक्ति को लॉक करें, एक काउंटर पंक्ति को अपडेट करें, या SSI को प्रेडिकेट निर्भरता का पता लगाने दें।
  • एक पुराने Repeatable Read स्नैपशॉट के बाद FOR UPDATE जोड़ना → एक अपरिवर्तित गार्ड पंक्ति को लॉक करने से ट्रांज़ैक्शन स्नैपशॉट रीफ़्रेश नहीं होता है → Read Committed के तहत लॉक करें और फिर से पढ़ें, या Serializable या एक परिवर्तनशील काउंटर पंक्ति का उपयोग करें।
  • 40001 के बाद केवल COMMIT या अंतिम SQL स्टेटमेंट का पुनः प्रयास करना → व्यावसायिक निर्णय अभी भी पुराने स्नैपशॉट से आया था → पूर्ण ट्रांज़ैक्शन और SQL चुनने वाले सभी एप्लिकेशन लॉजिक को फिर से चलाएं।
  • बिना किसी सीमा के तुरंत पुनः प्रयास करना → व्यस्त अनुरोध समकालिक रूप से टकराते हैं और डेटाबेस लोड को बढ़ाते हैं → जिटर्ड बैकऑफ़, अधिकतम प्रयास गणना और एक समग्र समय सीमा का उपयोग करें।
  • कमिट से पहले एक अधिसूचना भेजना → एक सीरियलाइज़ेशन रिट्राई अधिसूचना को एक से अधिक बार भेज सकता है → बाहरी प्रभावों को टालें और इडेम्पोटेंट उपभोक्ताओं के साथ एक ट्रांज़ैक्शनल आउटबॉक्स का उपयोग करें।
  • विवरण में बाईपास राइट्स की अनुमति देते हुए एक काउंटर बनाए रखना → active_count वास्तविक ऑन-कॉल गणना से भटक जाता है और स्थिति का कोई अर्थ नहीं रह जाता है → प्रत्येक राइट पथ को समान ट्रांज़ैक्शन प्रोटोकॉल का उपयोग करने दें और लगातार इसका मिलान करें।

अनुवर्ती प्रश्न और उत्तर

अनुवर्ती 1: एक एटॉमिक UPDATE नकारात्मक इन्वेंट्री को रोक सकता है लेकिन इस समस्या को स्वचालित रूप से क्यों हल नहीं कर सकता?

एक एकल इन्वेंट्री पंक्ति UPDATE inventory SET stock = stock - 1 WHERE stock > 0 का उपयोग कर सकती है। शर्त और राइट लक्ष्य एक ही पंक्ति हैं, इसलिए समवर्ती अपडेटर नवीनतम संस्करण पर शर्त की पुन: जाँच करते हैं। यहाँ, शर्त कई पंक्तियों की गणना है जबकि राइट एक डॉक्टर को छूता है। समान गुण प्राप्त करने के लिए, गणना को एक रोस्टर पंक्ति पर मूर्त रूप (materialize) दें या प्रेडिकेट निर्भरताओं का पता लगाने के लिए Serializable का उपयोग करें।

अनुवर्ती 2: यदि Serializable विफलता दर उच्च है तो क्या होगा?

पहले ट्रांज़ैक्शन को छोटा करें, प्रेडिकेट को इंडेक्स करें, ट्रांज़ैक्शन से नेटवर्क कॉल हटाएं, और प्रति शिफ्ट संघर्षों को मापें। यदि कुछ व्यस्त शिफ्ट अधिकांश 40001 त्रुटियों के लिए जिम्मेदार हैं, तो केवल उन राइट्स को रोस्टर कंडीशनल अपडेट पर ले जाएं ताकि प्रतिस्पर्धा एक पंक्ति पर कतारबद्ध हो सके। यदि प्रत्येक शिफ्ट में अत्यधिक प्रतिस्पर्धा है, तो बैच संचालन और ट्रांज़ैक्शन सीमाओं पर पुनर्विचार करें। एक बड़ी रिट्राई सीमा क्षमता विश्लेषण का विकल्प नहीं है।

अनुवर्ती 3: क्या केवल एक CHECK बाधा यह गारंटी दे सकती है कि एक डॉक्टर ऑन कॉल बना रहे?

एक doctor_shifts पंक्ति पर रखी गई जाँच उस पंक्ति के फ़ील्ड को मान्य कर सकती है, लेकिन वह पंक्ति स्वतंत्र रूप से यह साबित नहीं कर सकती है कि उसी शिफ्ट के लिए कोई अन्य डॉक्टर सक्रिय रहता है या नहीं। इनवेरिएंट को रोस्टर active_count पर प्रोजेक्ट करने के बाद, CHECK (active_count >= 1) एक डेटाबेस-स्तरीय गार्ड बन जाता है। विवरण और काउंटर को अभी भी उसी ट्रांज़ैक्शन में बदलने और मिलान करने की आवश्यकता है।

अनुवर्ती 4: यदि रोस्टर गणना घटाने के बाद सेवा क्रैश हो जाती है तो क्या होगा?

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

अनुवर्ती 5: यदि नियम दो डेटाबेस में संग्रहीत डॉक्टरों को कवर करता है तो क्या होगा?

सिंगल-डेटाबेस Serializable और पंक्ति लॉक केवल उस डेटाबेस के लिए दृश्यमान डेटा की रक्षा करते हैं। एक आधिकारिक राइट सीमा चुनें, जैसे कि एक दृढ़ता से सुसंगत (strongly consistent) रोस्टर सेवा जिसकी स्थिति अन्य डेटाबेस में एसिंक्रोनस रूप से प्रोजेक्ट की जाती है, या एक वितरित ट्रांज़ैक्शन पेश करें और इसके समन्वय, उपलब्धता और विलंबता लागत को स्वीकार करें। यदि दोनों डेटाबेस स्थानीय रूप से निर्णय लेते हैं और एसिंक्रोनस रूप से मर्ज होते हैं, तो डिज़ाइन को स्पष्ट रूप से अस्थायी उल्लंघनों की अनुमति देनी चाहिए और क्षतिपूर्ति को परिभाषित करना चाहिए; एकल-डेटाबेस सुरक्षा प्रमाण अब लागू नहीं होता है।

सार्वजनिक स्रोत

संबंधित प्रश्न