DanLevy.net

JSONB: आपके डेटाबेस को बर्बाद करने का सबसे अच्छा तरीका

JSONB शक्तिशाली और उपयोगी है, लेकिन जब आप ब्लॉब को वास्तविक स्कीमा बना देते हैं तो इसे आसानी से दुरुपयोग किया जा सकता है।

PostgreSQL ने JSONB को जोड़ा ताकि आप बिना पहले से कठोर स्कीमा परिभाषित किए अर्ध‑संरचित डेटा संग्रहीत कर सकें। विचार सही था: कभी‑कभी आपको वास्तव में नहीं पता होता कि डेटा कैसा दिखेगा, या वह बहुत बार बदलता रहता है जिससे पारंपरिक कॉलम बेकार हो जाते हैं।

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

समस्या यह है कि JSONB सबसे आसान तरीका भी है स्कीमा निर्णयों को टालने का, बिना यह स्वीकार किए कि आप उन्हें टाल रहे हैं। इरादा और कार्यान्वयन के बीच कहीं, यह डेटाबेस में “मैं बाद में कमरा साफ़ करूँगा” कहने के बराबर हो गया। वह अस्थायी समाधान जो आपने छह महीने पहले अपनाया था? वह अभी भी मौजूद है, और अब प्रोडक्शन उस पर निर्भर है।

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

तकनीकी ऋण JSONB में नहीं है। यह उस अंतराल में है जो आपने खुद से कहा था कि आप क्या बना रहे हैं और असल में जो बना दिया: एक अनडॉक्यूमेंटेड “स्कीमा‑ऑन‑रीड” सिस्टम।

आमतौर पर क्या होता है

आप एक फीचर जोड़ रहे हैं और आपको यकीन नहीं है कि उपयोगकर्ताओं को twitter_handle चाहिए या bluesky_handle या कुछ पूरी तरह अलग। स्कीमा के बारे में सोचने के बजाय आप यह करते हैं:

CREATE TABLE users (
id SERIAL PRIMARY KEY,
profile JSONB
);

यह काम करता है। आप फीचर को शिप कर देते हैं, अगला फीचर पर चलते हैं, फिर अगला। JSONB कॉलम पृष्ठभूमि में चुपचाप बढ़ता रहता है।

यह वही मोड़ है जहाँ निर्णय लेना होता है। यदि profile केवल user.id द्वारा प्राप्त किया जाने वाला एक अस्पष्ट ब्लॉब बना रहता है, तो आप शायद ठीक हैं। यदि यह मुख्य व्यापार डेटा का प्राथमिक स्थान बनना शुरू हो जाता है, तो समझौते जल्दी बदलते हैं।

प्रोडक्ट पूछता है: “न्यू यॉर्क में कितने उपयोगकर्ता हैं?”

आप लिखते हैं:

SELECT count(*) FROM users WHERE profile->>'location' = 'New York';

Postgres पूरी टेबल स्कैन करता है। हर एक पंक्ति।

तो आप एक GIN इंडेक्स जोड़ते हैं। शायद यह अभी भी स्वीकार्य है। कभी‑कभी ऐसा ही होता है। लेकिन अब आप वास्तविक जटिलता और स्टोरेज लागत उठा रहे हैं क्योंकि वह फ़ील्ड जो प्रथम‑श्रेणी के रिलेशनल डेटा जैसा व्यवहार करता था, कभी प्रथम‑श्रेणी का कॉलम नहीं बना।

Year 1: Schema Drift

आपके पास एक ही कॉलम में तीन संस्करणों का डेटा है।

आपका एप्लिकेशन कोड अब इस तरह दिखता है:

const city = user.location || user.city || user.address?.city || "Unknown";

आपने स्कीमा नहीं हटाया। आपने केवल वैधता और संगतता जांच को डेटाबेस से निकालकर बिखरे हुए एप्लिकेशन कोड में ले जाया।


जब वास्तव में JSONB का उपयोग करना चाहिए

JSONB के वैध उपयोग मामलों होते हैं। कई बार यह पूरी तरह ठीक रहता है, और कभी‑कभी यह उपलब्ध सबसे अच्छा विकल्प भी होता है।

महत्वपूर्ण अंतर यह नहीं है “स्ट्रक्चर्ड अच्छा, JSON बुरा।” यह कुछ इस तरह है:

वैध JSONB उपयोग मामलों

  1. Webhook Payloads: आप Stripe, Slack, या GitHub से डेटा प्राप्त करते हैं। स्कीमा पर आपका कोई नियंत्रण नहीं है। आप शायद कभी इसे क्वेरी नहीं करेंगे। आपको केवल डिबगिंग या रिप्ले के लिए इसे स्टोर करना है। JSONB के लिए एकदम सही

  2. Logging & Event Streams: एप्लिकेशन लॉग, ऑडिट ट्रेल, एरर कॉन्टेक्स्ट। ये लिखने‑भारी होते हैं, विशिष्ट फ़ील्ड्स द्वारा शायद ही क्वेरी किए जाते हैं, और अक्सर बैच में विश्लेषण या एनालिटिक्स प्लेटफ़ॉर्म पर एक्सपोर्ट किए जाते हैं। JSONB यहाँ ठीक है

  3. User Preferences & Settings: सेटिंग्स ऑब्जेक्ट जहाँ आपके पास 100+ बूलियन फ़्लैग्स होते हैं, अधिकांश फ़ॉल्स होते हैं, और आप हमेशा पूरे ब्लॉब को यूज़र ID से फ़ेच करते हैं। आप WHERE preferences->>'theme' = 'dark' नहीं चला रहे हैं। JSONB काम करता है

  4. LLM Provider / Model Config: यह सबसे स्पष्ट आधुनिक उदाहरणों में से एक है। OpenAI, Anthropic, Gemini, ओपन‑वेट स्थानीय मॉडल, और विक्रेता‑विशिष्ट गेटवे सभी ओवरलैपिंग लेकिन अलग‑अलग पैरामीटर उजागर करते हैं। एक ही प्रोवाइडर के भीतर भी, मॉडल क्षमताएँ और विकल्प नाम विकसित होते रहते हैं। एक JSONB कॉन्फ़िग ब्लॉब अक्सर यह स्वीकार करने में अधिक ईमानदार होता है कि temperature, top_p, reasoning_effort, json_schema, tool_choice और बीस अन्य नॉब्स सभी सार्वभौमिक कॉलम नहीं हो सकते। JSONB अक्सर यहाँ सही एब्स्ट्रैक्शन है

  5. API Response Caching: आप पूरे API प्रतिक्रियाओं को कैश कर रहे हैं। डेटाबेस सिर्फ तेज़ Redis का विकल्प है। आप कैश की द्वारा फ़ेच करते हैं, नेस्टेड प्रॉपर्टीज़ द्वारा नहीं। JSONB उपयुक्त है

  6. Event Sourcing: आप अपरिवर्तनीय इवेंट पेलोड्स स्टोर कर रहे हैं। आपके क्वेरी हमेशा “aggregate X के सभी इवेंट्स समय के क्रम में दें” होते हैं। आप इवेंट प्रॉपर्टीज़ पर WHERE क्लॉज़ नहीं चलाते। JSONB फिट बैठता है

  7. विस्तारशील सतहें: इंटीग्रेशन, प्लगइन सेटिंग्स, प्रति‑टेनेंट ओवरराइड, मार्केटप्लेस मेटाडेटा, प्रोवाइडर क्षमताएँ, या “extras” फ़ील्ड जहाँ आप स्पष्ट रूप से अपेक्षा करते हैं कि उप‑प्रकार के अनुसार संरचना बदलती रहे। JSONB सही कॉन्ट्रैक्ट हो सकता है, समझौता नहीं

सामान्य नियम: यदि एप्लिकेशन ज्ञात कुंजी द्वारा दस्तावेज़ को फ़ेच करता है और उसे वैलिडेट/वर्ज़न करना आता है, तो JSONB उत्कृष्ट हो सकता है। यदि व्यवसाय नेस्टेड कुंजियों पर रिलेशनल प्रश्न पूछता रहता है, तो वे फ़ील्ड कॉलम बनने की कोशिश कर रहे हैं।

सबसे अच्छा पैटर्न अक्सर हाइब्रिड होता है

कई परिपक्व सिस्टम यहाँ समाप्त होते हैं:

CREATE TABLE llm_requests (
id UUID PRIMARY KEY,
provider TEXT NOT NULL,
model TEXT NOT NULL,
status TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
config JSONB NOT NULL
);

यह आम तौर पर दोनों सिरों की तुलना में बेहतर है।

यह “नॉर्मलाइज़ करने में विफलता” नहीं है। यह सही जगह पर सीमा निर्धारित करने की बात है।

बड़े पैमाने पर: ऑब्जेक्ट वर्ज़निंग > नॉर्मलाइज़ेशन

