सीधे कंटेंट पर जाएं
Configuration

WordPress डेटाबेस में URL सर्च-रिप्लेस कैसे करें, बिना serialized डेटा तोड़े

WordPress साइट का URL डेटाबेस में बदलिए, बिना widgets, menus या थीम सेटिंग्स खाली किए। वह WP-CLI कमांड जो serialized डेटा सही-सलामत रखती है।

Published

WordPress डेटाबेस पर सीधा UPDATE ... SET column = REPLACE(column, 'oldurl', 'newurl') मत चलाइए। यह टेक्स्ट तो ठीक-ठीक बदल देगा और साथ ही आपकी सेटिंग्स भी बर्बाद कर देगा, क्योंकि WordPress arrays और objects को PHP-serialized स्ट्रिंग्स के रूप में सेव करता है, और उस फ़ॉर्मैट में हर स्ट्रिंग की बाइट लंबाई भी दर्ज रहती है। सीधा SQL replace टेक्स्ट दोबारा लिख देता है पर दर्ज लंबाई को कभी नहीं छूता, और वैल्यू पढ़ने लायक ही नहीं बचती।

इसके बजाय ऐसा टूल इस्तेमाल कीजिए जो serialization समझता हो। कमांड लाइन पर वह है wp search-replace। पहले डेटाबेस का बैकअप लीजिए, इसे dry run की तरह चलाइए, फिर असल में चलाइए।

असल में टूटता क्या है

एक PHP-serialized स्ट्रिंग ऐसी दिखती है:

s:18:"http://example.com"

यहाँ 18 उसके बाद आने वाले टेक्स्ट की बाइट लंबाई है। गिन लीजिए — http://example.com ठीक 18 कैरेक्टर का है।

अब सोचिए कि थीम की कोई सेटिंग इस तरह सेव है:

a:1:{s:4:"logo";s:18:"http://example.com";}

अब सीधा SQL replace चलाकर http:// को https:// कीजिए, तो मिलना यह चाहिए:

a:1:{s:4:"logo";s:19:"https://example.com";}

पर मिलता यह नहीं है। मिलता यह है:

a:1:{s:4:"logo";s:18:"https://example.com";}

स्ट्रिंग अब 19 बाइट की है पर दावा अब भी 18 का है। PHP घोषित लंबाई पढ़ता है, 18 बाइट आगे बढ़ता है, जहाँ बंद करने वाला कोट और सेमीकोलन होना चाहिए वहाँ वे मिलते नहीं, और वह हाथ खड़े कर देता है। पूरा array unserialize होने में फेल हो जाता है।

यह चुपचाप क्यों फेल होता है

यही वह हिस्सा है जो इस बग को इतना महँगा बनाता है। WordPress कोई एरर फेंककर रुकता नहीं है। जब वह कोई option पढ़ता है तो वैल्यू को अपने unserialize helper से गुज़ारता है, खराब डेटा पर PHP false लौटा देता है, और WordPress आपके कोड को ऐसी वैल्यू थमा देता है मानो वह option कभी सेट ही नहीं हुआ था। इसके बाद प्लगइन या थीम अपने डिफ़ॉल्ट पर लौट आते हैं।

यानी लक्षण कोई एरर पेज नहीं होता। लक्षण ये होते हैं:

  • Sidebars से widgets गायब हो जाते हैं
  • Customizer की सेटिंग्स थीम के डिफ़ॉल्ट पर रीसेट हो जाती हैं
  • किसी page builder का लेआउट डेटा खाली लौटता है और पेज खाली रेंडर होता है
  • प्लगइन की सेटिंग्स स्क्रीन ऐसी दिखती है मानो अभी-अभी इंस्टॉल हुआ हो
  • Menus की तय जगहें छूट जाती हैं

एडमिन में कहीं कोई इशारा तक नहीं मिलता कि कुछ गड़बड़ है। पंक्ति अब भी डेटाबेस में मौजूद है, और उसमें दिखने में सही टेक्स्ट भी भरा है। बस लंबाई वाला prefix एक-दो बाइट से चूक गया है। unserialization फेल होने पर PHP एक notice ज़रूर देता है, लेकिन प्रोडक्शन साइट पर display errors बंद होने की वजह से उसे कोई देखता ही नहीं। WP_DEBUG चालू करके, WP_DEBUG_LOG को enable और WP_DEBUG_DISPLAY को false रखने पर ये notices एक लॉग फ़ाइल में चले जाते हैं जहाँ आप उन्हें पढ़ सकते हैं।

