2025, Oct 30 20:34
क्लाउड पर Oracle में python-oracledb से CLOB अपडेट धीमे क्यों होते हैं और उन्हें तेज़ कैसे करें
उच्च लेटेंसी नेटवर्क पर Oracle में python-oracledb से CLOB अपडेट क्यों धीमे पड़ते हैं, LOB लोकेटर का असर, और गति के लिए varchar2(4000), बैच executemany, Thin मोड।
python-oracledb के जरिए CLOB अपडेट धीमे होना तब उलझन पैदा करता है, जब बाकी सब कुछ तेज़ चलता है। पैटर्न परिचित है: लोकल डेटाबेस पर ऑब्जेक्ट टाइप अपडेट फुर्ती से हो जाते हैं, लेकिन क्लाउड-होस्टेड Oracle इंस्टेंस में वही काम रेंगने लगता है। फर्क पेलोड के आकार में नहीं, बल्कि लेटेंसी प्रोफाइल में और अंदरखाने LOBs को कैसे हैंडल किया जाता है, इसमें है।
सेटअप को दोहराना
नीचे का उदाहरण CLOB एट्रिब्यूट वाला एक ऑब्जेक्ट टाइप बनाता है और फिर Python से 40,000 इंस्टेंसेज़ भरता है। लॉजिक एक सामान्य ऑब्जेक्ट-निर्माण/अपडेट लूप जैसा है; सिर्फ नाम अलग हैं।
-- CLOB एट्रिब्यूट वाला ऑब्जेक्ट टाइप
create TYPE DOC_REC_T as OBJECT(
   DOC_ID NUMBER,
   DOC_BODY CLOB
);
import os
import time
import numpy as np
import oracledb
import pandas as pd
dsn = "localhost:1521/ORCLCDB"
db_user = "SYS"
db_pass = "mypassword1"
try:
    client_dir = os.path.join(os.environ.get("HOME"), "db", "instantclient_23_3")
    oracledb.init_oracle_client(lib_dir=client_dir)
    total_items = 40000
    payload = {
        'txt_col': [f"This is a very long string example for row {k}. It can contain various characters and be quite lengthy to simulate real-world data scenarios." * 5 for k in range(total_items)],
        'num_col': np.random.randint(1000, 100000, size=total_items)
    }
    frame = pd.DataFrame(payload)
    conn = oracledb.connect(user=db_user, password=db_pass, dsn=dsn, mode=oracledb.SYSDBA)
    print("Connected to Oracle Database (CDB Root) using Thin/Thick Client successfully!")
    cur = conn.cursor()
    objects_out = []
    obj_type = conn.gettype("DOC_REC_T")
    pool = [obj_type.newobject() for _ in range(total_items)]
    t0 = time.time()
    for idx, row in enumerate(frame.itertuples(index=False), start=0):
        rec = pool[idx]
        setattr(rec, 'DOC_ID', idx)
        setattr(rec, 'DOC_BODY', f"Name_{idx}" * 4000)
        objects_out.append(rec)
    t1 = time.time()
    print(f"Time to create objects and assign attributes: {t1 - t0:.4f} seconds")
    print(f"Total objects: {len(objects_out)}")
except oracledb.Error as exc:
    err, = exc.args
    print(f"Oracle error: {err.message}")
finally:
    if 'cur' in locals() and cur:
        cur.close()
    if 'conn' in locals() and conn:
        conn.close()
