कोड और टेस्ट से पहले PostgreSQL के `NOT NULL`, `CHECK`, `UNIQUE` और `FOREIGN KEY` नियमों पर भरोसा करके एआई-जनित ऐप्स को सुरक्षित रूप से शिप करें।

AI-लिखा कोड अक्सर सही लगता है क्योंकि वह खुश‑पथ (happy path) को संभालता है। असली ऐप्स बीच के गंदे हिस्से में फेल होते हैं: एक फॉर्म null की बजाय खाली स्ट्रिंग भेज देता है, एक बैकग्राउंड जॉब रीट्राय करके उसी रिकॉर्ड को दो बार बना देता है, या एक delete पैरेंट रो हटा देता है और चाइल्ड रो पीछे छोड़ देता है। ये अतार्किक बग नहीं हैं। ये खाली आवश्यक फील्ड, डुप्लिकेट "यूनिक" मान और ऑर्फ़न रो के रूप में दिखते हैं जो किसी भी चीज़ की ओर इशारा नहीं करते।
ये कोड रिव्यू और बेसिक टेस्ट्स से भी छूट जाते हैं एक साधारण कारण से: रिव्यूवर्स आशय पढ़ते हैं, हर एज‑केस नहीं। टेस्ट आमतौर पर कुछ सामान्य उदाहरण कवर करते हैं, न कि हफ्तों के असली यूज़र व्यवहार, CSV इम्पोर्ट, फ्लेकी नेटवर्क रीट्राय या समवर्ती अनुरोध। अगर किसी सहायक ने कोड बनाया है, तो वह छोटे पर महत्वपूर्ण चेक जैसे whitespace ट्रिम करना, रेंज मान्य करना, या रेइस कंडीशन्स से बचाव करना छोड़ सकता है।
"पहले constraints, बाद में कोड" का मतलब है कि आप अनन्य नियम (non‑negotiable rules) डेटाबेस में रखते हैं ताकि जो भी कोड पाथ उसे लिखने की कोशिश करे, खराब डेटा स्टोर न हो सके। आपकी ऐप्स को अभी भी इनपुट वैलिडेट करना चाहिए ताकि बेहतर त्रुटि संदेश मिलें, पर सचाई डेटाबेस ही लागू करे। यही जगह है जहाँ PostgreSQL के constraints चमकते हैं: वे आपको गलती की पूरी श्रेणियों से बचाते हैं।
एक त्वरित उदाहरण: एक छोटा CRM सोचिए। एक AI-जनित इम्पोर्ट स्क्रिप्ट contacts बनाती है। एक रो में email "" (खाली) है, दो रो एक ही ईमेल को अलग केसींग के साथ दोहराते हैं, और एक contact का account_id किसी ऐसे अकाउंट की ओर इशारा कर रहा है जो दूसरे प्रोसेस में डिलीट हो गया था। बिना constraints के, ये सब प्रोडक्शन में उतर सकता है और बाद में रिपोर्ट्स तोड़ सकता है।
सही डेटाबेस नियमों के साथ, वे राइट्स तुरंत फेल हो जाते हैं, स्रोत के नजदीक। आवश्यक फील्ड्स गायब नहीं हो सकते, रीट्राय के दौरान डुप्लीकेट्स नहीं छिप सकते, रिश्ते डिलीट या नॉनएक्सिस्टेंट रिकॉर्ड की ओर नहीं इशारा कर सकते, और मान अनुमति रेंज के बाहर नहीं जा सकते।
Constraints हर बग को रोकते नहीं। वे भ्रमित UI, गलत डिस्काउंट कैलकुलेशन, या धीमी क्वेरी नहीं ठीक करेंगे। पर वे खराब डेटा को चुपचाप जमा होने से रोकते हैं — और अक्सर "AI-जनित एज‑केस बग" यहीं महँगे बन जाते हैं।
आपकी ऐप शायद एक कोडबेस से केवल एक यूज़र तक सीमित नहीं है। एक सामान्य प्रोडक्ट में वेब UI, मोबाइल ऐप, एडमिन स्क्रीन, बैकग्राउंड जॉब्स, CSV इम्पोर्ट और कभी‑कभी थर्ड‑पार्टी एकीकरण होते हैं। हर रास्ता डेटा बना या बदल सकता है। अगर हर रास्ते को वही नियम याद रखने हों, तो किसी न किसी का भूलना तय है।
डेटाबेस एक जगह है जिसे वे सब साझा करते हैं। जब आप इसे अंतिम गेटकीपर मानते हैं, तो नियम अपने आप हर जगह लागू होते हैं। PostgreSQL constraints "हम मानते हैं कि यह हमेशा सच है" को बदल देते हैं — "यह होना चाहिए, या लिखना फेल हो जाएगा।"
AI-जनित कोड के साथ यह और भी ज़रूरी हो जाता है। कोई मॉडल React UI में फॉर्म वैलिडेशन जोड़ सकता है पर बैकग्राउंड जॉब के एक कॉर्नर‑केस को छोड़ सकता है। या वह खुश‑पथ डेटा को सही हैंडल कर सकता है, पर असली कस्टमर कुछ अनपेक्षित दर्ज करे तो टूट सकता है। Constraints उस पल पर इश्यू पकड़ते हैं जब खराब डेटा प्रवेश करने की कोशिश करता है, न कि हफ्तों बाद जब आप अजीब रिपोर्टों को डिबग कर रहे हों।
जब आप constraints छोड़ देते हैं, खराब डेटा अक्सर चुप रहता है। सेव सफल हो जाती है, ऐप आगे बढ़ता है, और समस्या बाद में सपोर्ट टिकट, बिलिंग मिसमैच या एक अब भरोसेमंद न रहे वाला डैशबोर्ड बनकर सामने आती है। क्लीनअप महँगा होता है क्योंकि आप इतिहास ठीक कर रहे होते हैं, न कि एक ही अनुरोध।
खराब डेटा आमतौर पर रोज़मर्रा की स्थितियों से घुसपैठ करता है: नया क्लाइंट ऐप एक फील्ड को खाली भेजता है न कि मिसिंग, एक रीट्राय डुप्लीकेट बनाता है, एक एडमिन एडिट UI चेक को बायपास कर देता है, एक इम्पोर्ट फाइल में inconsistent फॉर्मैटिंग होती है, या दो यूज़र्स एक ही समय में संबंधित रिकॉर्ड अपडेट कर देते हैं।
एक उपयोगी मानसिक मॉडल: केवल तभी डेटा स्वीकार करें जब वह बॉउंडरी पर वैध हो। व्यवहार में, वह बॉउंडरी डेटाबेस को भी शामिल करना चाहिए, क्योंकि डेटाबेस सभी राइट्स देखता है।
NOT NULL सबसे सरल PostgreSQL constraint है, और यह चौंकाने वाली बड़ी क्लास की बग्स को रोकता है। अगर किसी वैल्यू के बिना रो का अर्थ नहीं बनता, तो डेटाबेस से इसे लागू कराएँ।
NOT NULL सामान्यतः identifiers, आवश्यक नाम और timestamps के लिए सही होता है। अगर आप उसके बिना वैध रिकॉर्ड नहीं बना सकते, तो उसे खाली मत रहने दें। एक छोटे CRM में, बिना owner या created time वाला lead "आंशिक lead" नहीं है — वह टूटा हुआ डेटा है जो बाद में अजीब व्यवहार करेगा।
NULL अक्सर AI-जनित कोड में आसानी से घुस आता है क्योंकि "ऑप्शनल" पाथ बनाना आसान होता है और किसी को ध्यान नहीं रहता। एक फॉर्म फील्ड UI में वैकल्पिक हो सकता है, एक API मिसिंग key स्वीकार कर सकता है, और क्रिएट फ़ंक्शन की एक शाखा वैल्यू असाइन करना छोड़ दे सकती है। सब कुछ कम्पाइल हो जाता है और खुश‑पथ टेस्ट पास हो जाता है। फिर असली यूज़र्स CSV इम्पोर्ट करते हैं जिसमें खाली सेल होते हैं, या एक मोबाइल क्लाइंट अलग पेलोड भेजता है, और NULL डेटाबेस में आ जाता है।
एक अच्छा पैटर्न है कि NOT NULL को सिस्टम‑ओन्ड फील्ड्स के लिए एक समझदारी भरा डिफ़ॉल्ट दें:
created_at TIMESTAMP NOT NULL DEFAULT now()status TEXT NOT NULL DEFAULT 'new'is_active BOOLEAN NOT NULL DEFAULT trueडिफ़ॉल्ट हमेशा सही नहीं होते। ऐसे यूज़र‑प्रोवाइडेड फील्ड्स जैसे email या company_name में NOT NULL सिर्फ पूरा करने के लिए डिफ़ॉल्ट मत दे दें। एक खाली स्ट्रिंग NULL से "ज़्यादा वैध" नहीं है—यह सिर्फ समस्या छुपा देता है।
जब आप अनिश्चित हों, तो तय करें कि वैल्यू वास्तव में अज्ञात है या क्या वह अलग स्थिति दर्शाती है। अगर "अभी प्रदान नहीं किया गया" मायने रखता है, तो कहीं और अलग‑स्टेट कॉलम रखें बजाय इसके कि हर जगह NULL की इजाज़त दें। उदाहरण के लिए, phone को nullable रखें, पर phone_status जोड़ें जैसे missing, requested, या verified। इससे अर्थ को पूरे कोड में संगत रखा जा सकेगा।
एक CHECK constraint आपकी तालिका का वादा है: हर रो को एक नियम पूरा करना चाहिए, हर बार। यह एज‑केसेस को चुपचाप रिकॉर्ड बनाने से रोकने का सबसे आसान तरीकों में से एक है जो कोड में ठीक दिखते हैं पर असल में सही नहीं होते।
CHECK constraints सबसे अच्छे उन नियमों के लिए होते हैं जो केवल उसी रो के मानों पर निर्भर करते हैं: संख्यात्मक रेंज, अनुमति प्राप्त मान, और कॉलमों के बीच सरल संबंध।
-- 1) Totals should never be negative
ALTER TABLE invoices
ADD CONSTRAINT invoices_total_nonnegative
CHECK (total_cents \u003e= 0);
-- 2) Enum-like allowed values without adding a custom type
ALTER TABLE tickets
ADD CONSTRAINT tickets_status_allowed
CHECK (status IN ('new', 'open', 'waiting', 'closed'));
-- 3) Date order rules
ALTER TABLE subscriptions
ADD CONSTRAINT subscriptions_date_order
CHECK (end_date IS NULL OR end_date \u003e= start_date);
एक अच्छा CHECK एक नज़र में पढ़ने योग्य होना चाहिए। इसे अपने डेटा के लिए डॉक्यूमेंटेशन की तरह ट्रीट करें। छोटे व्यंजक, स्पष्ट constraint नाम और अनुमानित पैटर्न प्राथमिकता दें।
CHECK हर चीज के लिए सही टूल नहीं है। अगर किसी नियम को अन्य रोज़ की जाँच, aggregate डेटा या तालिकाओं के पार तुलना की ज़रूरत है (उदाहरण के लिए, "एक अकाउंट अपनी प्लान सीमा से अधिक नहीं हो सकता"), तो उस लॉजिक को एप्लिकेशन कोड, ट्रिगर्स या नियंत्रित बैकग्राउंड जॉब में रखें।
UNIQUE नियम सरल है: डेटाबेस दो रोज़ को स्टोर करने से मना कर देता है जिनमें constrained कॉलम (या कॉलम कॉम्बिनेशन) में एक ही वैल्यू हो। यह उन बग्स की पूरी श्रेणी मिटा देता है जहाँ "क्रिएट" पाथ दो बार चलता है, रीट्राय होता है, या दो यूज़र्स एक ही चीज़ एक ही समय में सबमिट करते हैं।
UNIQUE सिर्फ़ उन्हीं सटीक मानों के लिए डुप्लिकेट नहीं होने की गारंटी देता है जिन्हें आपने परिभाषित किया है। यह यह गारंटी नहीं देता कि वैल्यू मौजूद है (NOT NULL), कि उसका फॉर्मैट सही है (CHECK), या कि वह आपकी समानता की धारणा से मेल खाती है (केस, स्पेस, विराम) जब तक कि आप उसे परिभाषित न करें।
आम जगहें जहाँ आप आम तौर पर यूनिकनेस चाहते हैं: उपयोगकर्ता तालिका पर ईमेल, किसी अन्य सिस्टम से external_id, या एक नाम जो अकाउंट के भीतर यूनिक होना चाहिए जैसे (account_id, name)।
एक पकड़: NULL और UNIQUE. PostgreSQL में, NULL को "अज्ञात" माना जाता है, इसलिए UNIQUE constraint के अंतर्गत कई NULL मानों की इजाज़त होती है। यदि आपका मतलब है "वैल्यू मौजूद होना चाहिए और यूनिक भी होना चाहिए", तो UNIQUE के साथ NOT NULL का संयोजन करें।
यूज़र‑फेसिंग identifiers के लिए एक व्यावहारिक पैटर्न है केस‑इनसेंसिटिव यूनिकनेस। लोग "[email protected]" टाइप करेंगे और बाद में "[email protected]" और उन्हें समान उम्मीद होगी।
-- Case-insensitive unique email
CREATE UNIQUE INDEX users_email_unique_ci
ON users (lower(email));
-- Unique contact name per account
ALTER TABLE contacts
ADD CONSTRAINT contacts_account_name_unique UNIQUE (account_id, name);
परिभाषित करें कि "डुप्लिकेट" आपके उपयोगकर्ताओं के लिए क्या मतलब रखता है (केस, व्हाईटस्पेस, per‑account बनाम ग्लोबल), फिर इसे एक बार एन्कोड करें ताकि हर कोड पाथ वही नियम फॉलो करे।
FOREIGN KEY कहता है, "यह रो वहाँ किसी असली रो की ओर इशारा करना चाहिए।" इसके बिना, कोड चुपचाप ऑर्फ़न रिकॉर्ड बना सकता है जो अलग से वैध दिखते हैं पर बाद में ऐप तोड़ देते हैं। उदाहरण के लिए: एक नोट जो किसी कस्टमर की ओर इशारा करता है जिसे डिलीट कर दिया गया, या एक चालान जो एक ऐसे यूज़र आईडी की ओर इशारा करता है जो कभी मौजूद ही नहीं था।
Foreign keys सबसे महत्वपूर्ण होते हैं जब दो क्रियाएँ एक साथ नज़दीक होती हैं: एक डिलीट और एक क्रिएट, टाइमआउट के बाद रीट्राय, या बैकग्राउंड जॉब जो stale डेटा के साथ चलता है। डेटाबेस हर रास्ते से consistency लागू करने में बेहतर है बजाय इसके कि हर ऐप पाथ को याद रहे।
ON DELETE विकल्प को रिश्ते के वास्तविक अर्थ से मिलना चाहिए। पूछें: "अगर पैरेंट गायब हो जाए, तो क्या चाइल्ड मौजूद रहना चाहिए?"
RESTRICT (या NO ACTION): अगर चाइल्ड हैं तो पैरेंट डिलीट करने से रोकना।CASCADE: पैरेंट को डिलीट करने पर चाइल्ड भी डिलीट हो जाएं।SET NULL: चाइल्ड को रखें पर लिंक हटा दें।CASCADE के साथ सावधान रहें। यह सही हो सकता है, पर यह किसी बग या एडमिन क्रिया पर अपेक्षा से ज़्यादा मिटा भी सकता है।
मल्टी‑टेनेंट ऐप्स में, foreign keys सिर्फ correctness के बारे में नहीं होते। वे क्रॉस‑अकाउंट लीकेज को भी रोकते हैं। एक सामान्य पैटर्न है हर tenant‑owned तालिका में account_id शामिल करना और रिश्तों को उसके जरिए जोड़ना।
CREATE TABLE contacts (
account_id bigint NOT NULL,
id bigint GENERATED ALWAYS AS IDENTITY,
PRIMARY KEY (account_id, id)
);
CREATE TABLE notes (
account_id bigint NOT NULL,
id bigint GENERATED ALWAYS AS IDENTITY,
contact_id bigint NOT NULL,
body text NOT NULL,
PRIMARY KEY (account_id, id),
FOREIGN KEY (account_id, contact_id)
REFERENCES contacts (account_id, id)
ON DELETE RESTRICT
);
यह स्कीमा में "किसका क्या है" लागू करता है: एक नोट किसी दूसरे अकाउंट के संपर्क की ओर इशारा नहीं कर सकता, भले ही ऐप कोड (या एक LLM‑जनित क्वेरी) कोशिश करे।
पहले कुछ इनवेरिएंट्स की एक छोटी सूची लिखें: वे तथ्य जो हमेशा सत्य होने चाहिए। इन्हें सादा रखें। "हर contact को एक ईमेल चाहिए।" "स्टेटस कुछ अनुमत मानों में से एक होना चाहिए।" "एक invoice को एक असली कस्टमर से जुड़ा होना चाहिए।" ये वे नियम हैं जिन्हें आप चाहेंगे कि डेटाबेस हर बार लागू करे।
माइग्रेशन्स को छोटे‑छोटे हिस्सों में रोल आउट करें ताकि प्रोडक्शन हैरान न हो:
NOT NULL, UNIQUE, CHECK, FOREIGN KEY)।बिगड़ी हुई चीज़ें ही गन्दा हिस्सा हैं। इसके लिए योजना बनाएं। डुप्लीकेट्स के लिए, एक विजेता रो चुनें, बाकी को मर्ज करें और एक छोटा ऑडिट नोट रखें। गायब आवश्यक फील्ड्स के लिए, केवल तभी सुरक्षित डिफ़ॉल्ट चुनें जब वह सच में सुरक्षित हो; अन्यथा क्वारंटाइन करें। टूटे हुए रिश्तों के लिए, चाइल्ड रोज़ को सही पैरेंट को असाइन करें या खराब रोज़ को हटा दें।
हर माइग्रेशन के बाद, कुछ लिखें जो फेल होना चाहिए यह सत्यापित करने के लिए: एक रो डालें जिसमें आवश्यक मान गायब हो, एक डुप्लिकेट की कुंजी डालें, एक रेंज से बाहर मान डालें, और एक मिसिंग पैरेंट रो को रेफर करें। फेल लिखाई उपयोगी संकेत होते हैं। वे दिखाते हैं कि ऐप कहां "बेस्ट‑एफ़ोर्ट" व्यवहार पर चुपचाप निर्भर कर रहा था।
एक छोटे CRM की कल्पना करें: accounts (आपके SaaS के प्रत्येक कस्टमर), वो कंपनियाँ जिनसे वे काम करते हैं, उन कंपनियों के contacts, और deals जो किसी कंपनी से जुड़े हैं।
यह बिल्कुल वही तरह की ऐप है जिसे लोग चैट टूल से जल्दी जनरेट करते हैं। यह डेमो में ठीक दिखती है, पर असली डेटा जल्दी गंदा हो जाता है। दो बग जल्दी प्रकट होते हैं: डुप्लिकेट contacts (एक ही ईमेल थोड़े अलग तरीकों से दर्ज), और deals बिना company के बनना क्योंकि किसी पाथ ने company_id सेट करना भूल गया। एक और क्लासिक है नकारात्मक deal वैल्यू किसी रिफैक्टर या पार्सिंग गलती के बाद।
समाधान और अधिक if‑statements नहीं है। यह कुछ चुने हुए constraints हैं जो खराब डेटा को स्टोर करना असंभव बना देते हैं।
-- Contacts: prevent duplicates per account
ALTER TABLE contacts
ADD CONSTRAINT contacts_account_email_uniq UNIQUE (account_id, email);
-- Deals: require a company and keep the relationship valid
ALTER TABLE deals
ALTER COLUMN company_id SET NOT NULL,
ADD CONSTRAINT deals_company_fk
FOREIGN KEY (company_id) REFERENCES companies(id);
-- Deals: deal value cannot be negative
ALTER TABLE deals
ADD CONSTRAINT deals_value_nonneg CHECK (deal_value \u003e= 0);
-- A few obvious required fields
ALTER TABLE companies
ALTER COLUMN name SET NOT NULL;
ALTER TABLE contacts
ALTER COLUMN email SET NOT NULL;
यह सख्ती के लिए नहीं है। आप अनिश्चित अपेक्षाओं को ऐसे नियमों में बदल रहे हैं जिन्हें डेटाबेस हर बार लागू कर सकता है, चाहे ऐप का कोई भी हिस्सा डेटा लिखे।
इन constraints के बाद, ऐप सरल हो जाता है। आप उन कई रक्षात्मक चेक्स को हटा सकते हैं जो बाद में डुप्लिकेट्स का पता लगाने की कोशिश करते हैं। असफलताएँ स्पष्ट और कार्रवाई योग्य हो जाती हैं (उदाहरण: "इस अकाउंट के लिए ईमेल पहले से मौजूद है" बनाम बाद की अजीब व्यवहार)। और जब कोई जनरेटेड API रूट फील्ड भूल जाता है या मान गलत हैंडल करता है, तो लिखना तुरंत फेल हो जाता है न कि डेटाबेस चुपचाप करप्ट हो जाता है।
Constraints तब सबसे बेहतर काम करते हैं जब वे व्यवसाय के वास्तविक तरीके से मेल खाते हों। ज़्यादातर दर्द तब आता है जब ऐसे नियम जो उस वक्त "सुरक्षित" लग रहे हों बाद में आश्चर्य पैदा करें।
एक सामान्य फूट‑गन है हर जगह ON DELETE CASCADE का उपयोग करना। यह व्यवस्थित दिखता है जब तक कोई व्यक्ति पैरेंट रो हटा देता है और डेटाबेस सिस्टम के आधे रिकॉर्ड मिटा देता है। Cascades उन डेटा के लिए सही हो सकते हैं जो वास्तव में ओन्ड हैं (जैसे ड्राफ्ट लाइन आइटम जो अकेले कभी मौजूद नहीं होने चाहिए), पर ग्राहकों, चालानों, टिकटों जैसे रिकॉर्ड्स के लिए जोखिमभरा हो सकता है। अगर आप सुनिश्चित नहीं हैं, तो RESTRICT पसंद करें और डिलीट्स को जानबूझकर संभालें।
एक और समस्या है बहुत संकरे CHECK नियम लिखना। "Status must be 'new', 'won', or 'lost'" ठीक लगता है जब तक कि आपको "paused" या "archived" की ज़रूरत न पड़े। एक अच्छा CHECK constraint एक स्थिर सत्य का विवरण देता है, न कि अस्थायी UI विकल्प। "amount \u003e= 0" समय के साथ अच्छा रहता है। "country in (...)" अक्सर नहीं रहता।
जब टीमें जनरेटेड कोड पहले से चलने के बाद constraints जोड़ती हैं तो कुछ आवर्ती मुद्दे दिखते हैं:
CASCADE को क्लीनअप टूल के रूप में मानना और फिर अपेक्षा से ज़्यादा डेटा हट जाना।परफॉर्मेंस पर: PostgreSQL UNIQUE के लिए स्वचालित रूप से एक इंडेक्स बनाता है, पर foreign keys संदर्भित कॉलम पर स्वचालित इंडेक्स नहीं बनाते। बिना उस इंडेक्स के, पैरेंट पर अपडेट और डिलीट्स धीमे हो सकते हैं क्योंकि Postgres चाइल्ड तालिका को स्कैन कर के रेफरेंस चेक करता है।
किसी नियम को कड़ा करने से पहले, उन मौजूदा रोज़ को खोजें जो फेल होंगी, तय करें कि उन्हें ठीक करें या क्वारंटाइन करें, और बदलाव को चरणों में रोल आउट करें।
शिप करने से पहले, हर तालिका के लिए पाँच मिनट लें और लिखें कि क्या हमेशा सच होना चाहिए। अगर आप इसे सरल अंग्रेज़ी में कह सकते हैं, तो आम तौर पर आप इसे constraint के साथ लागू कर सकते हैं।
हर तालिका के लिए ये प्रश्न पूछें:
अगर आप चैट‑ड्रिवन बिल्ड टूल का उपयोग कर रहे हैं, तो उन इनवेरिएंट्स को डेटा के लिए acceptance criteria के रूप में ट्रीट करें, न कि वैकल्पिक नोट के रूप में। उदाहरण: "एक deal राशि नकारात्मक नहीं हो सकती," "एक contact ईमेल वर्कस्पेस के अनुसार यूनिक है," "एक task को वास्तविक contact रेफर करना चाहिए।" जितने स्पष्ट नियम होंगे, उतनी कम जगह बचेगी आकस्मिक एज‑केसेस के लिए।
Koder.ai (koder.ai) ऐसी सुविधाएँ शामिल करता है जैसे प्लानिंग मोड, स्नैपशॉट और रोलबैक, और सोर्स कोड एक्सपोर्ट, जो आपको स्कीमा बदलावों पर सुरक्षित तरीके से इटरेट करने में मदद कर सकता है जबकि आप समय के साथ constraints कड़ाई से लागू करते हैं।
एक सरल रोलआउट पैटर्न जो असली टीमों में काम करता है: एक हाई‑वैल्यू तालिका चुनें (users, orders, invoices, contacts), 1‑2 constraints जोड़ें जो सबसे बुरी विफलताओं को रोकते हैं (अक्सर NOT NULL और UNIQUE), जो राइट्स फेल होती हैं उन्हें ठीक करें, फिर दोहराएँ। समय के साथ नियम कड़ा करना एक बड़े जोखिम भरे माइग्रेशन से बेहतर होता है।