नुकसान भी हर जगह बराबर नहीं फैलता। साधारण scalar वैल्यू — siteurl, home, सादा पोस्ट कंटेंट, अपने ही कॉलम में पड़ा कोई URL — भोले-भाले replace के बाद भी बिल्कुल ठीक रहती हैं। सिर्फ़ serialized वैल्यू टूटती हैं। यही वजह है कि गलत replace पहली नज़र में अक्सर कामयाब लगता है।

कुछ भी छूने से पहले wp-config.php देखिए

अगर wp-config.php में WP_HOME या WP_SITEURL डिफ़ाइन हैं, तो डेटाबेस में जो भी हो, ये constants उस पर भारी पड़ते हैं। wp_options बदलने पर लगेगा कि कुछ हुआ ही नहीं। डेटाबेस को दोषी ठहराने से पहले ऐसी लाइनें खोजिए:

define( 'WP_HOME', 'http://example.com' );
define( 'WP_SITEURL', 'http://example.com' );

या तो इन्हें नए URL पर अपडेट कीजिए, या हटा दीजिए और कमान डेटाबेस को सौंप दीजिए।

पहले बैकअप, और सच में बैकअप

serialization का टूटना हमेशा आसानी से पलटा नहीं जा सकता। एक बार लंबाई वाला prefix और कंटेंट अलग-अलग हो गए, तो टूटी वैल्यू से असली वैल्यू वापस नहीं निकाली जा सकती — मूल स्ट्रिंग जाने बिना यह पता नहीं चल सकता कि सही लंबाई क्या रही होगी। बैकअप ही असली undo है।

wp db export backup-before-replace.sql

या अगर WP-CLI उपलब्ध न हो तो mysqldump से:

mysqldump -u USER -p DBNAME > backup-before-replace.sql

हो सके तो इसे सर्वर से बाहर रखिए। web root में पड़ा बैकअप restore point भी है और सिक्योरिटी की समस्या भी।

सुरक्षित कमांड

WP-CLI का search-replace हर वैल्यू को unserialize करता है, डिकोड किए स्ट्रक्चर में घूमता है, उसी के अंदर बदलाव करता है, और सही लंबाइयों के साथ दोबारा serialize कर देता है। पहले dry run:

wp search-replace 'http://example.com' 'https://example.com' --dry-run --all-tables --skip-columns=guid

रिपोर्ट पढ़िए। यह बताती है कि हर टेबल और कॉलम में कितने बदलाव होंगे। अगर टेबल की सूची गलत लगे, या किसी अनजान टेबल में हज़ारों hits दिखें, तो रुकिए और असल में चलाने से पहले जाँच लीजिए।

फिर इसे चलाइए:

wp search-replace 'http://example.com' 'https://example.com' --all-tables --skip-columns=guid --report-changed-only

flags के बारे में साफ़-साफ़ बात:

  • --all-tables उन टेबल्स को भी कवर करता है जिन्हें WordPress खुद रजिस्टर नहीं करता, और प्लगइन का डेटा अक्सर वहीं रहता है। इसके बिना कुछ प्लगइन टेबल्स पूरी तरह छूट जाती हैं।
  • --skip-columns=guid मायने रखता है। guid फीड रीडर्स के लिए स्थायी पहचान है, कोई जीवित URL नहीं। इसे बदलने पर फीड सब्सक्राइबर्स को आपका पूरा पुराना archive नई पोस्ट्स की तरह दिख सकता है।
  • बिना आख़िरी slash के खोजिए, और protocol के उन रूपों के बिना जिन्हें आप छूना नहीं चाहते। खाली example.com बदलने पर ईमेल पते और पोस्ट टेक्स्ट में हुआ हर ज़िक्र भी चपेट में आ जाएगा।

