PostgreSQL vs MariaDB for Transactional CRUD Apps
PostgreSQL vs MariaDB: इंडेक्सिंग, माइग्रेशन, JSON और ऐसे क्वेरी फीचर का व्यावहारिक अवलोकन जो प्रोटोटाइप के बाद CRUD ऐप के बढ़ने पर मायने रखते हैं।

जब CRUD ऐप प्रोटोटाइप से बाहर निकलता है
एक प्रोटोटाइप CRUD ऐप अक्सर तेज़ लगता है क्योंकि डेटा छोटा होता है, टीम छोटी होती है, और ट्रैफ़िक अनुमान्य होता है। आप सरल क्वेरी, कुछ इंडेक्स और मैन्युअल स्कीमा ट्वीक से काम चला लेते हैं। फिर ऐप के असली यूज़र्स, असली वर्कफ़्लोज़ और असली डेडलाइन्स आ जाते हैं।
विकास वर्कलोड बदल देता है। लिस्ट और डैशबोर्ड दिन भर खोले जाते हैं। एक ही रिकॉर्ड पर अधिक लोग एडिट करते हैं। बैकग्राउंड जॉब्स बैच में लिखना शुरू कर देते हैं। तभी "कल काम कर रहा था" धीमी पेजेज़, रेंडम टाइमआउट और पीक घंटों के दौरान लॉक वेट्स में बदल जाता है।
आपने शायद वह लाइन पार कर ली है अगर आप निम्न देख रहे हैं: पेज 20 के बाद लिस्ट पेज धीमा हो जाता है, रिलीज़ में डेटा बैकफिल्स शामिल हैं (सिर्फ नए कॉलम नहीं), मेटाडेटा और इंटीग्रेशन पेलोड के लिए और "फ्लेक्स फील्ड्स" बढ़ रहे हैं, या सपोर्ट टिकट्स कह रहे हैं कि "बचत में बहुत समय लगता है" व्यस्त समय में।
यही वह समय है जब PostgreSQL और MariaDB की तुलना ब्रांड-पसंद से हटकर व्यावहारिक प्रश्न बन जाती है। ट्रांज़ैक्शनल CRUD वर्कलोड के लिए निर्णय अक्सर उन विवरणों पर निर्भर करता है: जैसे-जैसे क्वेरीज़ जटिल होती हैं तो इंडेक्सिंग विकल्प, बड़े टेबल पर माइग्रेशन की सेफ़्टी, JSON स्टोरेज और क्वेरींग, और वे क्वेरी फीचर जो एप्लिकेशन-साइड काम कम कर देते हैं।
यह लेख उन्हीं डेटाबेस व्यवहारों पर केंद्रित रहेगा। यह सर्वर साइजिंग, क्लाउड प्राइसिंग या वेंडर कॉन्ट्रैक्ट्स में गहराई नहीं जाएगा। वे मायने रखते हैं, पर अक्सर बाद में बदलना आसान होता है बनिस्बत उस स्कीमा और क्वेरी स्टाइल के जिस पर आपका प्रोडक्ट निर्भर करता है।
अपने ऐप की ज़रूरतों से शुरू करें, न कि डेटाबेस ब्रांड से
बेहतर शुरुआत "PostgreSQL vs MariaDB" नहीं है। यह आपके ऐप के रोज़मर्रा के व्यवहार हैं: रिकॉर्ड बनाना, कुछ फ़ील्ड अपडेट करना, फ़िल्टर्ड परिणामों की लिस्ट दिखाना, और जब कई लोग एक साथ क्लिक करें तो सही बने रहना।
लिखें कि आपकी सबसे व्यस्त स्क्रीन क्या करती हैं। हर लिखने पर कितनी पढ़ाइयाँ होती हैं? स्पाइक्स कब आते हैं (सुबह लॉगिन, महीने के अंत की रिपोर्टिंग, बड़े इम्पोर्ट)? वे सटीक फ़िल्टर और सॉर्टिंग कैप्चर करें जिन पर आप निर्भर करते हैं, क्योंकि वही बाद में इंडेक्स डिजाइन और क्वेरी पैटर्न चलाएँगे।
फिर अपनी नॉन-नेगोशिएबल चीज़ें परिभाषित करें। कई टीमों के लिए इसका मतलब सख्त कंसिस्टेंसी (पैसे या इन्वेंटरी के लिए), "किसने क्या बदला" का ऑडिट ट्रेल, और रिपोर्टिंग क्वेरीज़ जो स्कीमा बदलने पर बिखर न जाएँ।
ऑपरेशनल रियलिटी फीचर्स जितनी ही महत्वपूर्ण है। तय करें कि आप मैनेज़्ड डेटाबेस चलाएँगे या सेल्फ-होस्ट, बैकअप से कितनी तेज़ी से रिस्टोर करना चाहिए, और मेंटेनेंस विंडोज़ के लिए आपकी सहनशीलता कितनी है।
अंत में, "पर्याप्त तेज़" को कुछ स्पष्ट लक्ष्यों में परिभाषित करें। उदाहरण के लिए: सामान्य लोड में p95 API लेटेंसी (200–400 ms), पीक कंकरेन्सी में p95 (शायद सामान्य का 2x), अपडेट्स के दौरान अधिकतम स्वीकार्य लॉक वेट्स (100 ms से कम), और बैकअप/रिस्टोर समय सीमाएँ।
इंडेक्सिंग के बुनियादी सिद्धांत जो CRUD स्पीड तय करते हैं
ज़्यादातर CRUD ऐप्स तब तक तेज़ रहते हैं जब तक टेबल्स मिलियन रोज़ तक नहीं पहुँचतीं और हर स्क्रीन "फिल्टर्ड लिस्ट विथ सॉर्टिंग" बन जाती है। उस बिंदु पर इंडेक्सिंग 50 ms क्वेरी और 5 सेकंड टाइमआउट के बीच का फ़र्क बन जाती है।
B-tree इंडेक्स दोनों, PostgreSQL और MariaDB में, डिफ़ॉल्ट वर्कहॉर्स हैं। वे तब मदद करते हैं जब आप किसी कॉलम पर फ़िल्टर करते हैं, कीज़ पर जॉइन करते हैं, और जब आपका ORDER BY इंडेक्स क्रम से मेल खाता है। असली प्रदर्शन फर्क अक्सर सेलेक्टिविटी (कितने रोज़ मैच करते हैं) और यह है कि क्या इंडेक्स फ़िल्टरिंग और सॉर्टिंग दोनों पूरा कर सकता है बिना अतिरिक्त रोज़ स्कैन किए।
जैसे-जैसे ऐप परिपक्व होते हैं, कॉम्पोजिट इंडेक्स एकल-कालम वाले इंडेक्स से अधिक मायने रखने लगते हैं। एक सामान्य पैटर्न है मल्टी-टेनेंट फ़िल्टर + स्टेटस + टाइम सॉर्ट, जैसे (tenant_id, status, created_at)। सबसे लगातार फ़िल्टर पहले रखें (अक्सर tenant_id), फिर अगला फ़िल्टर, और अंत में जिस कॉलम पर आप सॉर्ट करते हैं। यह अलग-अलग इंडेक्स से बेहतर हो सकता है जिन्हें ऑप्टिमाइज़र प्रभावी ढंग से जोड़ नहीं पाता।
अंतर "स्मार्टर" इंडेक्सों में दिखते हैं। PostgreSQL partial और expression इंडेक्स सपोर्ट करता है, जो फोकस्ड स्क्रीन के लिए शानदार हो सकते हैं (उदा., सिर्फ़ “open” टिकट्स को इंडेक्स करना)। ये शक्तिशाली हैं, पर तब टीमों को हैरानी हो सकती है जब क्वेरीज़ predicate से ठीक मेल नहीं खातीं।
इंडेक्स मुफ्त नहीं हैं। हर insert और update को हर इंडेक्स अपडेट करना होता है, इसलिए एक स्क्रीन बेहतर कर के आप आसानी से हर write को धीमा कर सकते हैं।
एक सरल अनुशासन:
- केवल उन्हीं क्वेरी पाथ्स के लिए इंडेक्स जोड़ें जिनका वास्तविक उपयोग है (एक नाम-सूचीबद्ध स्क्रीन या API कॉल)।
- कई ओवरलैपिंग इंडेक्सों के बजाय एक अच्छा कॉम्पोजिट इंडेक्स प्राथमिकता दें।
- फीचर बदलावों के बाद इंडेक्स को फिर से जाँचें और बेकार भार हटाएँ।
- रख-रखाव की योजना बनाएं: PostgreSQL को बLOAT से बचने और आँकड़े ठीक रखने के लिए नियमित
VACUUM/ANALYZEचाहिए; MariaDB भी अच्छी स्टैटिस्टिक्स और समय-समय पर क्लीनअप पर निर्भर करता है। - अनुमान पर भरोसा करने के बजाय मापें—पहले और बाद में।
असली स्क्रीन के लिए इंडेक्सिंग: लिस्ट, खोज और पेजिनेशन
ज़्यादातर CRUD ऐप्स कुछ स्क्रीन पर अपना समय बिताते हैं: फ़िल्टर वाली लिस्ट, एक सर्च बॉक्स, और एक डिटेल पेज। आपके इंडेक्स उन स्क्रीन से मेल खाते हैं या नहीं, यह अधिक मायने रखता है; पर दोनों इंजन बड़े होते ही अलग टूल्स देते हैं।
लिस्ट पेज के लिए सोचने का क्रम: पहले फ़िल्टर, फिर सॉर्ट, फिर पेजिनेशन। एक सामान्य पैटर्न है "account X के सभी टिकट्स, status in (open, pending), newest first"। वह कॉम्पोजिट इंडेक्स जो फ़िल्टर कॉलम से शुरू होकर सॉर्ट कॉलम पर खत्म होता है, आमतौर पर जीतता है।
पेजिनेशन को खास ध्यान चाहिए। Offset pagination (पेज 20 के लिए OFFSET 380) जैसे आप आगे बढ़ते हैं धीमा होता जाता है क्योंकि डेटाबेस को पहले के रोज़ पार करना पड़ता है। Keyset pagination अधिक स्थिर होता है: आप आख़िरी देखे गए मान (जैसे created_at और id) पास करते हैं और "उससे पुराने अगले 20" माँगते हैं। यह नए रोज़ के बीच स्क्रोलिंग के दौरान डुप्लिकेट और गैप भी कम करता है।
PostgreSQL लिस्ट स्क्रीन के लिए उपयोगी विकल्प देता है: INCLUDE का उपयोग करके "कवरिंग" इंडेक्स, जो visibility map की अनुमति होने पर index-only scans संभव कर सकता है। MariaDB भी कवरिंग रीड कर सकता है, पर आम तौर पर आप ज़रूरी कॉलम सीधे इंडेक्स में डालकर यह हासिल करते हैं—जिससे इंडेक्स चौड़े और मेंटेन करने में महँगे हो सकते हैं।
अगर कोई लिस्ट एंड़पॉइंट टेबल बढ़ने पर धीमा हो रहा है भले ही वह केवल 20–50 रोज़ लौटाता हो, सॉर्टिंग तब धीमी हो जाती है जब आप ORDER BY हटाते हैं, या सरल फ़िल्टर में I/O कूदता है—तो सम्भवतः आपको बेहतर इंडेक्स चाहिए। लंबी क्वेरीज़ पीक घंटों के दौरान लॉक वेट्स भी बढ़ाती हैं।
उदाहरण: एक ऑर्डर्स स्क्रीन जो customer_id और status से फ़िल्टर करती है और created_at से सॉर्ट करती है, आमतौर पर (customer_id, status, created_at) से शुरू होने वाले इंडेक्स से लाभ उठाती है। अगर बाद में आप "order number से सर्च" जोड़ते हैं, तो वह आमतौर पर एक अलग इंडेक्स होगा, न कि लिस्ट इंडेक्स में जोड़ा गया।
माइग्रेशन: डेटा बड़े होने पर रिलीज़ को सुरक्षित रखना
माइग्रेशन जल्दी ही "टेबल बदलो" नहीं रह जाते। असली यूज़र्स और इतिहास होने पर आपको डेटा बैकफिल्स, कंस्ट्रेंट कड़ाई, और पुराने डेटा शेप्स को बिना ऐप तोड़े साफ़ करने की ज़रूरत होगी।
एक सुरक्षित डिफ़ॉल्ट है expand, backfill, contract। जो आपको चाहिए वह इस तरह जोड़ें कि मौजूदा कोड बाधित न हो, छोटे चरणों में डेटा कॉपी या कैलकुलेट करें, और फिर पुराना पाथ हटाएँ।
व्यवहार में इसका मतलब अक्सर नया nullable कॉलम या टेबल जोड़ना, बैचों में बैकफिल करना जबकि राइट्स को कंसिस्टेंट रखना, बाद में NOT NULL, foreign keys और unique नियमों से वैलिडेट करना, और केवल तब पुराने कॉलम/इंडेक्स/कोड पाथ को हटाना जब आप निश्चित हो कि सब कुछ नए पाथ पर चल रहा है।
सभी स्कीमा बदलाव बराबर नहीं होते। कॉलम जोड़ना अक्सर कम जोखिम भरा है। बड़े टेबल पर इंडेक्स जोड़ना महंगा हो सकता है—इसे कम ट्रैफ़िक में शेड्यूल करें और मापें। कॉलम टाइप बदलना अक्सर सबसे जोखिम भरा होता है क्योंकि यह डेटा री-राइट कर सकता है या लिखने को ब्लॉक कर सकता है। एक सुरक्षित पैटर्न है: नया कॉलम नया टाइप लेकर बनाओ, बैकफिल करो, फिर रीड्स और राइट्स स्विच करो।
रोलबैक भी स्केल पर मतलब बदल देता है। स्कीमा रोलबैक कभी-कभी आसान होता है; डेटा रोलबैक अक्सर नहीं। स्पष्ट लिखें कि आप क्या उलट सकते हैं, खासकर अगर माइग्रेशन में विनाशकारी डिलीट्स या लॉसी ट्रांसफॉर्म्स हों।
JSON सपोर्ट: लचीले फ़ील्ड बिना भविष्य के दर्द के
JSON फ़ील्ड लुभावने होते हैं क्योंकि वे तेज़ी से शिप करने देते हैं: अतिरिक्त फॉर्म फ़ील्ड्स, इंटीग्रेशन पेलोड्स, यूज़र प्रेफ़रेंसेज़ और बाहरी सिस्टम के नोट्स—सब बिना स्कीमा चेंज के फिट हो जाते हैं। चाल यह है कि यह तय करें क्या JSON में रहेगा और क्या असली कॉलम होना चाहिए।
PostgreSQL और MariaDB दोनों में JSON तब बेहतर काम करता है जब उसे शायद ही फ़िल्टर किया जाए और अधिकतर दिखाया जाए, डिबग के लिए रखा जाए, प्रति यूज़र या टेनेंट एक "सेटिंग्स" ब्लॉब के रूप में हो, या छोटे वैकल्पिक एट्रिब्यूट्स के लिए जो रिपोर्टिंग को नहीं चलाते।
JSON का इंडेक्सिंग वह जगह है जहाँ टीमें हैरान होती हैं। किसी JSON की कुंजी पर एक बार क्वेरी करना आसान है। बड़े टेबल्स पर उस पर फ़िल्टर और सॉर्ट करना प्रदर्शन को तहस-नहस कर सकता है। PostgreSQL के पास JSON पाथ के लिए मजबूत इंडेक्स विकल्प हैं, पर अनुशासन जरूरी है: कुछ कुंजियाँ चुनें जिन पर आप सचमुच फ़िल्टर करते हैं और उन्हें इंडेक्स करें, बाकी को अनइंडेक्स्ड पेलोड रखें। MariaDB भी JSON क्वेरी कर सकता है, पर जटिल "JSON के अंदर खोज" पैटर्न अक्सर नाज़ुक और तेज़ बनाए रखना कठिन हो जाते हैं।
JSON कंस्ट्रेंट्स को भी कमजोर करता है। बिना संरचना वाले ब्लॉब के अंदर "यह मान इनमें से होना चाहिए" या "हमेशा मौजूद होना चाहिए" जैसी चीज़ें लागू करना कठिन है, और रिपोर्टिंग टूल्स आमतौर पर टाइप्ड कॉलम पसंद करते हैं।
एक नियम जो स्केल करता है: अनजाने के लिए JSON से शुरू करें, पर जब आप (1) उस पर फ़िल्टर या सॉर्ट करते हैं, (2) कंस्ट्रेंट्स चाहिए, या (3) वह हर सप्ताह डैशबोर्ड में दिख रहा है—तो उसे कॉलम या चाइल्ड टेबल में सामान्यीकृत करें। किसी ऑर्डर का पूरा शिपिंग API रेस्पॉन्स JSON के रूप में स्टोर करना अक्सर ठीक है। पर delivery_status और carrier जैसे फ़ील्ड्स वास्तविक कॉलम बनने चाहिए जब सपोर्ट और रिपोर्टिंग उन पर निर्भर हों।
परिपक्व ऐप्स में दिखने वाले क्वेरी फीचर्स
शुरू में, अधिकांश CRUD ऐप्स सरल SELECT, INSERT, UPDATE, और DELETE पर चलते हैं। बाद में आप एक्टिविटी फीड्स, ऑडिट व्यूज़, एडमिन रिपोर्ट्स और त्वरित सर्च जोड़ते हैं। तब चुनाव फीचर-ट्रेडऑफ़ की तरह दिखने लगते हैं।
CTE और सबक्वेरीज़ जटिल क्वेरीज़ को पढ़ने लायक बनाए रखते हैं। वे तब उपयोगी हैं जब आप परिणाम को चरण-दर-चरण बनाते हैं (ऑर्डर्स फ़िल्टर करें, पेमेंट्स जोड़ें, टोटल निकालें)। पर पठनीयता लागत छिपा सकती है—जब क्वेरी धीमी हो जाए तो आपको CTE को सबक्वेरी या जॉइन में बदलकर execution plan फिर से चेक करना पड़ सकता है।
विंडो फ़ंक्शंस तब मायने रखते हैं जब किसी ने पूछा हो "खर्च के हिसाब से कस्टमर्स को रैंक करो", "रनिंग टोटल दिखाओ", या "प्रत्येक टिकट की लेटेस्ट स्टेटस"। वे अक्सर एप्लिकेशन-लूप्स की जगह ले लेते हैं और क्वेरी की संख्या घटाते हैं।
इडेम्पोटेंट राइट्स भी एक बड़ा grown-up requirement है। जब retries होते हैं (मोबाइल नेटवर्क, बैकग्राउंड जॉब्स), upserts आपको बिना डुप्लिकेट बनाए सुरक्षित रूप से लिखने देते हैं:
- PostgreSQL:
INSERT ... ON CONFLICT - MariaDB:
INSERT ... ON DUPLICATE KEY UPDATE
सर्च वह फीचर है जो टीमों पर धीरे-धीरे असर डालता है। बिल्ट-इन फुल-टेक्स्ट सर्च प्रोडक्ट कैटलॉग, नॉलेज बेस और सपोर्ट नोट्स को कवर कर सकता है। ट्रिग्राम-नुमा सर्च टाइप-अहेड और टाइपो टॉलरेंस के लिए उपयोगी है। अगर सर्च कोर बन जाता है (कठोर रैंकिंग, कई फ़िल्टर, भारी ट्रैफ़िक), तो बाहरी सर्च टूल लाना अतिरिक्त कॉम्प्लेक्सिटी के लायक हो सकता है।
उदाहरण: एक ऑर्डर पोर्टल शुरुआत में "लिस्ट ऑर्डर्स" से शुरू होता है। एक साल बाद उसे चाहिए "प्रति कस्टमर का नवीनतम ऑर्डर दिखाओ, मासिक खर्च के हिसाब से रैंक करो, और गलत स्पेलिंग वाले नामों पर खोज करो"। ये केवल UI का काम नहीं रह जाते—ये डेटाबेस क्षमताएँ हैं।
लोड के तहत ट्रांज़ैक्शंस, लॉक और समवर्तीता
जब ट्रैफ़िक कम होता है, तो अधिकांश डेटाबेस ठीक लगते हैं। लोड के नीचे फर्क अक्सर इस बात पर होता है कि आप एक ही डेटा पर एक साथ होने वाले बदलाओं को कितना अच्छा हैंडल करते हैं, न कि केवल कच्ची स्पीड पर। PostgreSQL और MariaDB दोनों ट्रांज़ैक्शनल CRUD वर्कलोड चला सकते हैं, पर आपको कंटेंशन के लिए डिजाइन करना होगा।
सरल भाषा में आइसोलेशन
एक ट्रांज़ैक्शन कदमों का समूह है जो साथ में सफल होना चाहिए। आइसोलेशन नियंत्रित करता है कि उन कदमों के चलते अन्य सत्र क्या देख सकते हैं। उच्च आइसोलेशन चौंकाने वाली रीड्स से बचाता है, पर प्रतीक्षा बढ़ा सकता है। कई ऐप डिफ़ॉल्ट्स से शुरू होते हैं और केवल उन फ्लोज़ के लिए आइसोलेशन कड़ा करते हैं जिन्हें सचमुच इसकी ज़रूरत होती है (जैसे कार्ड चार्ज करना और ऑर्डर अपडेट करना)।
असल में क्या लॉक दर्द पैदा करता है
CRUD ऐप्स में लॉकिंग की समस्याएँ अक्सर कुछ ही बार-बार होने वाले कारणों से आती हैं: हॉट रोज़ जिन्हें हर कोई अपडेट करता है, हर कार्रवाई पर बदलने वाले काउंटर, जॉब क्यूज़ जहाँ कई वर्कर्स एक ही "नेक्स्ट जॉब" लेने की कोशिश करते हैं, और लंबी ट्रांज़ैक्शन जो लॉक तब तक होते हैं जबकि अन्य काम चलता है या यूज़र समय बीतता है।
कंटेंशन घटाने के लिए ट्रांज़ैक्शन को छोटा रखें, केवल ज़रूरी कॉलम अपडेट करें, और ट्रांज़ैक्शन के अंदर नेटवर्क कॉल से बचें।
एक सहायक आदत है कॉन्फ्लिक्ट पर रीट्राई करना। अगर दो सपोर्ट एजेंट एक ही टिकट को एक ही समय में सेव करते हैं, तो चुपचाप फेल न करें। कॉन्फ्लिक्ट डिटेक्ट करें, नवीनतम रो लोड करें, और यूज़र से कहें कि वे बदलाव फिर से लागू करें।
समस्याओं को जल्दी पकड़ने के लिए डेडलॉक्स, लंबी चलती ट्रांज़ैक्शन और ऐसे क्वेरी देखें जो चलने के बजाय वेट कर रहे हों। स्लो क्वेरी लॉग्स को अपनी रूटीन का हिस्सा बनाएं, खासकर उन रिलीज़ के बाद जिनमें नई स्क्रीन या बैकग्राउंड जॉब्स जुड़े हों।
लॉन्च के बाद जो ऑपरेशंस महत्वपूर्ण हो जाते हैं
लॉन्च के बाद आप सिर्फ़ क्वेरी स्पीड के लिए ऑप्टिमाइज़ नहीं कर रहे होते—आप रिकवरी, सेफ़ चेंज और अनुमान्य प्रदर्शन के लिए ऑप्टिमाइज़ कर रहे होते हैं।
अगला आम कदम एक रिप्लिका जोड़ना होता है। प्राइमरी राइट्स संभालती है, और रिप्लिका ऐसे रीड-हेवी पेजेस जैसे डैशबोर्ड या रिपोर्ट्स सर्व कर सकती है। इससे आप ताज़गी के बारे में अलग सोचते हैं: कुछ रीड सेकंड्स से लेट हो सकती हैं, इसलिए आपका ऐप जानता होना चाहिए कि कौन सी स्क्रीन प्राइमरी से पढ़े (जैसे अभी-अभी रखा गया ऑर्डर) और कौन सा थोड़ा पुराना डेटा सहन कर सकता है (साप्ताहिक सारांश)।
बैकअप केवल आधा काम है। मायने यह रखता है कि आप कितनी जल्दी और सही तरीके से रिस्टोर कर सकते हैं। नियमित टेस्ट रिस्टोर्स शेड्यूल करें अलग वातावरण में, फिर बेसिक्स वैलिडेट करें: ऐप कनेक्ट हो सकता है, मुख्य टेबल्स मौजूद हैं, और क्रिटिकल क्वेरीज़ अपेक्षित परिणाम देती हैं। टीमें अक्सर बहुत देर में पाती हैं कि उन्होंने गलत चीज़ बैकअप की, या रिस्टोर टाइम उनकी डाउनटाइम बजट से बहुत आगे है।
अपग्रेड्स भी "क्लिक और होप" नहीं रह जाते। मेंटेनेंस विंडो प्लान करें, कम्पैटिबिलिटी नोट्स पढ़ें, और प्रोडक्शन डेटा की कॉपी के साथ अपग्रेड पाथ टेस्ट करें। मामूली वर्शन बम्प भी क्वेरी प्लान या इंडेक्स/JSON फंक्शंस के व्यवहार को बदल सकते हैं।
सरल ऑब्ज़र्वेबिलिटी जल्दी फायदा देती है। स्लो क्वेरी लॉग्स और कुल समय द्वारा टॉप क्वेरीज़, कनेक्शन सैचुरेशन, रेप्लिकेशन लैग (यदि उपयोग करते हैं), कैश हिट रेशियो और I/O दबाव, तथा लॉक वेट्स और डेडलॉक इवेंट्स से शुरू करें।
कैसे चुनें: एक व्यावहारिक मूल्यांकन प्रक्रिया
अगर आप अटके हुए हैं, तो फीचर सूचियाँ पढ़ना बंद करें और अपने वर्कलोड के साथ एक छोटा ट्रायल चलाएँ। लक्ष्य परफेक्ट बेंचमार्क नहीं है। लक्ष्य है बड़े टेबल्स और तेज़ रिलीज़ साइकिल पर अनपेक्षित चीज़ों से बचना।
1) प्रोडक्शन जैसा मिनी टेस्ट बनाएं
अपने ऐप का एक हिस्सा चुनें जो असली दर्द दिखाता हो: एक-दो मुख्य टेबल्स, कुछ स्क्रीन, और उनके पीछे के राइट पाथ। अपने टॉप क्वेरीज़ इकट्ठा करें (लिस्ट पेजेस, डिटेल पेजेस, बैकग्राउंड जॉब्स की क्वेरीज़)। वास्तविक रो काउंट डालें (कम से कम अपने प्रोटोटाइप डेटा का 100x, समान आकार के साथ)। वे इंडेक्स जोड़ें जिन्हें आप सोचते हैं कि चाहिए, फिर वही क्वेरीज़ वही फ़िल्टर और सॉर्टिंग के साथ चलाएँ और टाइमिंग पकड़ें। लिखते हुए भी रन करें (एक साधारण स्क्रिप्ट जो रोज़ इन्सर्ट और अपडेट कर दे)।
एक तेज़ उदाहरण है "Customers" लिस्ट जो status से फ़िल्टर करती है, नाम से सर्च करती है, last activity से सॉर्ट करती है, और पेजिनेट करती है। वह एकल स्क्रीन अक्सर बताती है कि आपकी इंडेक्सिंग और प्लानर बिहेवियर अच्छा उम्र लेगा या नहीं।
2) माइग्रेशंस का रिहर्सल असली रिलीज़ जैसा करें
डेटासेट की स्टेजिंग कॉपी बनाएं और उन बदलावों का अभ्यास करें जो आने वाले हैं: कॉलम जोड़ना, टाइप बदलना, बैकफिल करना, इंडेक्स जोड़ना। मापें कितनी देर लगती है, क्या यह राइट्स को ब्लॉक करता है, और rollback का मतलब क्या होता है जब डेटा पहले से बदल चुका हो।
3) सरल स्कोरकार्ड का उपयोग करें
टेस्ट के बाद प्रत्येक विकल्प को स्कोर करें: आपके वास्तविक क्वेरीज़ के लिए परफॉर्मेंस, कैरेक्टनेस और सेफ़्टी (कंस्ट्रेंट्स, ट्रांज़ैक्शंस, एज केस), माइग्रेशन रिस्क (लॉकिंग, डाउनटाइम, रिकवरी विकल्प), ऑप्स प्रयास (बैकअप/रिस्टोर, रेप्लिकेशन, मॉनिटरिंग), और टीम का कम्फर्ट।
अगले 12 महीनों के लिए जो जोखिम घटाए वह डेटाबेस चुनें, न कि जो एक माइक्रो-टेस्ट जीतता है।
सामान्य गलतियाँ और जाल
सबसे महंगे डेटाबेस समस्याएँ अक्सर "क्विक विन" से शुरू होती हैं। दोनों डेटाबेस ट्रांज़ैक्शनल CRUD ऐप चला सकते हैं, पर गलत आदतें किसी भी एक को बिगाड़ देंगी जब ट्रैफ़िक और डेटा बढ़ेगा।
एक आम जाल JSON को हर चीज़ के लिए शॉर्टकट मानना है। एक लचीला "extras" फ़ील्ड सच में वैकल्पिक डेटा के लिए ठीक है, पर कोर फ़ील्ड्स जैसे status, timestamps और foreign keys असली कॉलम बने रहने चाहिए। वरना आप धीमे फ़िल्टर्स, अजीब वेलिडेशन, और रिपोर्टिंग के प्राथमिकता बनने पर कष्टप्रद रिफैक्टर्स पाएँगे।
इंडेक्सिंग का अपना जाल है: स्क्रीन पर दिखने वाले हर फ़िल्टर के लिए इंडेक्स जोड़ना। इंडेक्स रीड्स तेज़ करते हैं, पर वे लिखों को धीमा करते हैं और माइग्रेशंस को भारी बनाते हैं। वास्तव में उपयोग होने वाले चीज़ों पर इंडेक्स करें, फिर मापकर वैलिडेट करें।
माइग्रेशंस बड़ा नुकसान पहुँचा सकते हैं जब वे टेबल्स को लॉक कर देते हैं। बड़े-झटके वाले बदलाव जैसे बड़े कॉलम का री-राइट, NOT NULL के साथ डिफ़ॉल्ट जोड़ना, या बड़ा इंडेक्स बनाना लिखों को मिनटों तक ब्लॉक कर सकता है। जोखिम भरे बदलावों को स्टेप्स में तोड़ें और ऐप शांत होने पर शेड्यूल करें।
ORM डिफ़ॉल्ट्स पर हमेशा निर्भर भी न रहें। जब कोई लिस्ट व्यू 1,000 रोज़ से 10 मिलियन तक बढ़ता है, तब आपको क्वेरी प्लान पढ़ने होंगे, गायब इंडेक्स देखना होगा, और धीमे जॉइन्स ठीक करने होंगे।
त्वरित चेतावनी संकेत: प्राइमरी फ़िल्टरिंग और सॉर्टिंग के लिए JSON फ़ील्ड का प्रयोग, बिना माप के बढ़ता इंडेक्स काउंट जो लिखने को धीमा कर देता है, एक ही डिप्लॉय में बड़े टेबल्स को री-राइट करने वाली माइग्रेशंस, और स्थिर ऑर्डरिंग के बिना पेजिनेशन (जिससे मिसिंग और डुप्लिकेट रोज़ होते हैं)।
अंतिम सत्यापन सूची (Quick checklist) पहले निर्णय लेने से
फैसला लेने से पहले अपनी सबसे व्यस्त स्क्रीन और रिलीज़ प्रक्रिया के आधार पर एक त्वरित रियलिटी चेक करें:
- क्या आपकी टॉप स्क्रीन पीक लोड पर तेज़ रह सकती हैं? वास्तविक फ़िल्टर, सॉर्टिंग और पेजिनेशन के साथ सबसे धीमे लिस्ट पेज को टेस्ट करें और पुष्टि करें कि इंडेक्स वही सटीक क्वेरी मैच करते हैं।
- क्या आप सुरक्षित रूप से स्कीमा चेंज भेज सकते हैं? अगले ब्रेकिंग चेंज के लिए expand-backfill-contract योजना लिखें।
- क्या JSON बनाम कॉलम के लिए आपकी स्पष्ट नियमावली है? तय करें कौन से JSON कुंजियाँ searchable या sortable होनी चाहिए और कौन सी वास्तव में लचीली हैं।
- क्या आप किसी विशिष्ट क्वेरी फ़ीचर पर निर्भर हैं? अपसर्ट बिहेवियर, विंडो फ़ंक्शंस, CTE व्यवहार, और क्या आपको functional या partial इंडेक्स चाहिए—इनकी जाँच करें।
- क्या आप लॉन्च के बाद इसे ऑपरेट कर सकते हैं? प्रमाणित करें कि आप बैकअप से रिस्टोर कर सकते हैं, स्लो क्वेरीज माप सकते हैं, और बेसलाइन लेटेंसी व लॉक वेट्स ले सकते हैं।
उदाहरण: साधारण ऑर्डर ट्रैकिंग से व्यस्त कस्टमर पोर्टल तक
कल्पना कीजिए एक कस्टमर पोर्टल जो सरल शुरुआत करता है: कस्टमर्स लॉगिन करते हैं, ऑर्डर्स देखते हैं, इनवॉइस डाउनलोड करते हैं, और सपोर्ट टिकट खोलते हैं। पहले सप्ताह में लगभग कोई भी ट्रांज़ैक्शनल डेटाबेस ठीक काम करता है। पेज तेज़ लोड होते हैं और स्कीमा छोटा होता है।
कुछ महीनों बाद विकास के संकेत दिखने लगते हैं। कस्टमर्स ऐसे फ़िल्टर मांगते हैं जैसे “orders shipped last 30 days, paid by card, with partial refund.” सपोर्ट त्वरित CSV एक्सपोर्ट चाहता है साप्ताहिक समीक्षा के लिए। फ़ाइनेंस एक ऑडिट ट्रेल चाहता है: किसने इनवॉइस स्टेटस बदला, कब, और किससे किसमें बदला। क्वेरी पैटर्न मूल स्क्रीन से कहीं ज़्यादा विविध हो जाते हैं।
यही वह जगह है जहाँ निर्णय विशिष्ट फीचर्स और उनके व्यवहार के बारे में होता है—और कैसे वे असली लोड के तहत चलते हैं।
अगर आप लचीले फ़ील्ड जोड़ते हैं (डिलिवरी निर्देश, कस्टम एट्रिब्यूट्स, टिकट मेटाडेटा), तो JSON सपोर्ट मायने रखता है क्योंकि आप अंततः उन फ़ील्ड्स के अंदर क्वेरी करना चाहेंगे। ईमानदार रहें कि आपकी टीम JSON पाथ्स को इंडेक्स करेगी या नहीं, शेप्स की वेरिफिकेशन रखेगी या नहीं, और JSON बढ़ने पर प्रदर्शन को कैसे नियंत्रित करेगी।
रिपोर्टिंग एक और दबाव बिंदु है। जैसे ही आप ऑर्डर्स, इनवॉइस, पेमेंट्स और टिकट्स को कई फ़िल्टर के साथ जॉइन करते हैं, आपको कॉम्पोजिट इंडेक्स, क्वेरी प्लानिंग, और बिना डाउनटाइम के इंडेक्स विकसित करने की क्षमता की परवाह होगी। माइग्रेशंस भी "शुक्रवार को स्क्रिप्ट चलाओ" नहीं रह जाते—क्योंकि एक छोटा स्कीमा चेंज मिलियन रोज़ को छू सकता है।
एक व्यावहारिक आगे का तरीका है: छह महीने में आपकी उम्मीदों के पाँच असली स्क्रीन और एक्सपोर्ट लिखें, शुरू से ही ऑडिट हिस्ट्री टेबल्स शामिल करें, अपने सबसे धीमे क्वेरीज़ के साथ वास्तविक डेटा साइज़ पर बेंचमार्क करें (hello-world CRUD नहीं), और टीम नियम दस्तावेज करें JSON उपयोग, इंडेक्सिंग और माइग्रेशंस के लिए।
यदि आप हर लेयर को हाथ से बनाना छोड़कर तेज़ी से आगे बढ़ना चाहते हैं, तो AppMaster (appmaster.io) उत्पादन-तैयार बैकएंड, वेब ऐप और नेटिव मोबाइल ऐप्स एक विज़ुअल मॉडल से जेनरेट कर सकता है। यह आपको स्क्रीन, फ़िल्टर और बिजनेस प्रोसेसेज़ को वास्तविक क्वेरी वर्कलोड के रूप में जल्दी पकड़ने के लिए प्रेरित भी करता है, जिससे इंडेक्सिंग और माइग्रेशन जोखिम प्रोडक्शन में आने से पहले पकड़ में आ जाते हैं।
सामान्य प्रश्न
सबसे पहले अपने वास्तविक वर्कलोड को लिखें: सबसे व्यस्त लिस्ट स्क्रीन, फ़िल्टर, सॉर्ट और पीक लिखने के पाथ। दोनों डेटाबेस CRUD अच्छी तरह चला सकते हैं, पर सुरक्षित चुनाव वह होगा जो अगले साल के लिए आपकी इंडेक्सिंग, माइग्रेशन और क्वेरी शैली से मेल खाता, न कि केवल नाम से परिचित होने पर।
अगर लिस्ट पेज गहरे पन्नों पर धीमे दिखने लगते हैं तो आप शायद OFFSET स्कैन के खर्च उठा रहे हैं। अगर व्यस्त समय में सेविंग अटकती है तो संभवतः लॉक कंटेंशन या लंबी ट्रांज़ैक्शन की वजह है। और अगर रिलीज़ में अब बैकफिल्स और बड़े इंडेक्स आते हैं, तो माइग्रेशन अब सिर्फ़ स्कीमा चेंज नहीं रह गए—यह विश्वसनीयता का मसला बन गए हैं।
हर महत्वपूर्ण स्क्रीन-क्वेरी के लिए एक कॉम्पोजिट इंडेक्स पर डिफ़ॉल्ट रखें, जिसमें सबसे स्थिर फ़िल्टर पहले और सॉर्ट कॉलम आख़िर में हो। उदाहरण के लिए, मल्टी-टेनेंट सूचियों में अक्सर (tenant_id, status, created_at) अच्छा काम करता है क्योंकि यह फ़िल्टर और ऑर्डरिंग दोनों को सपोर्ट करता है बिना अतिरिक्त स्कैन के।
OFFSET पेजिनेशन इसलिए धीमा होता है क्योंकि डेटाबेस को पहले के रोज़ को भी पार करना पड़ता है। इसके बजाय कीसेट पेजिनेशन इस्तेमाल करें: आख़िरी देखे गए created_at और id पास करें और “next 20 older than that” जैसा प्रश्न पूछें—यह प्रदर्शन को स्थिर रखता है और नए रोज़ के आ जाने पर डुप्लिकेट या गैप कम करता है।
केवल उन्हीं इंडेक्सों को जोड़ें जिनका आप नाम लेकर बता सकें कि कौन सी स्क्रीन या API कॉल उन्हें इस्तेमाल करेगी। फीचर रिलीज़ के बाद रिइवैलुएट करें। बहुत सारे ओवरलैपिंग इंडेक्स हर इन्सर्ट/अपडेट को धीमा कर देते हैं और पीक लिखने पर ऐप 'रैंडमली' स्लो लगने लगती है।
expand, backfill, contract की रणनीति अपनाएँ: नए स्ट्रक्चर ऐसे जोड़ें कि मौजूदा कोड बाधित न हो, छोटे बैचों में बैकफिल करें, बाद में NOT NULL, फॉरेन की या यूनिक जैसे कंस्ट्रेंट्स से वैलिडेट करें, और तभी पुराने कॉलम/इंडेक्स/कोड को हटाएँ जब पढ़ना और लिखना नए पाथ पर स्विच हो चुका हो।
JSON को उन पेलोड्स के लिए रखें जिन्हें ज्यादातर दिखाना होता है या डिबगिंग के लिए स्टोर करते हैं। जब आप उसी JSON की कुँजी पर फ़िल्टर, सॉर्ट या नियमित रिपोर्ट बना रहे हों, तो उसे वास्तविक कॉलम में प्रमोट कर दें। यह JSON-भारी क्वेरीज़ को धीमा होने से बचाता है और कंस्ट्रेंट लागू करना आसान बनाता है।
अपसर्ट्स अहम हैं जब रीट्राईज़ सामान्य हों (मोबाइल नेटवर्क, बैकग्राउंड जॉब्स, टाइमआउट)। PostgreSQL में INSERT ... ON CONFLICT और MariaDB में INSERT ... ON DUPLICATE KEY UPDATE का उपयोग करें; दोनों ही मामलों में यूनिक कीज़ को सावधानी से परिभाषित करें ताकि रीट्राई डुप्लिकेट नहीं बनाएँ।
ट्रांज़ैक्शन्स को छोटा रखें, ट्रांज़ैक्शन के अंदर नेटवर्क कॉल करने से बचें, और उन “हॉट रोज़” को घटाएँ जिन पर सभी लोग अपडेट करते हैं (जैसे साझा काउंटर)। जब कॉन्फ्लिक्ट हो, तो रीट्राई करें या स्पष्ट रूप से यूज़र को बताएं ताकि एडिट्स चुपचाप खो न जाएँ।
हाँ, अगर आप रीड-हेवी पेजों पर थोड़ी लैग सहन कर सकते हैं। रीड-रिप्लिका जोड़ने से आपने लेटेंसी कम की पर ताज़गी (freshness) पर असर पड़ेगा—ऐप को पता होना चाहिए कि कौन सी स्क्रीन प्राथमिक से पढ़ना चाहिए (जैसे अभी-अभी प्लेस किया गया ऑर्डर) और कौन सा थोड़ा पुराना डेटा सहन कर सकता है (साप्ताहिक सारांश)। रेप्लिकेशन लैग मॉनिटर करें।


