कॉनकरेंसी-सुरक्षित इनवॉइस नंबरिंग जो डुप्लिकेट और गैप को रोकती है
व्यावहारिक तरीके जानें ताकि कई उपयोगकर्ता एक साथ इनवॉइस या टिकट बना सकें बिना डुप्लिकेट या अनपेक्षित गैप के।

जब दो लोग एक ही समय पर रिकॉर्ड बनाते हैं तो क्या गलत होता है
सोचिए ऑफिस में चार बजकर पचपन मिनट (4:55 PM) है। दो लोग एक इनवॉइस पूरा करते हैं और एक-दूसरे से केवल एक सेकंड के भीतर Save दबाते हैं। दोनों स्क्रीन पर अस्थायी रूप से “इनवॉइस #1042” दिखता है। एक रिकॉर्ड सफल होता है, दूसरा फेल हो सकता है, या बदतर, दोनों ही उसी नंबर के साथ सेव हो जाते हैं। यह सबसे आम वास्तविक समस्या है: लोड के दौरान ही दिखाई देने वाले डुप्लिकेट नंबर।
टिकट भी वैसा ही व्यवहार करते हैं। दो एजेंट एक ही ग्राहक के लिए एक नया टिकट एक ही समय में बनाते हैं, और आपका सिस्टम "अगला नंबर चुनने" के लिए सबसे हालिया रिकॉर्ड देखता है। अगर दोनों रिक्वेस्ट्स किसी भी लिखने से पहले एक ही "लेटेस्ट" वैल्यू पढ़ लें, तो दोनों ही एक ही अगला नंबर चुन सकते हैं।
दूसरी समस्या अधिक सूक्ष्म है: स्किप हुए नंबर। आप #1042 के बाद #1044 देख सकते हैं और #1043 गायब हो सकता है। यह अक्सर किसी एरर या रिट्राई के बाद होता है। एक रिक्वेस्ट नंबर रिज़र्व कर लेती है, फिर सेव विफल हो जाती है—क्योंकि वैलिडेशन एरर, टाइमआउट, या यूज़र ने टैब बंद कर दिया। या बैकग्राउंड जॉब नेटवर्क समस्या के बाद रिट्राई कर देता है और नया नंबर ले लेता है जबकि पहला प्रयास पहले ही एक नंबर ले चुका था।
इनवॉइस के लिये यह मायने रखता है क्योंकि नंबरिंग आपके ऑडिट ट्रेल का हिस्सा है। अकाउंटेंट हर इनवॉइस की अनूठी पहचान की उम्मीद करते हैं, और ग्राहक भुगतान या सपोर्ट ईमेल में इनवॉइस नंबर का जिक्र कर सकते हैं। टिकटों के लिए नंबर वह हैंडल है जिसका हर कोई बातचीत, रिपोर्ट और एक्सपोर्ट में उपयोग करता है। डुप्लिकेट्स भ्रम पैदा करते हैं। मिसिंग नंबर रिव्यू के दौरान सवाल उठाते हैं, भले ही कुछ गलत न हुआ हो।
यहाँ एक महत्वपूर्ण अपेक्षा पहले से तय कर लें: हर नंबरिंग मेथड दोनों—कॉनकरेंसी-सुरक्षित और गैप-रहित—नहीं हो सकता। कॉनकरेंसी-सुरक्षित नंबरिंग (कई उपयोगकर्ताओं के साथ भी डुप्लिकेट नहीं) हासिल की जा सकती है और यह अनिवार्य होनी चाहिए। गैपलेस नंबरिंग भी संभव है, लेकिन इसके लिए अतिरिक्त नियम चाहिए और अक्सर यह ड्राफ्ट, फेल्यर और कैंसलेशंस को संभालने के तरीके बदल देता है।
समस्या को फ्रेम करने का अच्छा तरीका यह पूछना है कि आपके नंबरों से आपको क्या गारंटी चाहिए:
- हमेशा अनूठा होना चाहिए (यूनिक, हमेशा)
- ज्यादातर बढ़ता हुआ होना चाहिए (अच्छा हो तो)
- कभी भी स्किप न होना चाहिए (सिर्फ अगर आपने इसके लिए डिज़ाइन किया हो)
एक बार जब आप नियम चुन लेते हैं, तकनीकी समाधान चुनना आसान हो जाता है।
डुप्लिकेट और गैप क्यों होते हैं
अधिकांश ऐप्स एक सरल पैटर्न का पालन करते हैं: यूज़र Save दबाता है, ऐप अगला इनवॉइस या टिकट नंबर मांगता है, फिर वह नया रिकॉर्ड उस नंबर के साथ इन्सर्ट होता है। यह सुरक्षित लगता है क्योंकि यह अकेले उपयोगकर्ता पर बिल्कुल सही काम करता है।
मुसीबत तब शुरू होती है जब दो सेव लगभग एक साथ होती हैं। दोनों रिक्वेस्ट्स "अगला नंबर लें" चरण तक पहुँच सकती हैं इससे पहले कि कोई भी इन्सर्ट पूरा हो। अगर दोनों पढ़ते हैं और एक ही "अगला" वैल्यू देखते हैं, तो दोनों एक ही नंबर लिखने की कोशिश कर सकते हैं। यही रेस कंडीशन है: परिणाम टाइमिंग पर निर्भर करता है, लॉजिक पर नहीं।
एक सामान्य टाइमलाइन कुछ इस तरह दिखती है:
- रिक्वेस्ट A अगला नंबर पढ़ती है: 1042
- रिक्वेस्ट B अगला नंबर पढ़ती है: 1042
- रिक्वेस्ट A इनवॉइस 1042 इन्सर्ट करती है
- रिक्वेस्ट B इनवॉइस 1042 इन्सर्ट करती है (या UNIQUE नियम ब्लॉक कर दे तो फेल)
डुप्लिकेट तब होते हैं जब डेटाबेस किसी भी तरह से दूसरे इन्सर्ट को नहीं रोकता। अगर आप केवल ऐप कोड में "क्या यह नंबर लिया जा चुका है?" जाँच करते हैं, तब भी चेक और इन्सर्ट के बीच रेस हार सकते हैं।
गैप्स एक अलग समस्या हैं। वे तब होते हैं जब सिस्टम कोई नंबर "रिज़र्व" कर दे, पर रिकॉर्ड असल और कमिटेड इनवॉइस या टिकट बनकर सामने न आए। आम कारण हैं फेल्ड पेमेंट, लेट मिलने वाला वैलिडेशन एरर, टाइमआउट, या यूज़र का टैब बंद कर देना। भले ही इन्सर्ट फेल हो और कुछ सेव न हुआ हो, नंबर पहले ही खपत हो चुका हो सकता है।
छिपी हुई कॉनकरेंसी इसको और बदतर बनाती है क्योंकि यह शायद ही सिर्फ "दो इंसान Save क्लिक कर रहे हैं" तक सीमित रहती है। आपके पास हो सकता है:
- पैरेलल में रिकॉर्ड बनाने वाले API क्लाइंट
- बैच में चलने वाली इम्पोर्ट्स
- रात में इनवॉइस जनरेट करने वाले बैकग्राउंड जॉब
- स्पॉटी कनेक्शन्स वाले मोबाइल ऐप्स से होने वाले रिट्राई
तो मूल कारण हैं: (1) कई रिक्वेस्ट्स के एक ही काउंटर वैल्यू पढ़ने पर टाइमिंग कॉन्फ्लिक्ट, और (2) नंबर को असाइन कर लिया जाना इससे पहले कि आप सुनिश्चित हों कि ट्रांज़ैक्शन सफल होगा। किसी भी कॉनकरेंसी-सुरक्षित नंबरिंग प्लान को तय करना होगा कि आप कौन सा परिणाम सहन कर सकते हैं: कोई डुप्लिकेट नहीं, कोई गैप नहीं, या दोनों—और किन घटनाओं के दौरान (ड्राफ्ट, रिट्राई, कैंसिल)।
समाधान चुनने से पहले अपना नंबरिंग नियम तय करें
कॉनकरेंसी-सुरक्षित इनवॉइस नंबरिंग डिज़ाइन करने से पहले लिखें कि नंबर का बिज़नेस में क्या मतलब होगा। सबसे आम गलती यह है कि पहले तकनीकी तरीका चुन लिया जाए, और बाद में पता चले कि अकाउंटिंग या कानूनी नियम कुछ और अपेक्षा करते हैं।
शुरू करें इन दो लक्ष्यों को अलग कर के जो अक्सर मिक्स हो जाते हैं:
- यूनिक: कोई भी दो इनवॉइस या टिकट कभी एक ही नंबर साझा न करें।
- गैपलेस: नंबर यूनिक हों और साथ ही कड़ियाँ लगातार हों (कोई गायब नहीं)।
कई वास्तविक सिस्टम केवल यूनिक-ओनली का लक्ष्य रखते हैं और गैप स्वीकार कर लेते हैं। गैप सामान्य कारणों से हो सकते हैं: यूज़र ड्राफ्ट छोड़ दे, पेमेंट रिजेक्ट हो जाए, या रिकॉर्ड बनाकर वॉयड कर दिया जाए। हेल्पडेस्क टिकटों के लिए गैप सामान्यतः मायने नहीं रखते। यहां तक कि इनवॉइस के लिए भी कई टीमें गैप स्वीकार कर लेती हैं यदि वे उन्हें ऑडिट ट्रेल (voided, canceled, test, आदि) के साथ समझा सकें। गैपलेस नंबरिंग संभव है, पर ये अतिरिक्त नियम थोपता है और अक्सर उपयोग में रुकावट लाता है।
इसके बाद काउंटर के स्कोप का निर्णय लें। छोटे शब्दों का अंतर डिजाइन को बहुत बदल देता है:
- सबके लिए एक ग्लोबल सीक्वेंस, या प्रत्येक कंपनी/टेनेंट के लिए अलग-अलग सीक्वेंस?
- हर साल रीसेट (2026-000123) या कभी रीसेट न करें?
- इनवॉइस vs क्रेडिट नोट vs टिकट के लिए अलग सीरीज?
- क्या आपको ह्यूमन-फ़्रेंडली फॉर्मेट चाहिए (प्रिफिक्स, सेपरेटर), या सिर्फ एक इंटरनल नंबर?
एक ठोस उदाहरण: एक SaaS प्रोडक्ट जिसमें कई क्लाइंट कंपनियाँ हैं, हो सकता है कि इनवॉइस नंबर हर कंपनी के लिए यूनिक और कैलेंडर वर्ष के अनुसार रीसेट होने चाहिए, जबकि टिकट ग्लोबली यूनिक हों और कभी रीसेट न हों। ये दो अलग काउंटर हैं जिनके अलग नियम होंगे, भले ही UI समान लगे।
अगर आपको वास्तव में गैपलेस चाहिए, तो स्पष्ट रूप से बताएं कि नंबर असाइन होने के बाद कौन-कौन सी घटनाएँ स्वीकार्य हैं। उदाहरण के लिए, क्या इनवॉइस को delete किया जा सकता है, या केवल canceled किया जा सकता है? क्या यूज़र ड्राफ्ट सेव कर सकते हैं बिना नंबर के और नंबर सिर्फ फाइनल अप्प्रोवल पर असाइन होगा? ये विकल्प अक्सर डेटाबेस तकनीक से ज़्यादा मायने रखते हैं।
एक छोटा स्पेक लिखें:
- कौन से रिकॉर्ड टाइप सीक्वेंस इस्तेमाल करते हैं?
- कब नंबर "यूज़्ड" माना जाएगा (ड्राफ्ट, सेंट, पेड)?
- स्कोप क्या है (ग्लोबल, प्रति कंपनी, प्रति वर्ष, प्रति सीरीज़)?
- वॉइड्स और करेक्शंस को आप कैसे हैंडल करेंगे?
AppMaster में, इस तरह के नियम आपके डेटा मॉडल और बिज़नेस प्रोसेस फ़्लो के पास होने चाहिए, ताकि टीम API, वेब UI और मोबाइल में एक ही व्यवहार लागू करे बिना सरप्राइज़ के।
सामान्य तरीके और हर एक की गारंटी
जब लोग "इनवॉइस नंबरिंग" की बात करते हैं, वे अक्सर दो अलग लक्ष्य मिक्स कर देते हैं: (1) कभी भी वही नंबर दो बार न जनरेट होना, और (2) कभी गैप न होना। अधिकांश सिस्टम पहले वाले को आसानी से गारंटीकृत कर सकते हैं। दूसरा कठिन है, क्योंकि गैप किसी भी समय दिख सकते हैं जब ट्रांज़ैक्शन फेल हो, ड्राफ्ट छोड़ा जाए, या रिकॉर्ड वॉयड हो।
तरीका 1: डेटाबेस सीक्वेंस (तेज़ यूनिकनेस)
PostgreSQL सीक्वेंस सबसे सरल तरीका है यूनिक, बढ़ते हुए नंबर पाने का। यह अच्छा स्केल करता है क्योंकि डेटाबेस सीक्वेंस वैल्यूज़ तेजी से दे सकता है, भले ही बहुत सारे उपयोगकर्ता एक साथ रिकॉर्ड बना रहे हों।
आप क्या पाते हैं: यूनिकनेस और क्रम (ज्यादातर बढ़ता हुआ)। आप जो नहीं पाते: गैपलेस नंबरिंग। अगर इन्सर्ट फेल हो जाता है तो वह नंबर "बर्न" हो सकता है और गैप दिखाई देगा।
तरीका 2: यूनिक कॉन्स्ट्रेंट प्लस रिट्राई (डेटाबेस को निर्णय दें)
यहां आप ऐप लॉजिक से एक कैंडिडेट नंबर बनाते हैं, सेव करते हैं, और कोई UNIQUE कॉन्स्ट्रेंट डुप्लिकेट को रिजेक्ट कर देता है। अगर कॉन्फ्लिक्ट होता है तो आप नया नंबर लेकर रिट्राई करते हैं।
यह काम कर सकता है, पर उच्च कॉनकरेंसी में यह शोर पैदा कर देता है। अधिक रिट्राई, अधिक फेल्ड ट्रांज़ैक्शंस और डिबग करना कठिन स्पाइक्स हो सकते हैं। यह तब भी गैपलेस नंबरिंग नहीं देता जब तक आप सख्त रिज़र्वेशन रूल्स जोड़ें, जो जटिलता बढ़ाते हैं।
तरीका 3: काउंटर रो के साथ लॉकिंग (गैपलेस के लिए)
अगर आपको सचमुच गैपलेस नंबर चाहिए, सामान्य पैटर्न एक समर्पित काउंटर टेबल है (प्रत्येक स्कोप के लिए एक रो)। आप उस रो को ट्रांज़ैक्शन में लॉक करते हैं, इसे इंक्रीमेंट करते हैं, और नया वैल्यू लेते हैं।
यह सामान्य डेटाबेस डिज़ाइन में सबसे नज़दीकी तरीका है गैपलेस पाने का, पर इसकी कीमत है: यह एक "हॉट स्पॉट" बनाता है जिस पर सभी राइटर वेट करेंगे। यह ऑपरेशनल गलतियों (लंबे ट्रांज़ैक्शंस, टाइमआउट, डेडलॉक्स) के लिए जोखिम भी बढ़ाता है।
तरीका 4: अलग रिज़र्वेशन सर्विस (विशेष मामलों के लिए)
एक स्टैंडअलोन "नंबरिंग सर्विस" कई ऐप्स या डेटाबेस में नियम केंद्रीकृत कर सकती है। यह तब ही वर्थइट होता है जब आपके पास कई सिस्टम नंबर जारी कर रहे हों और आप लिखने को कंसॉलिडेट न कर सकें।
ट्रेडऑफ़ ऑपरेशनल रिस्क है: आपने एक और सर्विस जोड़ दी है जिसे सही, हाईली अवेलेबल और कंसिस्टेंट बनाना होगा।
व्यावहारिक रूप से गारंटियों के साथ सोचें:
- सीक्वेंस: यूनिक, तेज, गैप स्वीकार करता है
- यूनिक + रिट्राई: यूनिक, कम लोड पर सरल, उच्च लोड पर थ्रैश कर सकता है
- लॉक्ड काउंटर रो: गैपलेस हो सकता है, भारी कॉनकरेंसी पर धीमा
- अलग सर्विस: सिस्टम्स के पार फ्लेक्सिबिलिटी, सबसे जटिल और अधिक फेल्योर मोड
अगर आप यह AppMaster जैसे नो-कोड टूल में बना रहे हैं, तो वही विकल्प लागू होते हैं: डेटाबेस correctness का स्रोत है। ऐप लॉजिक रिट्राई और क्लियर एरर मैसेज में मदद कर सकता है, पर अंतिम गारंटी कॉन्स्ट्रेंट्स और ट्रांज़ैक्शंस से आनी चाहिए।
स्टेप-बाय-स्टेप: सीक्वेंस और यूनिक कॉन्स्ट्रेंट से डुप्लिकेट रोकें
अगर आपका मुख्य लक्ष्य डुप्लिकेट रोकना है (गैप्स गारंटीकृत नहीं करना), तो सबसे सरल भरोसेमंद पैटर्न यह है: डेटाबेस एक आंतरिक ID जेनरेट करे, और कस्टमर-फेसिंग नंबर पर यूनिकनेस लागू हो।
दो अवधारणाओं को अलग रखें। जॉइन्स, एडिट्स और एक्सपोर्ट के लिए एक डेटाबेस-जनरेटेड वैल्यू (identity/sequence) का प्रयोग करें। invoice_no या ticket_no को अलग कॉलम रखें जो लोगों को दिखाया जाता है।
PostgreSQL में व्यावहारिक सेटअप
यहाँ एक सामान्य PostgreSQL तरीका है जो "नेक्स्ट नंबर" लॉजिक को डेटाबेस के अंदर रखता है, जहाँ कॉनकरेंसी सही तरीके से संभाली जाती है।
-- Internal, never-shown primary key
create table invoices (
id bigint generated always as identity primary key,
invoice_no text not null,
created_at timestamptz not null default now()
);
-- Business-facing uniqueness guarantee
create unique index invoices_invoice_no_uniq on invoices (invoice_no);
-- Sequence for the visible number
create sequence invoice_no_seq;
अब डिस्प्ले नंबर इन्सर्ट के समय जेनरेट करें (न कि select max(invoice_no) + 1 करके)। एक सरल पैटर्न यह है कि INSERT के अंदर ही सीक्वेंस वैल्यू को फ़ॉर्मैट करें:
insert into invoices (invoice_no)
values (
'INV-' || lpad(nextval('invoice_no_seq')::text, 8, '0')
)
returning id, invoice_no;
यदि 50 यूज़र्स एक साथ "Create invoice" क्लिक करते हैं, तो भी हर इन्सर्ट अलग सीक्वेंस वैल्यू पाता है, और यूनिक इंडेक्स किसी भी आकस्मिक डुप्लिकेट को ब्लॉक कर देता है।
कॉलेज़न होने पर क्या करें
सादे सीक्वेंस के साथ, कॉलेज़न दुर्लभ होते हैं। वे आमतौर पर तब होते हैं जब आप "प्रति वर्ष रीसेट", "प्रति टेनेन्ट" या उपयोगकर्ता-एडिटेबल नंबर जैसे अतिरिक्त नियम जोड़ते हैं। इसलिए यूनिक कॉन्स्ट्रेंट अभी भी महत्वपूर्ण है।
एप्लिकेशन स्तर पर यूनिक-वायलेशन एरर को एक छोटे रिट्राई लूप के साथ हैंडल करें। इसे साधारण और सीमित रखें:
- इन्सर्ट कोशिश करें
- अगर invoice_no पर यूनिक कॉन्स्ट्रेंट एरर मिले, तो फिर से कोशिश करें
- सीमित प्रयासों के बाद साफ़ एरर दिखाएँ
यह काम करता है क्योंकि रिट्राई केवल तब ट्रिगर होंगे जब कुछ असामान्य होता है—जैसे दो अलग कोड पाथ्स एक ही फ़ॉर्मैटेड नंबर बना रहे हों।
रेस विंडो छोटा रखें
नंबर को UI में न बनाएं, और न ही पहले रीड करके बाद में रिज़र्व करें। इसे डेटाबेस राइट के जितना पास हो सके जेनरेट करें।
यदि आप AppMaster के साथ PostgreSQL का उपयोग कर रहे हैं, तो id को Data Designer में identity primary key के रूप में मॉडल करें, invoice_no के लिए यूनिक कॉन्स्ट्रेंट जोड़ें, और create फ़्लो के दौरान invoice_no जेनरेट करें ताकि यह इन्सर्ट के साथ साथ हो। इस तरह डेटाबेस सत्यता का स्रोत बने रहता है और कॉनकरेंसी समस्याएँ वहीं रहती हैं जहाँ PostgreSQL सबसे मजबूत है।
स्टेप-बाय-स्टेप: रो लॉकिंग से गैपलेस काउंटर बनाना
अगर आपको सचमुच गैपलेस नंबर चाहिए (कोई मिसिंग इनवॉइस या टिकट नंबर नहीं), तो आप ट्रांज़ैक्शनल काउंटर टेबल और रो लॉकिंग का उपयोग कर सकते हैं। विचार साधारण है: एक समय में केवल एक ट्रांज़ैक्शन ही किसी दिए हुए स्कोप के अगला नंबर ले सकता है, इसलिए नंबर क्रम में दिए जाते हैं।
पहले अपना स्कोप तय करें। कई टीमें प्रति कंपनी, प्रति वर्ष, या प्रति सीरीज़ अलग सीक्वेंस चाहती हैं। काउंटर टेबल हर स्कोप के लिए आख़िरी उपयोग किया गया नंबर स्टोर करती है।
यहाँ PostgreSQL रो लॉक के साथ कॉन्करेंसी-सुरक्षित नंबरिंग का व्यावहारिक पैटर्न है:
- एक तालिका बनाएं, जैसे
number_counters, जिसमेंcompany_id,year,series,last_numberजैसी कॉलम हों और(company_id, year, series)पर यूनिक की हो। - एक डेटाबेस ट्रांज़ैक्शन शुरू करें।
- अपने स्कोप के लिए काउंटर रो को
SELECT last_number FROM number_counters WHERE ... FOR UPDATEसे लॉक करें। next_number = last_number + 1गणना करें और काउंटर रो कोlast_number = next_numberसे अपडेट करें।- अगले नंबर का उपयोग करके इनवॉइस या टिकट इन्सर्ट करें और कमिट करें।
कुंजी FOR UPDATE है। लोड में, आपको डुप्लिकेट नहीं मिलते। आप यह भी नहीं पाएंगे कि "दो उपयोगकर्ताओं ने एक ही नंबर लिया", क्योंकि दूसरी ट्रांज़ैक्शन पहले के कमिट (या रोलबैक) तक वही काउंटर रो नहीं पढ़ सकती। इसके बजाय, दूसरी रिक्वेस्ट थोड़ी देर इंतज़ार करेगी। यह वेट गैपलेस होने की कीमत है।
नए स्कोप की प्रारम्भिक सेटअप
यह भी ज़रूरी है कि जब कोई नया स्कोप आए (नई कंपनी, नया वर्ष, नई सीरीज़) तो आप उसका योजना रखें। दो सामान्य विकल्प:
- पहले से काउंटर रो बनाकर रखें (उदाहरण: दिसंबर में अगले वर्ष की रो बनाएं)।
- डिमांड पर बनाएँ: काउंटर रो
last_number = 0के साथ इन्सर्ट करने की कोशिश करें, और यदि पहले से मौजूद है तो सामान्य लॉक-एंड-इंक्रीमेंट फ्लो पर वापस जाएँ।
यदि आप यह कोई-कोड टूल जैसे AppMaster में बना रहे हैं, तो पूरा "लॉक, इंक्रीमेंट, इन्सर्ट" सीक्वेंस एक ट्रांज़ैक्शन के अंदर रखें, ताकि यह सब या तो हो या कुछ भी न हो।
किनारों के मामले: ड्राफ्ट, फेल्ड सेव, कैंसिलेशन और एडिट्स
अधिकांश नंबरिंग बग गंदी हिस्सों में दिखते हैं: ड्राफ्ट जो कभी पोस्ट नहीं होते, सेव जो फेल हो जाती हैं, इनवॉइस जो वॉयड हो जाती हैं, और रिकॉर्ड जो तब एडिट होते हैं जब किसी ने पहले नंबर देख लिया हो। यदि आप कॉनकरेंसी-सुरक्षित नंबरिंग चाहते हैं, तो आपको यह स्पष्ट नियम चाहिए कि नंबर कब "रियल" बनता है।
सबसे बड़ा निर्णय टाइमिंग है। अगर आप नंबर असाइन कर देते हैं जैसे ही कोई "New invoice" पर क्लिक करता है, तो आपको ड्राफ्ट छोड़े जाने पर गैप मिलेगा। यदि आप केवल तभी नंबर असाइन करते हैं जब इनवॉइस फाइनल हो (posted, issued, sent—जो भी आपके बिज़नेस में "फाइनल" बोले), तो नंबर अधिक कड़े और समझाने में आसान रहते हैं।
फेल्ड सेव और रोलबैक वे जगहें हैं जहाँ अपेक्षाएँ अक्सर डेटाबेस व्यवहार से टकराती हैं। एक सामान्य सीक्वेंस के साथ, एक बार नंबर लिया जा चुका तो वह लिया ही गया माना जाता है, भले ही ट्रांज़ैक्शन बाद में फेल हो जाए। यह सामान्य और सुरक्षित है, पर गैप बनता है। अगर आपकी पॉलिसी गैपलेस मांगती है, तो नंबर केवल अंतिम चरण पर और केवल ट्रांज़ैक्शन कमिट होने पर असाइन होना चाहिए। इसका मतलब प्रायः एकल काउंटर रो लॉक करके, अंतिम नंबर लिखकर और कमिट करना है। अगर कोई स्टेप फेल होता है, तो कुछ भी असाइन नहीं होता।
कैंसिलेशन और वॉयड्स कभी भी नंबर "रीयूस" नहीं करना चाहिए। नंबर रखें और स्टेटस बदल दें। ऑडिटर्स और ग्राहक उम्मीद करते हैं कि इतिहास स्थिर रहे, भले ही कोई दस्तावेज़ ठीक किया गया हो।
एडिट्स सरल हैं: एक बार जब नंबर सिस्टम के बाहर दिखना शुरू हो जाए, तो उसे स्थायी मानें। शेयर, एक्सपोर्ट या प्रिंट होने के बाद इनवॉइस या टिकट का नंबर कभी न बदलें। अगर आपको करेक्शन चाहिए, तो नया दस्तावेज़ बनाएं और पुराने का संदर्भ दें (जैसे क्रेडिट नोट या रिप्लेसमेंट टिकट), पर इतिहास को फिर से न लिखें।
कई टीमें यह व्यावहारिक नियम अपनाती हैं:
- ड्राफ्ट का कोई फाइनल नंबर नहीं होता (इंटर्नल ID या "DRAFT" का उपयोग करें)।
- नंबर केवल "पोस्ट/इशू" पर असाइन करें, और वह भी उसी ट्रांज़ैक्शन में जब स्टेटस बदले।
- वॉइड्स और कैंसिलेशन नंबर रखते हैं, पर एक स्पष्ट स्टेटस और कारण जोड़े जाते हैं।
- प्रिंट/ईमेल किए गए नंबर कभी बदलते नहीं।
- इम्पोर्ट्स मौलिक नंबरों को सुरक्षित रखें और काउंटर को इम्पोर्ट किए गए अधिकतम मान के बाद सेट करें।
माइग्रेशन और इम्पोर्ट्स विशेष देखभाल मांगते हैं। यदि आप किसी दूसरे सिस्टम से आ रहे हैं, तो मौजूदा इनवॉइस नंबर उसी तरह लाएँ, फिर अपना काउंटर उस अधिकतम इम्पोर्ट किए गए वैल्यू के बाद से शुरू करें। विभिन्न फ़ॉर्मैट्स के साथ क्या करना है (जैसे साल के अनुसार अलग प्रिफिक्स) यह भी तय करें। सामान्यतः बेहतर है कि आप डिस्प्ले नंबर को वैसा ही रखें जैसा था, और अलग आंतरिक प्राइमरी की रखें।
उदाहरण: एक हेल्पडेस्क तेज़ी से टिकट बनाता है, पर कई ड्राफ्ट होते हैं। टिकट नंबर केवल तब असाइन करें जब एजेंट "Send to customer" क्लिक करे। इससे ड्राफ्ट पर नंबर बर्न होने से बचता है और दिखने वाला सीक्वेंस वास्तविक ग्राहक संचार के साथ संरेखित रहता है। AppMaster जैसे नो-कोड टूल में भी यही विचार लागू होता है: ड्राफ्ट में सार्वजनिक नंबर न रखें, और "submit" बिज़नेस प्रोसेस स्टेप के दौरान ही फाइनल नंबर जेनरेट करें जो सफलतापूर्वक कमिट हो।
डुप्लिकेट या अनपेक्षित गैप का कारण बनने वाली सामान्य गलतियाँ
अधिकांश नंबरिंग समस्याएँ एक साधारण विचार से आती हैं: नंबर को एक डिस्प्ले वैल्यू की तरह व्यवहार करना बजाय साझा स्टेट की तरह। जब कई लोग एक साथ सेव करते हैं, सिस्टम को अगले नंबर का निर्णय करने के लिए एक स्पष्ट जगह चाहिए, और विफलता पर एक स्पष्ट नियम चाहिए।
एक क्लासिक गलती है SELECT MAX(number) + 1 का ऐप कोड में उपयोग। यह एकल-यूज़र परीक्षण में सही लगता है, पर दो रिक्वेस्ट्स दोनों MAX पढ़ सकते हैं इससे पहले कि कोई कमिट करे। दोनों अगला वैल्यू जेनरेट करते हैं, और आपको डुप्लिकेट मिलता है। भले ही आप "चेक फिर रिट्राई" जोड़ दें, पिक पीक के दौरान आप अतिरिक्त लोड और अजीब स्पाइक्स बना सकते हैं।
डुप्लिकेट का एक और सामान्य स्रोत है क्लाइंट-साइड नंबर जनरेशन (ब्राउज़र या मोबाइल) सेव से पहले। क्लाइंट नहीं जानता कि अन्य यूज़र्स क्या कर रहे हैं, और यदि सेव फेल हो जाए तो वह नंबर सुरक्षित रूप से रिज़र्व नहीं कर सकता। क्लाइंट-जेनरेटेड नंबर अस्थायी लेबल्स के लिए ठीक हैं जैसे "Draft 12", पर आधिकारिक इनवॉइस या टिकट IDs के लिए नहीं।
गैप्स उन टीमों को चौंकाते हैं जो मानते हैं कि सीक्वेंस गैपलेस होते हैं। PostgreSQL में, सीक्वेंस यूनिकनेस के लिए डिज़ाइन किए गए हैं, पर परफेक्ट कंटिन्यूटी के लिए नहीं। एक ट्रांज़ैक्शन रोलबैक होने पर, आप प्रीफेच IDs करते समय, या डेटाबेस रीस्टार्ट पर वैल्यूज़ स्किप हो सकती हैं। यह सामान्य व्यवहार है। अगर आपका असल आवश्यकताएं "कोई डुप्लिकेट नहीं" हैं, तो सीक्वेंस + यूनिक कॉन्स्ट्रेंट सही जवाब है। अगर आपका वास्तविक आवश्यकताएं "गैपलेस इनवॉइस नंबर" हैं, तो आपको अलग पैटर्न (आम तौर पर रो लॉकिंग) और थ्रूपुट में ट्रेडऑफ़ स्वीकार करने होंगे।
लॉकिंग तब भी बैकफायर कर सकती है जब यह बहुत व्यापक हो। एक ग्लोबल शेयर लॉक सभी क्रिएट एक्शन्स को लाइन में डाल देता है, भले ही आप काउंटर को कंपनी, लोकेशन, या डॉक्यूमेंट टाइप द्वारा विभाजित कर सकें। इससे पूरा सिस्टम धीमा हो सकता है और यूज़र्स को लगेगा कि सेव "अचानक" फँस जाता है।
जाँच के योग्य गलतियाँ:
MAX + 1(या "find last number") का उपयोग बिना डेटाबेस-लेवल यूनिक कॉन्स्ट्रेंट के।- अंतिम नंबर को क्लाइंट पर जेनरेट करना, फिर "कॉनफ्लिक्ट्स को बाद में ठीक करना"।
- PostgreSQL सीक्वेंस को गैपलेस मानना और गैप को एरर की तरह ट्रीट करना।
- सब कुछ के लिए एक साझा काउंटर लॉक करना, बजाय उचित पार्टिशनिंग के।
- केवल एक यूज़र के साथ टेस्ट करना, ताकि रेस कंडीशंस लॉन्च तक सामने न आएँ।
व्यावहारिक टेस्ट टिप: 100 से 1,000 रिकॉर्ड पैरेलल में बनाकर एक सरल कॉनकरेंसी टेस्ट चलाएँ और फिर डुप्लिकेट्स व अनअपेक्षित गैप्स की जाँच करें। यदि आप AppMaster जैसे नो-कोड टूल में बना रहे हैं, तो वही नियम लागू होता है: अंतिम नंबर सर्वर-साइड एकल ट्रांज़ैक्शन के अंदर असाइन होना चाहिए, UI फ्लो में नहीं।
शिप करने से पहले जल्दी जाँचें
इनवॉइस या टिकट नंबरिंग रोलआउट करने से पहले उन हिस्सों पर तेज़ी से पास करें जो असली ट्रैफ़िक में अक्सर फेल होते हैं। लक्ष्य सरल है: हर रिकॉर्ड को ठीक एक बिज़नेस नंबर मिले, और आपके नियम 50 लोगों के एक साथ "Create" क्लिक करने पर भी सच रहें।
यहाँ एक प्रैक्टिकल प्री-शिप चेकलिस्ट है:
- सुनिश्चित करें कि बिज़नेस नंबर फ़ील्ड पर डेटाबेस में यूनिक कॉन्स्ट्रेंट है (केवल UI चेक नहीं)। यह आख़िरी रक्षा लाइन है अगर दो रिक्वेस्ट टकराएं।
- सुनिश्चित करें कि नंबर उसी डेटाबेस ट्रांज़ैक्शन के अंदर असाइन हो जो रिकॉर्ड सेव करता है। अगर नंबर असाइनमेंट और सेव अलग अनुरोधों में हैं, तो अंततः आपको डुप्लिकेट्स दिखेंगे।
- अगर आपको गैपलेस चाहिए, तो रिकॉर्ड फाइनल होने पर ही नंबर असाइन करें (उदाहरण: जब इनवॉइस जारी हो, न कि ड्राफ्ट बनते समय)। ड्राफ्ट, छोड़े गए फॉर्म और फेल्ड पेमेंट सबसे आम गैप स्रोत हैं।
- दुर्लभ कॉन्फ्लिक्ट्स के लिए रिट्राई रणनीति जोड़ें। रो लॉकिंग या सीक्वेंस के साथ भी आप कभी-कभी serialization error, deadlock, या यूनिक वायलेशन देख सकते हैं। छोटे बैकऑफ के साथ सरल रिट्राई अक्सर काफी है।
- UI, सार्वजनिक API और बल्क इम्पोर्ट जैसे सभी एंट्री पॉइंट्स पर 20 से 100 समकालिक क्रिएट्स के साथ स्ट्रेस टेस्ट करें। रीयलिस्टिक मिक्सेस का परीक्षण करें जैसे बर्स्ट, धीने नेटवर्क, और डबल सबमिट्स।
एक तेज़ वैरिफिकेशन तरीका यह है कि एक व्यस्त हेल्पडेस्क क्षण का सिमुलेशन करें: दो एजेंट "New ticket" फॉर्म खोलते हैं, एक वेब ऐप से सबमिट करता है जबकि एक इम्पोर्ट जॉब ईमेल इनबॉक्स से टिकट्स डाल रहा हो। रन के बाद जाँच करें कि सभी नंबर यूनिक हैं, सही फॉर्मैट में हैं, और फेल्यर आधा-सेव रिकॉर्ड नहीं छोड़ते।
यदि आप फ्लो AppMaster में बना रहे हैं, तो वही सिद्धांत लागू होते हैं: नंबर असाइनमेंट डेटाबेस ट्रांज़ैक्शन में रखें, PostgreSQL कॉन्स्ट्रेंट्स पर भरोसा करें, और UI एक्शंस तथा API एंडपॉइंट्स दोनों का परीक्षण करें जो एक ही एंटिटी बना सकते हैं। यही जगह है जहाँ कई टीमें मैन्युअल टेस्ट में सुरक्षित महसूस करती हैं पर असली यूज़र्स के आने पर आश्चर्यचकित होती हैं।
उदाहरण: व्यस्त हेल्पडेस्क टिकट्स और आगे क्या करें
सोचिए एक सपोर्ट डेस्क जहाँ एजेंट दिन भर वेब ऐप में टिकट बनाते हैं, जबकि एक इंटीग्रेशन चैट टूल और ईमेल से भी टिकट बनाता है। हर कोई टिकट नंबर जैसे T-2026-000123 की उम्मीद करता है, और अपेक्षा करता है कि हर नंबर ठीक एक टिकट से जुड़ा हो।
एक भोला तरीका है: "लेटेस्ट टिकट नंबर पढ़ो", 1 जोड़ो, नया टिकट सेव करो। लोड में, दो रिक्वेस्ट्स एक ही "लेटेस्ट नंबर" पढ़ सकती हैं इससे पहले कि कोई सेव करे। दोनों एक ही अगला नंबर गणना करेंगे और डुप्लिकेट मिल सकता है। अगर आप फेल पर रिट्राई करके इसे ठीक करने की कोशिश करते हैं, तो अक्सर बिना मतलब के गैप बन जाते हैं।
डेटाबेस डुप्लिकेट रोक सकता है भले ही आपका ऐप कोड भोला हो। ticket_number कॉलम पर यूनिक कॉन्स्ट्रेंट जोड़ें। फिर जब दो रिक्वेस्ट्स एक ही नंबर ट्राय करेंगी, एक इन्सर्ट फेल होगा और आप सफाई से रिट्राई कर सकेंगे। यही कॉनकरेंसी-सुरक्षित इनवॉइस नंबरिंग की जड़ है: यूनिकनेस को UI पर नहीं, डेटाबेस पर भरोसा करें।
गैपलेस नंबरिंग वर्कफ़्लो बदल देती है। अगर आपको गैपलेस चाहिए, तो आप आमतौर पर अंतिम नंबर टिकट बनने पर ही असाइन कर सकते हैं (ड्राफ्ट पर नहीं)। इसके बजाय, टिकट को Draft स्टेट के साथ बनाएं और ticket_number NULL रखें। नंबर केवल फाइनलाइज़ेशन पर असाइन करें ताकि फेल्ड सेव और छोड़े गए ड्राफ्ट नंबर "बर्न" न करें।
एक सरल टेबल डिजाइन इस तरह दिखता है:
- tickets: id, created_at, status (Draft, Open, Closed), ticket_number (nullable), finalized_at
- ticket_counters: key (उदा. "tickets_2026"), next_number
AppMaster में आप इसे Data Designer में मॉडल कर सकते हैं, और फिर Business Process Editor में लॉजिक बनाएँ:
- Create Ticket: ticket insert करें status=Draft और कोई ticket_number न रखें
- Finalize Ticket: एक ट्रांज़ैक्शन शुरू करें, काउंटर रो लॉक करें, ticket_number सेट करें, next_number इंक्रीमेंट करें, कमिट करें
- Test: एक साथ दो "Finalize" क्रियाएँ चलाएँ और पुष्टि करें कि डुप्लिकेट कभी नहीं आते
आगे क्या करें: अपने नियम से शुरू करें (सिर्फ यूनिक बनाम सचमुच गैपलेस)। अगर आप गैप स्वीकार कर सकते हैं, तो डेटाबेस सीक्वेंस + यूनिक कॉन्स्ट्रेंट आमतौर पर पर्याप्त है और फ्लो सरल रहता है। अगर आपको गैपलेस चाहिए, तो नंबरिंग को फाइनलाइज़ेशन स्टेप में ले जाएँ और "ड्राफ्ट" को प्राथमिक दर्जा दें। फिर मल्टीपल एजेंट्स और API बर्स्ट के साथ लोड-टेस्ट करें ताकि रियल यूज़र्स आने से पहले व्यवहार दिख जाए।