अगर कमांड लाइन इस्तेमाल नहीं कर सकते, तो serialization समझने वाले migration प्लगइन एडमिन स्क्रीन से यही डिकोडिंग का काम करते हैं। जो व्यवहार आपको चाहिए वह साफ़ लिखा होता है कि टूल serialized डेटा संभालता है; अगर कोई टूल ऐसा नहीं कहता, तो मान लीजिए कि वह नहीं करता।

कौन सी टेबल और कौन से कॉलम शामिल हैं

टेबलकॉलमserialized जोखिम
wp_optionsoption_valueज़्यादा — widgets, theme mods, transients, ज़्यादातर प्लगइन सेटिंग्स
wp_postmetameta_valueज़्यादा — page builder लेआउट, custom field arrays
wp_usermetameta_valueज़्यादा — capabilities, स्क्रीन और डैशबोर्ड प्राथमिकताएँ
wp_termmetameta_valueमध्यम — प्लगइन पर निर्भर
wp_postspost_content, guidकंटेंट के लिए कम, पर shortcode attributes और block markup में URL होते हैं
wp_commentscomment_content, comment_author_urlकम
wp_linkslink_url, link_imageकम, और अक्सर इस्तेमाल में ही नहीं

multisite पर इनमें wp_blogs, wp_site, wp_sitemeta, और हर साइट के wp_2_options, wp_3_options वगैरह भी जोड़ लीजिए। multisite पर URL बदलने के नियम सर्च और replace से आगे जाते हैं — उसे अलग काम मानकर चलिए।

जो मामले search-replace पूरी तरह हल नहीं करता

सीमाओं के बारे में ईमानदारी से:

डेटाबेस के अंदर सेव JSON। कुछ page builders लेआउट डेटा JSON में escape किए forward slashes के साथ रखते हैं, इसलिए आपका URL https://example.com की जगह https:\/\/example.com जैसा दिखता है। सामान्य रूप की खोज इसे चूक जाएगी। escape किए स्ट्रिंग पर दूसरा pass चलाना पड़ सकता है। पहले किसी कॉपी पर टेस्ट कीजिए।

Base64 में encode की गई वैल्यू। अगर कोई प्लगइन सेटिंग्स सेव करने से पहले उन्हें encode करता है, तो कोई भी टेक्स्ट-आधारित replace उस URL को देख ही नहीं सकता। ऐसी सेटिंग्स आम तौर पर प्लगइन की अपनी स्क्रीन से दोबारा भरनी पड़ती हैं।

फ़ाइलों में हार्डकोड किए URL। functions.php, child theme के किसी टेम्पलेट, या किसी हार्डकोड की गई asset path में लिखी चीज़ डेटाबेस में है ही नहीं और छुई नहीं जाएगी। थीम डायरेक्टरी को अलग से grep कीजिए।

Cache और generated आउटपुट। सही replace के बाद भी object cache, page cache और CDN की कॉपियाँ पुराने URL परोसती रहती हैं। इन्हें flush कीजिए, फिर replace को नाकाम मानने से पहले दोबारा जाँचिए।

अगर आप गलत query पहले ही चला चुके हैं

बैकअप restore कीजिए। जवाब यही है, और इसे साफ़-साफ़ कहना ज़रूरी है, बजाय कोई ऐसी मरम्मत की प्रक्रिया बताने के जो ज़्यादातर काम नहीं करती।

अगर बैकअप है ही नहीं, तो बचने का रास्ता संकरा है: जो वैल्यू कभी serialized थीं ही नहीं वे ठीक हैं, और टूटी हुई serialized वैल्यू को या तो हाथ से दोबारा बनाना पड़ेगा या संबंधित सेटिंग्स स्क्रीन दोबारा सेव करके रीसेट करना पड़ेगा। किसी widget एरिया को दोबारा सेव करना, menu की जगह दोबारा चुनना, या किसी प्लगइन की सेटिंग्स दोबारा भरना टूटी वैल्यू के ऊपर एक नई, सही तरीके से serialize की गई वैल्यू लिख देता है। थकाऊ है, पर काम करता है, और यह उन खराब पंक्तियों को वैसे ही छोड़ देने से बेहतर है जो अगली बार साइट देखने वाले को उलझा देंगी।