यहाँ बात दिलचस्प हो जाती है। जब डेटा की मात्रा पर्याप्त रूप से बड़ी हो—अर्थात् अरबों पंक्तियों और बार‑बार स्कीमा परिवर्तन—तो “सही” समाधान नॉर्मलाइज़ेशन नहीं, बल्कि ऑब्जेक्ट वर्ज़निंग होता है।

यदि आपके पास अरबों पंक्तियाँ हैं और स्कीमा लगातार बदलता रहता है, तो कॉलम माइग्रेशन महँगा पड़ता है। Stripe, GitHub, और Netflix जैसी कंपनियाँ सब कुछ नॉर्मलाइज़ नहीं करतीं। इसके बजाय वे यह करते हैं:

CREATE TABLE entities (
id UUID PRIMARY KEY,
version INT NOT NULL,
data JSONB NOT NULL
);

आपका एप्लिकेशन जानता है कि version: 1, version: 2, version: 3 को कैसे पढ़ना है। नए फ़ील्ड के लिए कोई डेटाबेस माइग्रेशन नहीं चाहिए। कोड बैकवर्ड कम्पैटिबिलिटी संभालता है।

यह एक आर्किटेक्चरल निर्णय है, आलस्य नहीं। यह डेटाबेस की जटिलता को एप्लिकेशन की जटिलता के बदले में ले जाता है। कभी‑कभी यही सही ट्रेड‑ऑफ़ होता है, विशेषकर जब दस्तावेज़ स्वाभाविक रूप से वर्ज़न‑ड होता है और एप्लिकेशन ही उसका मानक व्याख्याता होता है।

विफलता का कारण “JSONB का उपयोग” नहीं है। विफलता तब आती है जब JSONB को बिना संस्करणीकरण, सत्यापन, प्रमोशन नियमों, या दस्तावेज़ डेटा और रिलेशनल डेटा के बीच स्पष्ट सीमा के उपयोग किया जाता है।

वास्तव में मायने रखने वाले प्रश्न

JSONB कॉलम जोड़ने से पहले, स्वयं से पूछें:

  1. क्या हम WHERE, JOIN, GROUP BY, या ORDER BY में नेस्टेड फ़ील्ड्स को नियमित रूप से क्वेरी करेंगे?
  2. क्या हम इस स्कीमा को नियंत्रित करते हैं, या यह बाहरी रूप से परिभाषित और अस्थिर है?
  3. क्या रिकॉर्ड्स के बीच आकार जानबूझकर विषम है?
  4. क्या हमारे पास एप्लिकेशन‑स्तर का सत्यापन और संस्करणीकरण है?
  5. कौन‑से फ़ील्ड बाद में ऑपरेशनल डाइमेंशन बन सकते हैं?

यदि #1 का उत्तर “हाँ, लगातार” है, तो यह कॉलम्स के लिए एक मजबूत संकेत है।

यदि #2 और #3 के उत्तर “हाँ” हैं, तो JSONB संभवतः आपके लिए वास्तविक कार्य कर रहा है।


ट्रैप से बचना

यदि आप पहले से ही इस गड्ढे में हैं, तो खुदाई बंद करें।

  1. ऑडिट: jsonb_object_keys चलाएँ और वास्तविक आकार‑विचलन को देखें, न कि वह आकार जिसे आप मानते हैं कि मौजूद है।
  2. प्रोमोट: उन फ़ील्ड्स की पहचान करें जिन्हें आप सबसे अधिक फ़िल्टर, जॉइन, सॉर्ट या रिपोर्ट करते हैं। इन्हें वास्तविक कॉलम बनाएँ।
  3. वैलिडेट: जो कुछ भी JSONB में बचा है, उसके लिए एप्लिकेशन‑स्तर या डेटाबेस‑स्तर पर सत्यापन जोड़ें।
  4. वर्ज़न: यदि ब्लॉब वास्तविक डोमेन डेटा है, तो इसे स्पष्ट रूप से संस्करणित करें।
  5. ट्रिम: प्रोमोट किए गए कॉलम स्थापित हो जाने के बाद ब्लॉब से डुप्लिकेट कीज़ हटाएँ।

हर ब्लॉब को सामान्यीकृत करना आवश्यक है, ऐसा न सोचें। साथ ही यह भी न मानें कि स्थायी व्यापारिक अर्थ वाले ब्लॉब “अस्थायी” हैं।

JSONB तब बढ़िया है जब दस्तावेज़ वास्तव में दस्तावेज़‑आकार का हो। यह खतरनाक है जब यह एक रिलेशनल स्कीमा है जो नकली मूँछें पहन रहा है।

संसाधन