असल में धीमा क्या करता है
डेटाबेस को क्लाउड प्रोवाइडर पर ले जाने से नेटवर्क की प्रकृति बदल जाती है। ऑन-प्रेम या डेस्कटॉप से क्लाउड तक की लेटेंसी भूगोल, फ़ायरवॉल की कई परतों, आइसोलेटेड स्विचिंग और ट्रैफिक इंस्पेक्शन की वजह से आसानी से दर्जनों मिलीसेकंड हो सकती है। यह LAN की एक-अंक मिलीसेकंड लेटेंसी से कई गुना अधिक है।
महत्वपूर्ण बात यह है कि तार के पार LOBs कैसे काम करते हैं। जब LOB शामिल होता है, क्लाइंट एक ही कॉल में सामान्य स्केलर की तरह पूरा मान न तो भेजता है और न ही प्राप्त करता है। इसके बजाय एक LOB लोकेटर का आदान-प्रदान होता है, और LOB सामग्री को भेजने या पढ़ने के लिए अतिरिक्त कॉल की जाती हैं। LOB छोटा हो तब भी उसे प्रोसेस करने के लिए प्रति-रो एक राउंड ट्रिप लगती है। हजारों रो के साथ, प्रति-रो लेटेंसी का योग कुल समय पर हावी हो जाता है। इसलिए लोकल रन सामान्य लगते हैं, जबकि क्लाउड-होस्टेड डेटाबेस बहुत धीमे प्रतीत होते हैं।
LOB डेटा टाइप्स को क्लाइंट ड्राइवर द्वारा विशेष प्रोसेसिंग चाहिए। ड्राइवर हर रो के लिए अतिरिक्त LOB कॉल करता है, और हर कॉल पर उच्च लेटेंसी की कीमत चुकानी पड़ती है।
LOB वाले व्यवहार के अलावा, यूज़र-डिफाइंड ऑब्जेक्ट टाइप इस्तेमाल करने से सादे स्केलर की तुलना में और अधिक राउंड ट्रिप्स जुड़ जाती हैं, जिससे प्रभाव और बढ़ जाता है।
समस्या से कैसे निपटें
सबसे कारगर तरीका यह है कि जहां संभव हो, ऊंची लेटेंसी वाले लिंक पर LOB प्रोसेसिंग से बचें। यदि टेक्स्ट हमेशा 4 KB से छोटा है, तो उसे स्केलर रखें और varchar2(4000) का उपयोग करके LOB सेमांटिक्स को पूरी तरह हटा दें। यदि स्कीमा स्तर पर यह संभव नहीं है, तो पढ़ते या writते समय CAST करें।
अगर मान आमतौर पर छोटे हैं लेकिन कभी-कभार 4 KB से ऊपर चले जाते हैं, तो काम को दो चरणों में बांटें। पहले varchar2-CAST वाली रो को लाएं/प्रोसेस करें और लंबी टेल को अलग से संभालें जहां LOB प्रोसेसिंग वास्तव में आवश्यक हो।
यदि कंटेंट का सिर्फ शुरुआती हिस्सा चाहिए, तो एक्सेस के दौरान सर्वर-साइड पर ही उसे ट्रंकेट करें। इससे ट्रांसफर के लिए डेटा स्केलर रूप में रहता है।
-- मान <= 4000 बाइट्स हों तो सब कुछ स्केलर रखें
SELECT CAST(mylob AS varchar2(4000)) AS mylob, col2, col3 FROM your_table;
-- यदि आपको सिर्फ शुरुआत चाहिए, तो एक्सेस के दौरान ट्रंकेट करें
SELECT CAST(SUBSTR(mylob, 1, 4000) AS varchar2(4000)) AS mylob, col2, col3 FROM your_table;
जब आप चयन करने के बजाय अपडेट कर रहे हों, तब भी LOB पर वही लेटेंसी लागत लागू होती है। व्यावहारिक उपाय भी वही है: जहां डेटा फिट हो, वहां varchar2(4000) का उपयोग करें, अन्यथा यह देखें कि कितनी रो को LOB हैंडलिंग की आवश्यकता है और उसे कम करें।
डेटा लोड करने का तेज़ रास्ता
ऑब्जेक्ट्स और LOB लोकेटर्स अतिरिक्त राउंड ट्रिप्स जोड़ते हैं। सीधे स्केलर बाइंड करना और स्टेटमेंट्स को बैच करना आमतौर पर बेहतर थ्रूपुट देता है। 40,000 ऑब्जेक्ट इंस्टेंस बनाने के बजाय, सादे NUMBER और CLOB जोड़ी के साथ बैच इन्सर्ट द्वारा रो भेजें। इससे ऑब्जेक्ट ओवरहेड बचता है और ड्राइवर की बैचिंग पाइपलाइन का लाभ मिलता है।
-- स्केलर कॉलम वाली तालिका
CREATE TABLE DOCS_TBL (
  DOC_ID   NUMBER,
  DOC_BODY CLOB
);
import oracledb
# मान लें कि conn खुला है
cur = conn.cursor()
rows = [(i, f"Name_{i}" * 4000) for i in range(40000)]
cur.executemany(
    "insert into DOCS_TBL (DOC_ID, DOC_BODY) values (:1, :2)",
    rows
)
conn.commit()
यह तरीका python-oracledb की ट्यूनिंग गाइडेंस से मेल खाता है कि ऑब्जेक्ट्स से बचना तेज़ हो सकता है और LOB लोकेटर्स का उपयोग स्ट्रिंग/बफ़र की तरह बाइंड करने की तुलना में धीमा होता है। यह ड्राइवर और डेटाबेस को कम, बड़े नेटवर्क ऑपरेशनों में बैचिंग को बेहतर करने देता है।
ड्राइवर मोड से जुड़ी बातें
यदि आप python-oracledb को Thick मोड में इस्तेमाल कर रहे हैं, तो Thin मोड का परीक्षण करने पर विचार करें। ऐसे परिदृश्यों में हल्का-सा सुधार दिख सकता है, और ऑब्जेक्ट्स को फ़ेच करने के लिए Thin मोड खास तौर पर फायदेमंद है। “Python-oracledb Thin mode Object performance” नामक लेख इस पर अच्छा परिप्रेक्ष्य और Thin मोड में ऑब्जेक्ट प्रदर्शन के आंकड़े देता है। हालांकि यदि आपका कोड-पाथ ऑब्जेक्ट्स और LOBs पर निर्भर है, तो LOB प्रोसेसिंग की मूल लेटेंसी विशेषताएं वैसे ही लागू रहेंगी।
जब LOB से बचना संभव न हो
यदि अधिकांश मान वास्तव में 4 KB से अधिक हैं और पूरी निष्ठा से चाहिए, तो सबसे असरदार लीवर राउंड ट्रिप्स की संख्या घटाना है। जहां पूर्ण एक्सट्रैक्ट जरूरी न हो, वहां इन्क्रिमेंटल पुल पर्याप्त हों तो उसी के अनुसार प्रोसेसिंग को पुनर्संरचित करें, या क्लाइंट-सरवर चैटर कम करने के लिए डेटा के पास ही ऑपरेशन कर दें। जब कंटेंट वास्तव में स्ट्रक्चर्ड डेटा हो, तो उसे अनस्ट्रक्चर्ड CLOB में रखने के बजाय स्केलर कॉलम और चाइल्ड टेबल्स में नॉर्मलाइज़ करें; यह LOBs को मानक प्रकारों में बदल देता है और अक्सर LOB वाला रास्ता बिल्कुल हट जाता है।
यह क्यों मायने रखता है
सब-सेकंड और कई घंटों की रन के बीच का अंतर अक्सर प्रति-रो राउंड-ट्रिप लागत के हजारों रो पर गुणा होने से आता है। LOBs एक दिखने में एकल अपडेट को कई नेटवर्क कॉल में बदल देते हैं, और उच्च-लेटेंसी नेटवर्क पर हर कॉल महंगा पड़ता है। यह पहचानना कि आपकी डिज़ाइन कब अप्रत्यक्ष रूप से LOB प्रोसेसिंग ट्रिगर करती है, आपको ऐसे विकल्प चुनने देता है जो डेटा को स्केलर रूप में रखते हैं या राउंड ट्रिप्स को नाटकीय रूप से घटाते हैं।
अंतिम सलाह
शुरुआत क्लाइंट और क्लाउड डेटाबेस के बीच लेटेंसी प्रोफाइल की पुष्टि से करें; जो व्यवहार आप देख रहे हैं, वह उच्च-लेटेंसी लिंक के अनुरूप है। छोटे टेक्स्ट के लिए varchar2(4000) को प्राथमिकता दें ताकि स्केलर पथ पर बने रहें। जहां LOB संभालना अनिवार्य हो, वहां CAST, ट्रंकेशन या वर्कलोड को बांटकर उन रो का दायरा छोटा करें जिन्हें LOB सेमांटिक्स चाहिए। हॉट पाथ में यूज़र-डिफाइंड ऑब्जेक्ट्स से बचें और इन्सर्ट करते समय executemany के साथ बैच करें। यदि आपके वातावरण में संभव हो, तो Thin मोड को आज़माएं ताकि क्रमिक लाभ दिखें। ये कदम असल कारण—उच्च-लेटेंसी नेटवर्क पर LOB प्रोसेसिंग के राउंड-ट्रिप पैटर्न—पर वार करते हैं और रनटाइम को आपकी लोकल अनुभूति के करीब लाते हैं।
यह लेख StackOverflow के प्रश्न (द्वारा: omkar kothiwale) और Paul W के उत्तर पर आधारित है।