कुछ भी चलाने से पहले सही statements तैयार करने के लिए इन्हें WordPress search and replace SQL tool से बनाइए — यह दिखाता है कि किन कॉलम्स पर सादा SQL चलाना सुरक्षित है और किन्हें serialization समझने वाले तरीके की ज़रूरत है, ताकि किसी कमांड पर टिकने से पहले आपको यह बँटवारा साफ़ दिख जाए।

FAQ

सवाल

सीधा SQL REPLACE चलाने से WordPress साइट क्यों टूट जाती है?

क्योंकि WordPress arrays और objects को PHP-serialized टेक्स्ट के रूप में सेव करता है, और उस फ़ॉर्मैट में हर स्ट्रिंग की बाइट लंबाई भी दर्ज रहती है। सीधा REPLACE टेक्स्ट तो बदल देता है, पर पुरानी लंबाई वाला नंबर वैसा ही छोड़ देता है। इसके बाद PHP उस वैल्यू को unserialize करने से मना कर देता है, WordPress डिफ़ॉल्ट पर लौट आता है, और सेटिंग्स गलत नहीं बल्कि पूरी तरह खाली दिखने लगती हैं।

डेटाबेस में WordPress साइट का URL बदलने का सबसे सुरक्षित तरीका क्या है?

पहले पूरा डेटाबेस बैकअप लीजिए, फिर WP-CLI कमांड wp search-replace को पहले dry run के साथ चलाइए। WP-CLI हर वैल्यू को unserialize करता है, डिकोड किए हुए स्ट्रक्चर के अंदर बदलाव करता है, और सही लंबाई के साथ दोबारा serialize कर देता है। अगर कमांड लाइन उपलब्ध नहीं है तो serialization समझने वाले migration प्लगइन एडमिन स्क्रीन से यही काम करते हैं।

पुराना URL किन टेबल्स में पड़ा रहता है?

मुख्य रूप से wp_options, wp_posts, wp_postmeta, wp_usermeta, wp_termmeta और wp_comments में। serialized वाला असली खतरा wp_options और किसी भी meta_value कॉलम में होता है, क्योंकि widgets, theme mods, transients और page builder का डेटा arrays के रूप में सेव रहता है। multisite पर wp_blogs, wp_site और wp_sitemeta में भी डोमेन मौजूद रहता है।

अगर wp-config.php में URL तय है, तो क्या डेटाबेस बदलने से काम बनेगा?

नहीं। अगर wp-config.php में WP_HOME या WP_SITEURL डिफ़ाइन हैं, तो वे constants wp_options में मौजूद siteurl और home पर भारी पड़ते हैं। जब तक वे constants अपडेट या हटाए नहीं जाते, डेटाबेस में बदलाव करने से सामने कुछ नहीं बदलेगा। इसलिए सबसे पहले wp-config.php देखिए — यही एक लाइन कई फेल हुए migrations की वजह होती है।

क्या guid कॉलम भी बदलना चाहिए?

नहीं। guid फीड रीडर्स के लिए एक स्थायी पहचान है, चालू URL नहीं, और इसे बदलने पर फीड सब्सक्राइबर्स को हर पुरानी पोस्ट दोबारा नई दिख सकती है। WP-CLI चलाते समय skip-columns के साथ guid पास कीजिए। अपवाद सिर्फ़ वही साइट है जिसके कभी पब्लिक फीड या सब्सक्राइबर रहे ही नहीं।

कैसे पता चलेगा कि गलत replace के बाद कोई वैल्यू टूट चुकी है?

टूटी हुई serialized वैल्यू डेटाबेस में देखने पर सामान्य लगती है, लेकिन WordPress में खाली आती है। Widgets गायब हो जाते हैं, theme customizer की सेटिंग्स रीसेट हो जाती हैं, और प्लगइन के विकल्प बिना किसी एरर मैसेज के डिफ़ॉल्ट पर लौट आते हैं। WP_DEBUG और WP_DEBUG_LOG चालू करने पर unserialize वाला notice सामने आ जाता है। घोषित स्ट्रिंग लंबाई और असली बाइट गिनती की तुलना करने पर यह सीधे पक्का हो जाता है।