PostgreSQL advisory locks से समवर्ती-सुरक्षित वर्कफ़्लो
प्रैक्टिकल पैटर्न, SQL स्निपेट, और साधारण चेक्स के साथ जानें कि कैसे PostgreSQL advisory locks approvals, billing और schedulers में डबल-प्रोसेसिंग रोकते हैं।

असली समस्या: दो प्रोसेस एक ही काम कर देते हैं
डबल-प्रोसेसिंग तब होती है जब एक ही आइटम दो बार हाथ लगता है क्योंकि दो अलग actor दोनों सोचते हैं कि वह जिम्मेदार है। वास्तविक ऐप्स में यह दिखता है जैसे ग्राहक से दो बार चार्ज होना, एक approval दो बार लागू होना, या "invoice ready" ईमेल का दो बार जाना। टेस्ट में सब ठीक दिख सकता है, पर असली ट्रैफ़िक में टूट सकता है।
यह आमतौर पर तब होता है जब टाइमिंग कड़ी हो जाती है और एक से ज्यादा चीज़ें एक ही समय में कार्रवाई कर सकती हैं:
- दो workers एक ही जॉब को एक ही समय में उठा लेती हैं।
- एक retry ट्रिगर होता है क्योंकि नेटवर्क कॉल धीमा था, पर पहली कोशिश अभी भी चल रही होती है।
- यूज़र Approve पर दो बार क्लिक कर देता है क्योंकि UI एक सेकंड के लिए फ्रीज़ हुआ था।
- deploy के बाद या क्लॉक ड्रिफ्ट के कारण दो schedulers ओवरलैप कर लेते हैं।
- मोबाइल ऐप टाइमआउट पर दोबारा भेज दे तो एक टैप दो रिक्वेस्ट बन सकता है।
दर्दनाक हिस्सा यह है कि हर actor अपने आप में “तर्कसंगत” व्यवहार कर रहा है। बग दोनों के बीच का गैप है: दोनों नहीं जानते कि दूसरा पहले से ही उसी रिकॉर्ड को प्रोसेस कर रहा है।
लक्ष्य सरल है: किसी भी दिए गए आइटम (एक order, एक approval request, एक invoice) के लिए केवल एक actor को ही क्रिटिकल काम करने दिया जाए। बाकी को या तो थोड़ी देर इंतज़ार करना चाहिए या पीछे हटकर फिर कोशिश करनी चाहिए।
PostgreSQL advisory locks मदद कर सकते हैं। ये आपको हल्का-फुल्का तरीका देते हैं यह बताने का कि “मैं आइटम X पर काम कर रहा हूँ” — और वह वही डेटाबेस इस्तेमाल करते हैं जिसपर आप पहले से भरोसा करते हैं।
पर अपेक्षाएँ सेट करें। एक लॉक पूरा queue सिस्टम नहीं है। यह जॉब शेड्यूल नहीं करेगा, ऑर्डर गारंटी नहीं करेगा, और संदेश स्टोर नहीं करेगा। यह उस वर्कफ़्लो हिस्से के चारों ओर एक safety gate है जो कभी भी दो बार नहीं चलना चाहिए।
PostgreSQL advisory locks क्या हैं (और क्या नहीं)
PostgreSQL advisory locks एक तरीका हैं यह सुनिश्चित करने का कि केवल एक worker ही किसी काम का टुकड़ा एक समय में करे। आप एक lock key चुनते हैं (जैसे “invoice 123”), डेटाबेस से उसे लॉक करने को कहते हैं, काम करते हैं, फिर उसे रिलीज़ कर देते हैं।
"advisory" शब्द मायने रखता है। Postgres को आपके key का मतलब पता नहीं होता, और यह कुछ भी ऑटोमैटिकली प्रोटेक्ट नहीं करेगा। यह सिर्फ एक तथ्य ट्रैक करता है: या तो यह key लॉक है या नहीं। आपका कोड key फ़ॉर्मैट पर सहमत होना चाहिए और जोखिम वाले हिस्से को चलाने से पहले लॉक लेना होगा।
यह उपयोगी है row locks से तुलना करने पर। Row locks (जैसे SELECT ... FOR UPDATE) वास्तविक table rows की रक्षा करते हैं। जब काम साफ़ तौर पर एक row से मैप होता है तब वे बेहतरीन होते हैं। Advisory locks उस key की रक्षा करते हैं जिसे आप चुनते हैं — यह तब उपयोगी है जब वर्कफ़्लो कई टेबल छूता है, बाहरी सेवाओं को कॉल करता है, या तब शुरू होता है जब row अभी मौजूद भी नहीं है।
Advisory locks तब उपयोगी हैं जब आपको चाहिए:
- प्रति entity एक-एक करके कार्रवाई (एक approval प्रति request, एक charge प्रति invoice)
- अलग-अलग ऐप सर्वरों के बीच समन्वय बिना किसी अतिरिक्त locking सर्विस जोड़े
- उस वर्कफ़्लो स्टेप के चारों ओर सुरक्षा जो एक single row update से बड़ी हो
वे अन्य सुरक्षा उपकरणों के रिप्लेसमेंट नहीं हैं। वे ऑपरेशन्स को idempotent नहीं बनाते, बिज़नेस नियम लागू नहीं करते, और अगर कोई कोड पथ लॉक लेना भूल जाए तो duplicates नहीं रोकेंगे।
इन्हें अक्सर "लाइटवेट" कहा जाता है क्योंकि आप इन्हें schema changes या अतिरिक्त इंफ्रास्ट्रक्चर के बिना उपयोग कर सकते हैं। कई मामलों में, आप सिर्फ एक लॉक कॉल जोड़कर डबल-प्रोसेसिंग को ठीक कर सकते हैं और बाकी डिज़ाइन को उसी तरह रख सकते हैं।
वे लॉक प्रकार जो आप वास्तव में उपयोग करेंगे
जब लोग "PostgreSQL advisory locks" कहते हैं, तो वे आमतौर पर कुछ फ़ंक्शंस की बात कर रहे होते हैं। सही फ़ंक्शन चुनना यह बदल देता है कि त्रुटियों, timeouts, और retries पर क्या होगा।
सत्र vs ट्रांज़ैक्शन लॉक
एक session-level लॉक (pg_advisory_lock) तब तक रहता है जब तक डेटाबेस कनेक्शन रहता है। यह लंबे समय चलने वाले workers के लिए सुविधाजनक हो सकता है, पर इसका मतलब यह भी है कि अगर आपका ऐप क्रैश हो और pooled कनेक्शन लटका रहे तो लॉक टिका रह सकता है।
एक transaction-level लॉक (pg_advisory_xact_lock) वर्तमान ट्रांज़ैक्शन से जुड़ा होता है। जब आप commit या rollback करते हैं, PostgreSQL इसे अपने आप रिलीज़ कर देता है। अधिकांश request-response वर्कफ़्लोज़ (approvals, billing clicks, admin actions) के लिए यह सुरक्षित डिफ़ॉल्ट है क्योंकि रिलीज़ करना भूलना मुश्किल होता है।
blocking vs try-lock
Blocking कॉल तब तक इंतज़ार करते हैं जब तक लॉक उपलब्ध न हो जाए। सरल है, पर यह एक वेब रिक्वेस्ट को अटकाने जैसा महसूस करवा सकता है अगर दूसरी सत्र लॉक पकड़ रही हो।
Try-lock कॉल तुरंत लौटते हैं:
pg_try_advisory_lock(session-level)pg_try_advisory_xact_lock(transaction-level)
UI क्रियाओं के लिए try-lock अक्सर बेहतर होता है। अगर लॉक लिया हुआ है, तो आप एक साफ़ संदेश दे सकते हैं जैसे "पहले से प्रोसेस हो रहा है" और उपयोगकर्ता से फिर प्रयास करने को कह सकते हैं।
shared vs exclusive
Exclusive लॉक “एक-एक करके” होते हैं। Shared लॉक कई होल्डर्स को अनुमति देते हैं लेकिन एक exclusive लॉक को ब्लॉक कर देते हैं। अधिकतर डबल-प्रोसेसिंग समस्याओं में exclusive लॉक का उपयोग होता है। Shared लॉक तब उपयोगी होते हैं जब कई readers एक साथ आगे बढ़ सकते हैं, पर कोई writer अकेले चलना चाहिए।
लॉक रिलीज़ कैसे होता है
रिलीज़ प्रकार पर निर्भर करता है:
- Session locks: disconnect पर रिलीज़ होते हैं, या स्पष्ट रूप से
pg_advisory_unlockसे - Transaction locks: ट्रांज़ैक्शन खत्म होते ही ऑटोमैटिकली रिलीज़ हो जाते हैं
सही lock key चुनना
एक advisory लॉक तभी काम करेगा जब हर worker बिल्कुल उसी key को लॉक करने की कोशिश करे जो उसी काम के लिए है। अगर एक कोड पाथ "invoice 123" लॉक करता है और दूसरा "customer 45" लॉक करता है, तब आप फिर भी duplicates देखेंगे।
शुरू में उस "चीज़" को नाम दें जिसकी आप रक्षा करना चाहते हैं। इसे ठोस रखें: एक invoice, एक approval request, एक scheduled task run, या एक ग्राहक का मासिक बिलिंग साइकिल। यह चुनाव तय करेगा कि आप कितनी concurrency की अनुमति देते हैं।
जोखिम के अनुरूप स्कोप चुनें
अधिकांश टीमें इन में से किसी एक पर आकर बंद होती हैं:
- प्रति रिकॉर्ड: approvals और invoices के लिए सबसे सुरक्षित (lock by invoice_id या request_id)
- प्रति ग्राहक/अकाउंट: जब क्रियाओं को प्रति ग्राहक क्रमबद्ध करना ज़रूरी हो (billing, credit changes)
- प्रति वर्कफ़्लो स्टेप: जब अलग-अलग स्टेप्स समानांतर चल सकते हैं, पर हर स्टेप को एक-एक करके चलना चाहिए
स्कोप को प्रोडक्ट निर्णय के रूप में लें, डेटाबेस विवरण के रूप में नहीं। “Per record” डबल-क्लिक्स से दो बार चार्ज होने से रोकता है। “Per customer” दो बैकग्राउंड जॉब्स को overlapping statements जेनरेट करने से रोकता है।
एक स्थिर key रणनीति चुनें
आम तौर पर आपके पास दो विकल्प होते हैं: दो 32-bit integers (अक्सर namespace + id के रूप में), या एक 64-bit integer (bigint), जो कभी-कभी किसी string ID को hashing करके बनाया जाता है।
दो-int keys मानकीकृत करना आसान बनाते हैं: वर्कफ़्लो के लिए एक fixed namespace नंबर चुनें (उदाहरण के तौर पर approvals vs billing), और रिकॉर्ड ID को दूसरे मान के रूप में उपयोग करें।
Hashing तब उपयोगी हो सकता है जब आपका identifier UUID हो, पर आपको एक छोटा collision risk स्वीकार करना होगा और हर जगह लगातार होना चाहिए।
जो भी चुने, फ़ॉर्मैट लिखकर केंद्रीकृत रखें। "लगभग वही key" दो जगहों पर होना duplicates वापस लाने का सामान्य तरीका है।
चरण-दर-चरण: एक-एक करके प्रोसेसिंग के लिए सुरक्षित पैटर्न
एक अच्छा advisory-lock वर्कफ़्लो सरल है: lock, verify, act, record, commit। लॉक खुद बिज़नेस नियम नहीं है। यह एक guardrail है जो उस नियम को भरोसेमंद बनाता है जब दो worker एक ही रिकॉर्ड पर एक साथ पहुँचते हैं।
एक व्यावहारिक पैटर्न:
- जहाँ परिणाम atomic होना चाहिए, वहाँ ट्रांज़ैक्शन खोलें।
- विशिष्ट यूनिट ऑफ वर्क के लिए लॉक हासिल करें। ट्रांज़ैक्शन-स्कोप्ड लॉक (
pg_advisory_xact_lock) पसंद करें ताकि यह अपने आप रिलीज़ हो जाये। - डेटाबेस में स्थिति फिर से जांचें। यह मत मानें कि आप पहले हैं। रिकॉर्ड अभी भी eligible है या नहीं, इसकी पुष्टि करें।
- काम करें और डेटाबेस में एक टिकाऊ “done” मार्कर लिखें (status अपडेट, लेज़र एंट्री, audit row)।
- commit करें और लॉक को जाने दें। अगर आपने session-level लॉक लिया है तो कनेक्शन को पूल में लौटाने से पहले explicit unlock करें।
उदाहरण: दो ऐप सर्वर लगभग एक ही सेकंड में "Approve invoice #123" प्राप्त करते हैं। दोनों शुरू होते हैं, पर केवल एक 123 के लिए लॉक पाता है। विजेता चेक करता है कि invoice #123 अभी भी pending है, उसे approved कर देता है, audit/payment रिकॉर्ड लिखता है, और commit कर देता है। दूसरा सर्वर या तो fast fail कर जाता है (try-lock) या पहले के खत्म होने के बाद लॉक पाकर देखता है कि status पहले ही approved है और बिना कुछ बदले बाहर निकल जाता है।
advisory locks कहाँ फिट बैठते हैं: approvals, billing, schedulers
Advisory locks सबसे अच्छा उस समय काम करते हैं जब नियम सरल हो: किसी विशिष्ट चीज़ के लिए, सिर्फ़ एक प्रोसेस को "जीतने" दिया जाए। आप अपना मौजूद डेटाबेस और ऐप कोड रखते हैं, पर एक छोटा सा gate जोड़ते हैं जो race conditions को ट्रिगर करना मुश्किल बना देता है।
Approvals
Approvals क्लासिक concurrency traps हैं। दो रिव्यूअर्स (या वही व्यक्ति जो दो बार क्लिक कर दे) मिलीसेकंड के भीतर Approve कर सकते हैं। request ID पर लॉक होने पर केवल एक ट्रांज़ैक्शन state change करेगा। बाकी तुरंत परिणाम सीख लेंगे और साफ़ संदेश दिखा सकते हैं जैसे "पहले ही approved" या "पहले ही rejected"।
यह ग्राहक पोर्टल और एडमिन पैनल्स में आम है जहाँ कई लोग एक ही queue देख रहे होते हैं।
Billing
बिलिंग आमतौर पर कड़क नियम चाहती है: एक invoice पर सिर्फ एक payment attempt, भले ही retries हों। नेटवर्क टाइमआउट यूज़र को फिर से Pay पर क्लिक करने के लिए प्रेरित कर सकता है, या बैकग्राउंड retry पहले की कोशिश के चलते हुए चल सकता है।
invoice ID पर लॉक यह सुनिश्चित करता है कि एक ही समय में सिर्फ़ एक पाथ payment provider से बात करे। दूसरी कोशिश "payment in progress" लौट सकती है या नवीनतम payment status पढ़ सकती है। इससे duplicate work और दोगुने चार्ज का जोखिम कम होता है।
Schedulers और बैकग्राउंड वर्कर्स
Multi-instance सेटअप में schedulers गलती से एक ही विंडो को parallel चला सकते हैं। job name प्लस time window (उदाहरण के लिए, "daily-settlement:2026-01-29") पर लॉक करके यह सुनिश्चित करें कि सिर्फ़ एक instance ही इसे चलाए।
उसी दृष्टिकोण का उपयोग उन workers के लिए भी किया जा सकता है जो तालिका से आइटम पुल करते हैं: आइटम ID पर लॉक करें ताकि सिर्फ़ एक worker ही उसे प्रोसेस करे।
लोग आमतौर पर इन चीज़ों पर लॉक लगाते हैं: एक approval request ID, एक invoice ID, job name + time window, export के लिए customer ID (एक समय में एक export), या retries के लिए एक unique idempotency key।
एक वास्तविक उदाहरण: पोर्टल में दोहरी-approval रोकना
मान लीजिए एक approval request पोर्टल में है: एक purchase order इंतज़ार कर रहा है और दो मैनेजर एक ही सेकंड में Approve पर क्लिक कर देते हैं। बिना सुरक्षा के दोनों रिक्वेस्ट "pending" पढ़ सकते हैं और दोनों "approved" लिख सकते हैं, जिससे duplicate audit entries, duplicate notifications, या downstream काम दो बार ट्रिगर हो सकता है।
PostgreSQL advisory locks आपको इस क्रिया को प्रति approval एक-एक करके करने का सीधा तरीका देता है।
फ्लो
जब API को approve action मिलता है, तो वह पहले approval id के आधार पर लॉक लेता है (ताकि अलग approvals अभी भी समानांतर में प्रोसेस हो सकें)।
एक आम पैटर्न है: approval_id पर लॉक करें, current status पढ़ें, status अपडेट करें, फिर audit रिकॉर्ड लिखें — यह सब एक ट्रांज़ैक्शन में।
BEGIN;
-- One-at-a-time per approval_id
SELECT pg_try_advisory_xact_lock($1) AS got_lock; -- $1 = approval_id
-- If got_lock = false, return "someone else is approving, try again".
SELECT status FROM approvals WHERE id = $1 FOR UPDATE;
-- If status != 'pending', return "already processed".
UPDATE approvals
SET status = 'approved', approved_by = $2, approved_at = now()
WHERE id = $1;
INSERT INTO approval_audit(approval_id, actor_id, action, created_at)
VALUES ($1, $2, 'approved', now());
COMMIT;
ऊपर का कोड ब्लॉक जैसा है; कोड ब्लॉक के अंदर सामग्री अनुवादित नहीं की गई है।
दूसरा क्लिक क्या अनुभव करता है
दूसरी रिक्वेस्ट या तो लॉक नहीं पा पाती (तो वह जल्दी "Already being processed" लौटाती है) या वह पहले खत्म होने के बाद लॉक पाकर देखती है कि status पहले ही approved है और बिना कुछ बदले बाहर निकल जाती है। किसी भी तरह से आप डबल-प्रोसेसिंग से बचते हैं और UI को responsive रखते हैं।
डिबग के लिए पर्याप्त लॉग रखें ताकि हर प्रयास को ट्रेस किया जा सके: request id, approval id और कम्प्यूट की गई lock key, actor id, outcome (lock_busy, already_approved, approved_ok), और timing।
इंतज़ार, timeouts, और retries को बिना ऐप जमाये हैंडल करना
लॉक के लिए इंतज़ार करना हानिरहित लगता है जब तक कि वह स्पिनिंग बटन, अटका हुआ worker, या एक backlog न बन जाए जो कभी साफ़ न हो। जब आप लॉक प्राप्त नहीं कर पाते, तो जहाँ इंसान इंतज़ार कर रहा हो वहां फेल-फास्ट करें और जहाँ इंतज़ार सुरक्षित है वहां ही इंतज़ार रखें।
यूज़र क्रियाओं के लिए: try-lock और साफ़ जवाब
अगर कोई Approve या Charge पर क्लिक कर रहा है, तो उनकी रिक्वेस्ट को सेकंडों तक ब्लॉक न करें। try-lock का उपयोग करें ताकि ऐप तुरंत उत्तर दे सके।
एक व्यावहारिक तरीका यह है: लॉक की कोशिश करें, और अगर वह फेल हो तो एक स्पष्ट "busy, try again" उत्तर लौटाएं (या आइटम स्टेट रिफ्रेश कराएं)। इससे timeouts कम होते हैं और बार-बार क्लिक करने से रोका जा सकता है।
लॉक किया हुआ सेक्शन छोटा रखें: स्थिति सत्यापित करें, स्टेट चेंज लागू करें, commit करें।
बैकग्राउंड जॉब्स के लिए: blocking ठीक है, पर सीमा लगाएं
Schedulers और workers के लिए blocking ठीक हो सकता है क्योंकि कोई इंसान इंतज़ार नहीं कर रहा। पर फिर भी आपको लिमिट चाहिए, वरना एक धीमा जॉब पूरी फ़्लीट को जाम कर देगा।
timeout का उपयोग करें ताकि worker ने हार मानकर आगे बढ़ सके:
SET lock_timeout = '2s';
SET statement_timeout = '30s';
SELECT pg_advisory_lock(123456);
इसके अलावा जॉब के लिए अधिकतम अपेक्षित रनटाइम सेट करें। अगर billing आमतौर पर 10 सेकंड से कम में खत्म होता है, तो 2 मिनट को एक incident मानें। स्टार्ट टाइम, जॉब id, और कितना समय लॉक रखा गया यह ट्रैक करें। अगर आपका जॉब रनर cancellation सपोर्ट करता है तो उन टास्क्स को कैंसिल करें जो कैप से ज्यादा चल रहे हों ताकि session खत्म हो और लॉक रिलीज़ हो।
रिट्राईज़ का प्लान जानबूझ कर करें। जब लॉक हासिल न हो, तो तय करें कि अगला कदम क्या होगा: थोड़ी देर में backoff के साथ फिर से शेड्यूल करें (थोड़ी randomness डालें), इस चक्र के लिए best-effort काम छोड़ दें, या आइटम को contended के रूप में मार्क करें अगर बार-बार विफलताएँ ध्यान देने योग्य हैं।
सामान्य गलतियाँ जो लॉक फंसा देती हैं या duplicates बनाती हैं
सबसे आम आश्चर्य session-level लॉक होते हैं जो कभी रिलीज़ नहीं होते। कनेक्शन पूल कनेक्शनों को खुला रखता है, इसलिए एक session वह जीवनकाल पार कर सकता है। अगर आप session लॉक लेते हैं और unlock करना भूल जाते हैं, तो लॉक तब तक बने रह सकता है जब तक कनेक्शन recycle न हो। दूसरे workers इंतजार करेंगे (या फेल होंगे) और यह समझना मुश्किल हो सकता है कि क्यों।
एक और duplicate का कारण है लॉक लेना पर स्थिति की जाँच न करना। लॉक केवल यह सुनिश्चित करता है कि एक worker एक समय में critical section चलाए। यह गारंटी नहीं देता कि रिकॉर्ड अभी भी eligible है। हमेशा उसी ट्रांज़ैक्शन के अंदर फिर से जाँच करें (उदाहरण के लिए, pending होने की पुष्टि करें)।
लॉक keys भी टीमों को फँसाते हैं। अगर एक सर्विस order_id पर लॉक करती है और दूसरी सेवा उसी वास्तविक संसाधन के लिए अलग तरीके से कम्प्यूट की गई key पर लॉक करती है, तो अब आपके पास दो अलग लॉक होंगे। दोनों पाथ्स एक साथ चल सकते हैं, जो सुरक्षा का एक गलत आभास देता है।
लॉन्ग लॉक होल्ड्स अक्सर खुद की वजह से होते हैं। अगर आप लॉक पकड़े रहते हुए धीमे नेटवर्क कॉल करते हैं (payment provider, email/SMS, webhooks), तो छोटा गार्डरेईल bottleneck बन सकता है। लॉक किए हुए हिस्से को तेज़ डेटाबेस कार्यों तक सीमित रखें: स्थिति सत्यापित करें, नया स्टेट लिखें, अगले कदम का रिकॉर्ड रखें। फिर side effects को ट्रांज़ैक्शन commit होने के बाद ट्रिगर करें।
आखिर में, advisory locks idempotency या database constraints का रिप्लेसमेंट नहीं हैं। इन्हें ट्रैफिक लाइट की तरह मानें, न कि प्रूफ सिस्टम। जहाँ फिट बैठता हो वहाँ unique constraints और idempotency keys का उपयोग करें।
शिप करने से पहले त्वरित चेकलिस्ट
Advisory locks को एक छोटे कॉन्ट्रैक्ट की तरह समझें: टीम में हर कोई जानता हो कि लॉक का क्या मतलब है, यह क्या प्रोटेक्ट करता है, और लॉक रहते हुए क्या करने की अनुमति है।
एक छोटा चेकलिस्ट जो अधिकांश समस्याओं को पकड़ लेता है:
- हर रिसोर्स के लिए एक स्पष्ट lock key, लिखित और हर जगह reuse किया हुआ
- irreversible चीज़ों (payments, emails, external API calls) से पहले लॉक हासिल करें
- लॉक होने के बाद और लिखने से पहले स्थिति फिर से जांचें
- लॉक किए हुए हिस्से को छोटा और मापने योग्य रखें (लॉक वेट और execution time लॉग करें)
- तय करें कि हर पाथ के लिए "lock busy" का क्या मतलब है (UI message, backoff के साथ retry, skip)
अगले कदम: पैटर्न लागू करें और मेंटेनेबल रखें
सबसे पहले उस जगह का चुनाव करें जहाँ duplicates सबसे ज्यादा नुकसान पहुँचाते हैं। अच्छे शुरुआती टारगेट वे क्रियाएँ हैं जो पैसे लगती हैं या स्थायी रूप से स्टेट बदलती हैं, जैसे "charge invoice" या "approve request"। सिर्फ़ उस क्रिटिकल सेक्शन को advisory lock से घेरें, फिर व्यवहार पर भरोसा होने पर आसपास के स्टेप्स तक विस्तार करें।
बुनियादी observability जल्दी जोड़ें। जब कोई worker लॉक न पा सके तो लॉग करें, औरLocked work कितना समय लेती है यह मापें। अगर लॉक वेट spike करे तो आमतौर पर इसका मतलब है कि critical section बहुत बड़ा है या उसके अंदर कोई slow query छिपी हुई है।
लॉक्स तब सबसे अच्छे होते हैं जब वे डेटा सुरक्षा के ऊपर हों, न कि उसकी जगह। साफ़ status फील्ड रखें (pending, processing, done, failed) और जहाँ संभव हो उन्हें constraints के साथ बैक करें। अगर worst moment पर retry होता है, तो unique constraint या idempotency key दूसरी रक्षा की पंक्ति बन सकती है।
अगर आप AppMaster (appmaster.io) में वर्कफ़्लो बना रहे हैं, तो आप वही पैटर्न लागू कर सकते हैं: क्रिटिकल स्टेट चेंज को एक ट्रांज़ैक्शन के अंदर रखें और "finalize" स्टेप से पहले transaction-level advisory lock लेने के लिए एक छोटा SQL स्टेप जोड़ें।
Advisory locks तब तक अच्छे हैं जब तक कि आपको वाकई queue फीचर्स (priorities, delayed jobs, dead-letter handling) की ज़रूरत न हो, भारी contention हो और स्मार्ट parallelism चाहिए, आपको अलग-अलग डेटाबेस के बीच समन्वय करना हो जहाँ साझा Postgres न हो, या आपको कड़े isolation नियम चाहिए। लक्ष्य उबाऊ भरोसेमंदता है: पैटर्न को छोटा, consistent, लॉग्स में दिखाई देने योग्य, और constraints से समर्थित रखें।
सामान्य प्रश्न
जब आपको किसी विशिष्ट यूनिट ऑफ वर्क के लिए "एक ही समय में सिर्फ एक actor" चाहिए — जैसे किसी request को approve करना, किसी invoice को charge करना, या किसी scheduled विंडो को चलाना — तब advisory lock का प्रयोग करें। यह तब खासकर मददगार है जब कई app इंस्टेंसेस एक ही आइटम तक पहुँच सकते हैं और आप अलग लॉकिंग सर्विस नहीं जोड़ना चाहते।
Row locks वास्तविक तालिका पंक्तियों की रक्षा करते हैं और तब बढ़िया हैं जब पूरा ऑपरेशन साफ़ तौर पर एक row update पर मैप होता है। Advisory locks आपके चुने हुए key की रक्षा करते हैं, इसलिए वे तब काम आते हैं जब वर्कफ़्लो कई टेबल छूता है, बाहरी सेवाओं को कॉल करता है, या अंतिम row बनने से पहले शुरू होता है।
अनुरोध/प्रतिक्रिया कार्यों के लिए डिफॉल्ट रूप से pg_advisory_xact_lock (transaction-level) का उपयोग करें क्योंकि यह commit या rollback पर अपने आप रिलीज़ हो जाता है। pg_advisory_lock (session-level) का उपयोग तब ही करें जब आपको सचमुच लै़क को ट्रांज़ैक्शन के बाहर रखना हो और आप सुनिश्चित हों कि आप कनेक्शन को पूल में लौटाने से पहले हमेशा unlock करेंगे।
UI-ड्रिवन क्रियाओं के लिए try-lock (pg_try_advisory_xact_lock) पसंद करें ताकि रिक्वेस्ट फास्ट फेल हो सके और साफ़ “already processing” जैसा उत्तर दिया जा सके। बैकग्राउंड वर्कर्स के लिए blocking lock ठीक हो सकता है, लेकिन lock_timeout सेट करके इसे सीमित करें ताकि एक फंसा हुआ टास्क सब कुछ रोक न दे।
उस चीज़ पर लॉक करें जो सबसे छोटी इकाई है जिसे दोहराया नहीं जाना चाहिए — आमतौर पर “एक invoice” या “एक approval request”। बहुत व्यापक लॉक (जैसे per customer) थ्रूपुट घटा सकता है; बहुत संकीर्ण या असंगत कुंजी करने पर फिर भी डुप्लिकेट्स आ सकते हैं।
एक स्थिर key फ़ॉर्मेट चुनें और उसी का हर जगह उपयोग करें जहाँ वही क्रिटिकल क्रिया हो सकती है। सामान्य तरीका दो integers का उपयोग करना है: एक फिक्स्ड namespace नंबर और entity ID, ताकि अलग-अलग workflows गलती से एक-दूसरे को ब्लॉक न कर दें जबकि सही समन्वय बना रहे।
नहीं। लॉक केवल समवर्ती निष्पादन को रोकता है; यह यह साबित नहीं करता कि ऑपरेशन को दोहराना सुरक्षित है। आपको ट्रांज़ैक्शन के अंदर स्थिति फिर से जांचनी चाहिए (उदाहरण के लिए, आइटम अभी भी pending है) और जहाँ उपयुक्त हो वहाँ unique constraints या idempotency का उपयोग करना चाहिए।
लॉक के अंदर काम छोटा और डेटाबेस-केंद्रित रखें: लॉक प्राप्त करें, योग्यता फिर से जांचें, नया स्टेट लिखें, और commit करें। धीमी साइड-इफेक्ट्स (पेमेंट, ईमेल, वेबहुक) को commit के बाद या आउटबॉक्स-स्टाइल रिकॉर्ड के माध्यम से ट्रिगर करें ताकि आप नेटवर्क देरी के दौरान लॉक न पकड़े रखें।
सबसे आम कारण session-level लॉक होता है जो pooled कनेक्शन पर रखा गया और कभी unlock नहीं हुआ क्योंकि कोड में कहीं गलती थी। ट्रांज़ैक्शन-लेवल लॉक को प्राथमिकता दें, और अगर session locks का उपयोग करना ही है तो सुनिश्चित करें कि pg_advisory_unlock कनेक्शन पूल को लौटने से पहले भरोसेमंद तरीके से चले।
लॉग करें: entity ID और कॉम्प्यूट की गई lock key, क्या लॉक मिला, उसे पाने में कितना समय लगा, और ट्रांज़ैक्शन कितना चला। साथ ही outcome लॉग करें जैसे lock_busy, already_processed, या processed_ok ताकि आप contention को असली duplicates से अलग कर सकें।


