PostgreSQL में शून्य-डाउनटाइम इंडेक्स परिवर्तन: एक सुरक्षित प्लेबुक
CONCURRENTLY, सरल लॉक चेक और स्पष्ट रोलबैक स्टेप्स के साथ PostgreSQL में शून्य-डाउनटाइम इंडेक्स परिवर्तन ताकि प्रोडक्शन ट्रैफिक बना रहे।

क्यों इंडेक्स परिवर्तन डाउनटाइम पैदा करते हैं (और इसे कैसे बचें)
इंडेक्स का काम मासूम लगता है। आप "सिर्फ" एक मददगार स्ट्रक्चर जोड़ रहे हैं। PostgreSQL में, हालांकि, इंडेक्स बनाना, हटाना या बदलना ऐसे लॉक ले सकता है जो दूसरी सेशंस को ब्लॉक करते हैं। अगर टेबल व्यस्त है तो ये वेट्स जमा हो जाते हैं और ऐप टूटे जैसा महसूस होने लगता है।
डाउनटाइम शायद साफ़ आउटेज बैनर जैसा नहीं दिखता। यह अक्सर पन्नों के हैंग होने, बैकग्राउंड जॉब्स के पीछे रहने, और डेटाबेस पर फंसे हुए अनुरोधों की बढ़ती कतार के रूप में आता है। कोई "Search" दबाता है और टाइमआउट मिलता है, जबकि सपोर्ट टूल्स और एडमिन स्क्रीन अचानक स्लगी लगने लगती हैं क्योंकि साधारण क्वेरीज़ भी उन्हें चाहिए लॉक नहीं पा रही हैं।
"रात में चलाओ" दो सामान्य कारणों से फेल हो जाता है। कई सिस्टम कभी पूरी तरह शांत नहीं होते (ग्लोबल यूज़र्स, बैच जॉब्स, ETL, बैकअप)। और इंडेक्स ऑपरेशन्स अपेक्षा से ज़्यादा समय ले सकते हैं क्योंकि वे बहुत डेटा पढ़ते हैं और CPU व डिस्क के लिए प्रतिस्पर्धा करते हैं। अगर विंडो बिल्ड के बीच बंद हो जाए, तो आप इंतज़ार करने और काम रुकवाने के बीच फँस जाते हैं।
शून्य-डाउनटाइम इंडेक्स परिवर्तन जादू नहीं हैं। ये सबसे कम-अवरोधक ऑपरेशन चुनने, गार्डरेयल्स (टाइमआउट और डिस्क चेक) सेट करने, और चलते समय डेटाबेस पर नज़र रखने में आते हैं।
यह प्लेबुक व्यावहारिक प्रोडक्शन आदतों पर केंद्रित है:
- पढ़ाई और लिखाई जारी रखने के लिए concurrent इंडेक्स बिल्ड्स को प्राथमिकता दें।
- लॉक और बिल्ड प्रोग्रेस की निगरानी करें ताकि आप जल्दी प्रतिक्रिया दे सकें।
- अगर बदलाव रिग्रेशन लाए या बहुत लंबा चले तो रोलबैक रास्ता तैयार रखें।
यह कवर नहीं करता: गहरी इंडेक्स डिज़ाइन थ्योरी, विस्तृत क्वेरी ट्यूनिंग, या ऐसे स्कीमा रिफैक्टर जो बहुत सारा डेटा री-राइट करते हैं।
इंडेक्स काम के पीछे सरल लॉक मॉडल
PostgreSQL कई सेशंस एक ही टेबल को छूने पर डेटा सही रखने के लिए लॉक का उपयोग करता है। एक लॉक बस एक नियम है जो बताता है कि अभी किसे किसी ऑब्जेक्ट को पढ़ने या लिखने की अनुमति है और किसे वेट करना होगा।
अधिकांश समय आप लॉक्स को नोटिस नहीं करते क्योंकि PostgreSQL हल्के मोड्स का उपयोग कर सकता है जो सामान्य क्वेरीज़ को चलने देते हैं। DDL अलग है। जब आप इंडेक्स बनाते या हटाते हैं, PostgreSQL को कैटालॉग और डेटा को कंसिस्टेंट रखने के लिए टेबल पर पर्याप्त नियंत्रण चाहिए होता है। जितना अधिक नियंत्रण चाहिए उतनी ही अन्य सेशंस को वेट करना पड़ सकता है।
इंडेक्स बनाना बनाम इंडेक्स का उपयोग
इंडेक्स का उपयोग आम तौर पर लॉकिंग के लिहाज़ से सस्ता होता है। SELECT, UPDATE, और DELETE क्वेरीज़ इंडेक्स पढ़ या बनाए रख सकती हैं जबकि दूसरी सेशंस भी वही कर रही हों।
इंडेक्स बनाना अलग है। PostgreSQL को टेबल स्कैन करना, कीज़ को सॉर्ट या हैश करना, और डिस्क पर नई स्ट्रक्चर लिखनी पड़ती है। वह काम समय लेता है, और समय वही है जो प्रोडक्शन में "छोटे लॉक" को "बड़ी समस्याओं" में बदल देता है।
CONCURRENTLY क्या बदलता है (और क्या नहीं)
एक सामान्य CREATE INDEX एक मजबूत लॉक लेता है जो पूरे बिल्ड के दौरान लिखने को ब्लॉक कर देता है। CREATE INDEX CONCURRENTLY को इस तरह डिज़ाइन किया गया है कि इंडेक्स बनाते समय सामान्य पढ़ाई और लिखाई चलती रहें।
लेकिन "concurrent" का मतलब "बिल्कुल लॉक नहीं" नहीं है। शुरुआत और अंत में अभी भी छोटे लॉक विंडो होते हैं, और बिल्ड तब फेल या रुक सकता है अगर कुछ और असंगत लॉक रखे हुए हों।
जो परिणाम सबसे ज़्यादा मायने रखते हैं:
- गैर-कन्करेंट बिल्ड्स टेबल पर इनसर्ट्स, अपडेट्स और डिलीट्स को ब्लॉक कर सकते हैं।
- कन्करेंट बिल्ड्स आमतौर पर पढ़ाई और लिखाई की अनुमति देते हैं, लेकिन लंबे ट्रांज़ेक्शन्स से धीमे या अटके हुए हो सकते हैं।
- फिनिशिंग स्टेप्स अभी भी संक्षिप्त लॉक मांगते हैं, इसलिए बहुत व्यस्त सिस्टम छोटे वेट देख सकते हैं।
सही तरीका चुनें: concurrent या नहीं
इंडेक्स बदलते समय आपके पास दो मुख्य विकल्प हैं: इंडेक्स को सामान्य रूप से बनाना (तेज़, पर ब्लॉकिंग), या CONCURRENTLY के साथ बनाना (आमतौर पर एप्लिकेशन ट्रैफिक के लिए गैर-ब्लॉकिंग, पर धीमा और लंबे ट्रांज़ेक्शन्स के प्रति संवेदनशील)।
कब CONCURRENTLY सही विकल्प है
जब टेबल वास्तविक ट्रैफिक सर्व करती हो और आप लिखने को रोक नहीं सकते तो CREATE INDEX CONCURRENTLY का उपयोग करें। यह आमतौर पर सुरक्षित विकल्प है जब:
- टेबल इतनी बड़ी हो कि सामान्य बिल्ड मिनट्स या घंटों तक ले सकता है।
- टेबल पर लगातार लिखाई होती हो, सिर्फ पढ़ाई नहीं।
- आप असली मेंटेनेंस विंडो शेड्यूल नहीं कर सकते।
- आपको पहले इंडेक्स बनाना है, सत्यापित करना है, और बाद में पुराना इंडेक्स हटाना है।
कब सामान्य इंडेक्स बिल्ड स्वीकार्य है
एक सामान्य CREATE INDEX ठीक हो सकता है जब टेबल छोटी हो, ट्रैफिक कम हो, या आपके पास नियंत्रित विंडो हो। यह अक्सर तेज़ खत्म होता है और चलाने में सरल होता है।
यदि बिल्ड स्टेजिंग में लगातार तेज़ होता है और आप अस्थायी रूप से लिखने बंद कर सकते हैं (भले ही थोड़ी देर के लिए), तो सामान्य तरीका पर विचार करें।
अगर आपको यूनिकनेस चाहिए, तो जल्दी से फैसला करें। CREATE UNIQUE INDEX CONCURRENTLY काम करता है, पर यदि डुप्लिकेट वैल्यूज़ मौजूद हों तो यह फेल होगा। कई प्रोडक्शन सिस्टम्स में डुप्लिकेट्स ढूँढना और ठीक करना असली प्रोजेक्ट होता है।
प्रोडक्शन छूने से पहले प्रीफ्लाइट चेक्स
ज़्यादातर समस्याएँ कमांड शुरू होने से पहले ही होती हैं। कुछ चेक आपको दो बड़े सरप्राइज़ से बचाने में मदद करेंगे: अनपेक्षित ब्लॉकिंग और ऐसा इंडेक्स बिल्ड जो आपके प्लान से बहुत लंबा (या अधिक स्पेस-खर्चीला) चल जाए।
1) सुनिश्चित करें कि आप किसी ट्रांज़ेक्शन के अंदर नहीं हैं। CREATE INDEX CONCURRENTLY अगर आप BEGIN के बाद चलाएँगे तो फेल होगा, और कुछ GUI टूल्स चुपचाप स्टेटमेंट्स को ट्रांज़ेक्शन में लपेट देते हैं। अगर आप निश्चित नहीं हैं तो एक नया सेशन खोलें और केवल वही इंडेक्स कमांड वहाँ चलाएँ।
2) समय और डिस्क के लिए अपेक्षाएँ सेट करें। कन्करेंट बिल्ड आमतौर पर सामान्य बिल्ड से ज़्यादा समय लेते हैं और चलने के दौरान अतिरिक्त वर्किंग स्पेस की ज़रूरत होती है। नए इंडेक्स प्लस अस्थायी ओवरहेड के लिए योजना बनाएँ और सुनिश्चित करें कि आपके पास आरामदायक फ्री डिस्क हेडरूम है।
3) अपने लक्ष्य के अनुसार टाइमआउट सेट करें। आप चाहते हैं कि अगर लॉक नहीं मिल रहा तो बिल्ड जल्दी फेल हो, पर आप यह भी नहीं चाहते कि सेशन आक्रामक statement_timeout के कारण बिल्ड के बीच मर जाए।
4) एक बेसलाइन कैप्चर करें। आप यह प्रमाण चाहते हैं कि बदलाव मददगार था और रिग्रेशन जल्दी पकड़े जा सकें। एक पहले का स्नैपशॉट रिकॉर्ड करें: स्लो क्वेरी का टाइमिंग, एक प्रतिनिधि EXPLAIN (ANALYZE, BUFFERS), और CPU, IO, कनेक्शन्स, और फ्री डिस्क का तात्कालिक दृश्य।
कई टीमों द्वारा उपयोग की जाने वाली सुरक्षित सेशन सेटिंग्स (अपनी नीतियों के अनुसार समायोजित करें):
-- Run in the same session that will build the index
SET lock_timeout = '2s';
SET statement_timeout = '0';
स्टेप-बाय-स्टेप: CONCURRENTLY के साथ इंडेक्स बनाना
जब आपको एप्लिकेशन ट्रैफिक चलते रहने की जरूरत हो और आप थोड़ा लंबा बिल्ड सह सकते हों तो CREATE INDEX CONCURRENTLY का उपयोग करें।
पहले तय करें कि आप क्या बना रहे हैं:
- कॉलम ऑर्डर के बारे में स्पष्ट रहें (यह मायने रखता है)।
- विचार करें कि क्या partial index पर्याप्त होगा। अगर अधिकांश क्वेरियाँ "active" रोज़ को फ़िल्टर करती हैं तो partial इंडेक्स छोटा, तेज़ और मेंटन करने में सस्ता हो सकता है।
एक सुरक्षित रन इस तरह दिखता है: लक्ष्य और इंडेक्स नाम लिख लें, किसी भी ट्रांज़ेक्शन ब्लॉक के बाहर बिल्ड चलाएँ, इसे पूरा होने तक देखें, फिर प्लैनर को इसे उपयोग करते देखने के बाद ही कुछ हटाएँ।
-- Example: speed up searches by email for active users
CREATE INDEX CONCURRENTLY idx_users_active_email
ON public.users (email)
WHERE status = 'active';
-- Validate it exists
SELECT indexname, indexdef
FROM pg_indexes
WHERE tablename = 'users';
-- Check the plan can use it
EXPLAIN (ANALYZE, BUFFERS)
SELECT id
FROM public.users
WHERE status = 'active' AND email = '[email protected]';
प्रोग्रेस नोट्स (ऑडिट्स के लिए उपयोगी): शुरू और समाप्ति समय तथा किसी भी वेट्स को रिकॉर्ड करें जो आपने देखे। चलते समय आप दूसरे सेशन से pg_stat_progress_create_index क्वेरी कर सकते हैं।
वैलिडेशन सिर्फ "इंडेक्स मौजूद है" नहीं है। पुष्टि करें कि प्लैनर इसे चुन सकता है, फिर तैनाती के बाद असली क्वेरी टाइमिंग देखें। अगर नया इंडेक्स उपयोग में नहीं आ रहा, तो पुराना तुरंत न हटाएँ। पहले क्वेरी या इंडेक्स डेफ़िनिशन ठीक करें।
ब्लॉक किए बिना इंडेक्स बदलना या हटाना (स्टेप-बाय-स्टेप)
सबसे सुरक्षित पैटर्न पहले जोड़ना है, ट्रैफ़िक को नए इंडेक्स से लाभ लेने देना, और तभी पुराना हटाना। इस तरह आप एक कार्यशील फॉल्बैक रखते हैं।
पुराना इंडेक्स नए से बदलना (सुरक्षित क्रम)
-
नया इंडेक्स
CREATE INDEX CONCURRENTLYसे बनाइए। -
सत्यापित कीजिए कि यह उपयोग में है। उन स्लो क्वेरीज़ पर
EXPLAINदेखें जो आप परवाह करते हैं, और समय के साथ इंडेक्स उपयोग पर नज़र रखें। -
केवल उसके बाद पुराना इंडेक्स कन्करेंटली ड्रॉप करें। अगर जोखिम अधिक है, तो हटाने से पहले पूरे बिजनेस साइकिल के लिए दोनों इंडेक्स रखें।
इंडेक्स ड्रॉप करना: कब CONCURRENTLY काम करता है (और कब नहीं)
जो इंडेक्स आपने खुद बनाया है उसके लिए DROP INDEX CONCURRENTLY आमतौर पर सही चुनाव होता है। दो ध्यान देने योग्य बातें: यह ट्रांज़ेक्शन ब्लॉक के अंदर नहीं चल सकता, और शुरू और अंत में अभी भी संक्षिप्त लॉक की ज़रूरत होती है, इसलिए लंबे चल रहे ट्रांज़ेक्शन्स से यह देरी हो सकता है।
यदि इंडेक्स PRIMARY KEY या UNIQUE constraint के कारण मौजूद है, तो आप आमतौर पर उसे सीधे ड्रॉप नहीं कर सकते। आपको constraint को ALTER TABLE से बदलना होगा, जो मजबूत लॉक ले सकता है। इसे अलग, नियोजित मेंटेनेंस ऑपरेशन मानें।
स्पष्टता के लिए इंडेक्स का नाम बदलना
ALTER INDEX ... RENAME TO ... आमतौर पर तेज़ होता है, पर अगर टूलिंग या माइग्रेशन्स इंडेक्स नामों का संदर्भ लेती हैं तो इससे बचें। शुरू से ही स्पष्ट नाम चुनना बेहतर आदत है।
अगर पुराने इंडेक्स की अभी भी ज़रूरत हो
कभी-कभी दो क्वेरी पैटर्न दो अलग इंडेक्स चाहते हैं। अगर महत्वपूर्ण क्वेरी अभी भी पुराने पर निर्भर हैं, तो उसे रखें। नया इंडेक्स (कॉलम ऑर्डर, partial condition) समायोजित करने पर विचार करें बजाय जबरन ड्रॉप करने के।
बिल्ड के दौरान लॉक और प्रोग्रेस मॉनिटर करें
भले ही CREATE INDEX CONCURRENTLY हो, आप चलते समय क्या हो रहा है यह देखें। अधिकांश सरप्राइज़ घटनाएँ दो में से एक कारण से आती हैं: एक ब्लॉकिंग सेशन जो आप नहीं देख रहे थे, या एक लंबा चल रहा ट्रांज़ेक्शन जो बिल्ड को वेट पर रखता है।
ब्लॉकिंग सेशंस पहचानें (कौन किसे ब्लॉक कर रहा है)
लॉक पर वेट कर रही सेशंस ढूँढने से शुरू करें:
SELECT
a.pid,
a.usename,
a.application_name,
a.state,
a.wait_event_type,
a.wait_event,
now() - a.xact_start AS xact_age,
left(a.query, 120) AS query
FROM pg_stat_activity a
WHERE a.wait_event_type = 'Lock'
ORDER BY xact_age DESC;
अगर आपको सटीक ब्लॉकर चाहिए, तो blocked_pid से blocking_pid का पालन करें:
SELECT
blocked.pid AS blocked_pid,
blocking.pid AS blocking_pid,
now() - blocked.xact_start AS blocked_xact_age,
left(blocked.query, 80) AS blocked_query,
left(blocking.query, 80) AS blocking_query
FROM pg_stat_activity blocked
JOIN pg_stat_activity blocking
ON blocking.pid = ANY (pg_blocking_pids(blocked.pid))
WHERE blocked.wait_event_type = 'Lock';
बिल्ड प्रोग्रेस और "अटके" संकेत देखें
PostgreSQL इंडेक्स बिल्ड प्रोग्रेस एक्सपोज़ करता है। अगर आप लंबे समय तक कोई मूवमेंट नहीं देखते, तो किसी लंबे ट्रांज़ेक्शन (अकसर एक idle सेशन जो पुराना स्नैपशॉट पकड़ रहा हो) को देखें।
SELECT
pid,
phase,
lockers_total,
lockers_done,
blocks_total,
blocks_done,
tuples_total,
tuples_done
FROM pg_stat_progress_create_index;
सिस्टम प्रेसर पर भी नजर रखें: डिस्क IO, रेप्लिकेशन लैग, और बढ़ते क्वेरी टाइम्स। कन्करेंट बिल्ड्स अपटाइम के लिए दोस्ताना हैं, पर वे फिर भी बहुत डेटा पढ़ते हैं।
प्रोडक्शन में काम करने वाले सरल नियम:
- अगर प्रोग्रेस बढ़ रहा है और यूज़र इम्पैक्ट कम है तो इंतज़ार करें।
- अगर बिल्ड किसी लंबे ट्रांज़ेक्शन के पीछे अटका हुआ है जिसे आप सुरक्षित रूप से समाप्त नहीं कर सकते, तो कैंसल कर के फिर से शेड्यूल करें।
- अगर IO पीक के दौरान ग्राहक-समक्ष क्वेरियों को नुकसान हो रहा हो तो पीक के दौरान रोक दें।
- समाप्त करने का निर्णय आखिरी सहारे के रूप में लें, और केवल यह पुष्टि करने के बाद कि सेशन क्या कर रहा है।
टीम कम्युनिकेशन के लिए अपडेट छोटे रखें: शुरू होने का समय, वर्तमान फेज, क्या ब्लॉक हो रहा है (यदि कुछ), और आप कब पुनः जाँच करेंगे।
रोलबैक योजना: सुरक्षित तरीके से वापस कैसे जाएँ
इंडेक्स परिवर्तन तभी कम-जोखिम रहते हैं जब आप शुरू करने से पहले निकास योजना बनाते हैं। सबसे सुरक्षित रोलबैक अक्सर नाटकीय उलट नहीं होता। यह बस नया काम रोकना और पुराना इंडेक्स जगह पर रखना है।
सामान्य तरीके जिनसे इंडेक्स काम फेल होता है
अधिकांश प्रोडक्शन फेल्यर्स अनुमानित होते हैं: बिल्ड टाइमआउट पर पहुँचता है, किसी ने एक घटना के दौरान इसे कैंसल कर दिया, सर्वर डिस्क पर कमज़ोर हो गया, या बिल्ड सामान्य ट्रैफ़िक से इतना प्रतिस्पर्धा कर गया कि यूज़र-समक्ष लेटेंसी बढ़ गई।
CREATE INDEX CONCURRENTLY के साथ, ऐप के लिए कैंसल करना आमतौर पर सुरक्षित रहता है क्योंकि क्वेरीज़ चलती रहती हैं। ट्रेडऑफ क्लीनअप है: एक कैंसल या फेल हुआ concurrent बिल्ड एक invalid इंडेक्स छोड़ सकता है।
सुरक्षित कैंसल और क्लीनअप नियम
एक concurrent बिल्ड को कैंसल करना सामान्य ट्रांज़ेक्शन की तरह रोलबैक नहीं करता। PostgreSQL एक ऐसा इंडेक्स छोड़ सकता है जो मौजूद तो है पर प्लैनर के लिए वैध नहीं है।
-- Cancel the session building the index (use the PID you identified)
SELECT pg_cancel_backend(\u003cpid\u003e);
-- If the index exists but is invalid, remove it without blocking writes
DROP INDEX CONCURRENTLY IF EXISTS your_index_name;
ड्रॉप करने से पहले पुष्टि करें कि आप क्या देख रहे हैं:
SELECT
c.relname AS index_name,
i.indisvalid,
i.indisready
FROM pg_index i
JOIN pg_class c ON c.oid = i.indexrelid
WHERE c.relname = 'your_index_name';
अगर indisvalid = false है, तो यह उपयोग में नहीं आ रहा और इसे ड्रॉप करना सुरक्षित है।
पारंपरिक रोलबैक चेकलिस्ट जब आप एक मौजूदा इंडेक्स बदल रहे हों:
- नया इंडेक्स पूरी तरह से बिल्ड और वैध होने तक पुराना इंडेक्स रखें।
- अगर नया बिल्ड फेल या कैंसल हो जाए, तो invalid नए इंडेक्स को कन्करेंटली ड्रॉप करें।
- अगर आपने पहले ही पुराना इंडेक्स हटा दिया है, तो पिछले व्यवहार को बहाल करने के लिए उसे
CREATE INDEX CONCURRENTLYसे फिर से बनाइए। - अगर डिस्क प्रेशर ने फेल कराया, तो पहले स्पेस खाली करें, फिर पुनः प्रयास करें।
- अगर टाइमआउट ने फेल कराया, तो बिना दबाव के एक शांत विंडो शेड्यूल करें बजाय जबरदस्त प्रयास के।
उदाहरण: आप एक नया इंडेक्स एक एडमिन सर्च के लिए शुरू करते हैं, यह 20 मिनट चलता है, फिर डिस्क अलर्ट्स आते हैं। बिल्ड कैंसल करें, invalid इंडेक्स को कन्करेंटली ड्रॉप करें, और पुराना इंडेक्स ट्रैफ़िक को सर्व करता रहे। आप स्पेस खाली करने के बाद बिना यूज़र-देखे आउटेज के फिर से कोशिश कर सकते हैं।
सामान्य गलतियाँ जो सरप्राइज़ आउटेज बनाती हैं
इंडेक्स के आसपास ज्यादातर आउटेज PostgreSQL के "धीमे" होने से नहीं आते। वे इसलिए होते हैं क्योंकि एक छोटा सा विवरण एक सुरक्षित परिवर्तन को ब्लॉकिंग में बदल देता है।
1) कन्करेंट बिल्ड को ट्रांज़ेक्शन के अंदर डालना
CREATE INDEX CONCURRENTLY ट्रांज़ेक्शन ब्लॉक के अंदर नहीं चल सकता। कई माइग्रेशन टूल डिफ़ॉल्ट रूप से हर परिवर्तन को एक ही ट्रांज़ेक्शन में लपेट देते हैं। परिणाम या तो एक हार्ड एरर है (सबसे अच्छा मामला) या रीट्राईज़ के साथ एक गड़बड़ डिप्लॉय।
माइग्रेशन चलाने से पहले पुष्टि करें कि आपका टूल एक स्टेटमेंट को बाहरी ट्रांज़ेक्शन के बिना चला सकता है, या माइग्रेशन को एक विशेष गैर-ट्रांज़ेक्शनल स्टेप में बाँट दें।
2) पीक ट्रैफ़िक के दौरान इसे शुरू करना
कन्करेंट इंडेक्स बिल्ड्स ब्लॉकिंग कम करते हैं, पर वे फिर भी लोड जोड़ते हैं: अतिरिक्त पढ़ाई, अतिरिक्त लिखाई, और autovacuum पर अधिक दबाव। जब ट्रैफ़िक उछल रहा हो तो बिल्ड शुरू करना धीमा कर देने वाली स्थिति पैदा कर सकता है जो आउटेज जैसा महसूस हो।
एक शांत अवधि चुनें और इसे किसी भी अन्य प्रोडक्शन मेंटेनेंस की तरह ट्रीट करें।
3) लंबे चल रहे ट्रांज़ेक्शन्स की अनदेखी
एक अकेला लंबा ट्रांज़ेक्शन कन्करेंट बिल्ड के क्लीनअप चरण को रोक सकता है। इंडेक्स लगता है कि वह प्रोग्रेस कर रहा है, फिर अंत के पास अटक जाता है क्योंकि पुराने स्नैपशॉट्स गायब नहीं हुए।
आदत डालें: शुरू करने से पहले और यदि प्रोग्रेस अटके तो फिर से लंबे चल रहे ट्रांज़ेक्शन्स की जाँच करें।
4) गलत चीज़ ड्रॉप कर देना (या constraint तोड़ देना)
टीमें कभी-कभी मेमोरी से इंडेक्स नाम डाल कर ड्रॉप कर देती हैं, या ऐसा इंडेक्स हटा देती हैं जो यूनिकनेस नियम का समर्थन करता है। अगर आप गलत ऑब्जेक्ट ड्रॉप कर देते हैं तो आप एन्फोर्समेंट खो सकते हैं (unique constraints) या तुरंत क्वेरी प्रदर्शन बिगाड़ सकते हैं।
त्वरित सुरक्षा चेकलिस्ट: कैटलॉग में इंडेक्स नाम की पुष्टि करें, जाँचें कि क्या यह किसी constraint का समर्थन करता है, स्कीमा और टेबल को डबल-चेक करें, और "नया बनाओ" को "पुराना हटाओ" से अलग रखें। शुरू करने से पहले रोलबैक कमांड तैयार रखें।
वास्तविक उदाहरण: एक एडमिन सर्च तेज़ बनाना
एक सामान्य पेन पॉइंट एडमिन सर्च है जो स्टेजिंग में तीव्र लगता है पर प्रोडक्शन में धीमा हो जाता है। मान लीजिए आपके पास एक बड़ा tickets टेबल (दसों मिलियन्स रोज़) है जो एक अंदरूनी एडमिन पैनल के पीछे है, और एजेंट अक्सर "एक ग्राहक के खुले टिकट, नवीनतम पहले" खोजते हैं।
क्वेरी इस तरह दिखती है:
SELECT id, customer_id, subject, created_at
FROM tickets
WHERE customer_id = $1
AND status = 'open'
ORDER BY created_at DESC
LIMIT 50;
(customer_id, status, created_at) पर पूरा इंडेक्स मदद कर सकता है, पर यह हर टिकट अपडेट के लिए लिखने का ओवरहेड जोड़ता है, जिसमें closed ones भी शामिल हैं। अगर अधिकांश रोज़ open नहीं हैं, तो partial index अक्सर सरल और बेहतर जीत है:
CREATE INDEX CONCURRENTLY tickets_open_by_customer_created_idx
ON tickets (customer_id, created_at DESC)
WHERE status = 'open';
प्रोडक्शन में एक सुरक्षित टाइमलाइन:
- प्रीफ्लाइट: पुष्टि करें कि क्वेरी का स्वरूप स्थिर है और टेबल के पास नया इंडेक्स बिल्ड करने के लिए पर्याप्त फ्री डिस्क है।
- बिल्ड: स्पष्ट टाइमआउट सेटिंग्स वाले अलग सेशन में
CREATE INDEX CONCURRENTLYचलाएँ। - वैलिडेट:
ANALYZE tickets;चलाएँ और पुष्टि करें कि प्लैनर नया इंडेक्स उपयोग कर रहा है। - क्लीनअप: एक बार जब आप आश्वस्त हों, तो अब गैर-आवश्यक पुराने इंडेक्स को
DROP INDEX CONCURRENTLYसे हटाएँ।
सफलता का मापदंड:
- सामान्य ग्राहकों के लिए एडमिन सर्च सेकंड्स से दशमलव मिलिसेकंड्स तक गिर जाती है।
- बिल्ड के दौरान नियमित पढ़ाई और लिखाई काम करती रहती हैं।
- बिल्ड के दौरान CPU और डिस्क IO बढ़ते हैं पर आपके सामान्य सुरक्षा सीमाओं के भीतर रहते हैं।
- आप स्पष्ट पहले/बाद के आंकड़ों का हवाला दे सकते हैं: क्वेरी समय, स्कैन की गई पंक्तियाँ, और लॉक इतिहास।
त्वरित चेकलिस्ट और अगले कदम
इंडेक्स का काम तब सबसे सुरक्षित होता है जब आप इसे एक छोटे प्रोडक्शन रिलीज़ जैसा ट्रीट करते हैं: तैयार करें, चलते समय देखें, फिर क्लीनअप करें।
शुरू करने से पहले:
- टाइमआउट सेट करें ताकि एक आश्चर्यजनक लॉक अनिश्चितकाल तक न लटका रहे।
- नए इंडेक्स बिल्ड के लिए पर्याप्त फ्री डिस्क स्पेस की पुष्टि करें।
- लंबे चल रहे ट्रांज़ेक्शन्स की खोज करें जो बिल्ड को धीमा कर सकते हैं।
- एक कम-ट्रैफ़िक विंडो चुनें और यह परिभाषित करें कि "किया हुआ" क्या दिखता है।
- अभी अपनी रोलबैक योजना लिख दें।
चलते समय:
- ब्लॉकिंग और लॉक वेट चेन देखें।
pg_stat_progress_create_indexसे बिल्ड प्रोग्रेस ट्रैक करें।- ऐप के लक्षणों पर नज़र रखें: एरर रेट, टाइमआउट, और टेबल से जुड़े स्लो एंडपॉइंट्स।
- अगर लॉक वेट्स बढ़ें या यूजर-फेसिंग टाइमआउट स्पाइक हों तो कैंसल करने के लिए तैयार रहें।
- क्या हुआ इसका लॉग रखें: शुरू होने का समय, खत्म होने का समय, और कोई अलर्ट्स।
खत्म होने के बाद, पुष्टि करें कि इंडेक्स वैध है, एक-दो मुख्य क्वेरियाँ चलाकर प्लैन और टाइमिंग में सुधार देखें, और तभी पुराने इंडेक्स को गैर-ब्लॉकिंग तरीके से हटाएँ।
अगर आप यह बार-बार करते हैं, तो इसे एक दोहराने योग्य डिलीवरी स्टेप में बदल दें: एक छोटा रनबुक, प्रोडक्शन-जैसा डेटा वाला स्टेजिंग रिहर्सल, और बिल्ड को देखने के लिए एक स्पष्ट ओनर।
अगर आप आंतरिक टूल्स या एडमिन पैनल AppMaster (appmaster.io) के साथ बना रहे हैं, तो डेटाबेस बदलावों को उसी रिलीज चेकलिस्ट का हिस्सा समझना मददगार होता है: मापी, मॉनिटर की गई, और एक तेज़ी से लागू करने योग्य रोलबैक के साथ।
सामान्य प्रश्न
डाउनटाइम आमतौर पर पूरी तरह के आउटेज की तरह नहीं दिखता बल्कि लॉक वेट्स के रूप में आता है। एक सामान्य CREATE INDEX पूरे बिल्ड के दौरान लिखने की क्रियाओं को ब्लॉक कर सकता है, जिससे जो रिक्वेस्ट इनसर्ट, अपडेट या डिलीट करना चाहते हैं वह वेट करने लगते हैं और फिर टाइमआउट हो जाते हैं — इससे पेज हैंग होते हैं और बैकलॉग बनता है।
जब टेबल पर असली ट्रैफिक हो और आप लिखने को रोक नहीं सकते, तो CREATE INDEX CONCURRENTLY का उपयोग करें। यह बड़े या व्यस्त टेबल्स के लिए डिफ़ॉल्ट रूप से सुरक्षित विकल्प है, हालांकि यह धीरे चलता है और लंबे ट्रांज़ेक्शन्स से बाधित हो सकता है।
नहीं। यह ब्लॉकिंग कम कर देता है, लेकिन लॉक-फ्री नहीं है। बिल्ड की शुरुआत और अंत में छोटे लॉक विंडो आते हैं, और अगर अन्य सेशंस असंगत लॉक रखते हैं या लंबे ट्रांज़ेक्शन्स होते हैं तो बिल्ड रुका या वेट कर सकता है।
क्योंकि प्रोडक्शन अक्सर शांत नहीं होता, और इंडेक्स बिल्ड अपेक्षा से बहुत लंबा चल सकता है—टेबल साइज, CPU और डिस्क IO के कारण। अगर बिल्ड आपकी विन्डो के पार चला गया, तो आपको या तो बिजनेस ऑवर्स के दौरान जोखिम बढ़ाने या मिड-चेंज कैंसल करने के बीच चुनना पड़ता है।
सबसे पहले यह सुनिश्चित करें कि आप किसी ट्रांज़ेक्शन के अंदर नहीं हैं, क्योंकि CREATE INDEX CONCURRENTLY ट्रांज़ेक्शन ब्लॉक के अंदर फेल होगा। फिर नए इंडेक्स और अस्थायी ओवरहेड के लिए पर्याप्त फ्री डिस्क की पुष्टि करें, और एक छोटा lock_timeout सेट करें ताकि यदि लॉक न मिलें तो आप जल्दी फेल कर जाएँ।
एक सामान्य शुरुआत यह है कि उसी सेशन में SET lock_timeout = '2s'; और SET statement_timeout = '0'; चलाएँ। इससे आप लॉक पर अनिश्चितकाल तक इंतजार नहीं करेंगे और साथ ही बिल्ड को आक्रामक स्टेटमेंट टाइमआउट से बीच में ही मरने से बचाएंगे।
पहले pg_stat_progress_create_index देखें कि फेज क्या है और ब्लॉक्स/ट्यूपल्स आगे बढ़ रहे हैं या नहीं। अगर प्रोग्रेस अटकी हुई दिखे तो pg_stat_activity में लॉक वेट्स और लंबे चल रहे ट्रांज़ेक्शन्स (खासकर idle सेशंस जो पुरानी स्नैपशॉट पकड़े हुए हों) देखें।
नए इंडेक्स को CREATE INDEX CONCURRENTLY से बनाइए, यह सत्यापित कीजिए कि प्लैन इसे उपयोग कर सकता है (और असली क्वेरी टाइमिंग बेहतर हुई है), और तभी पुराना इंडेक्स DROP INDEX CONCURRENTLY से हटाइए। यह “पहले जोड़ें, बाद में हटाएँ” तरीका अगर नया इंडेक्स काम न करे तो फॉल्बैक रखता है।
DROP INDEX CONCURRENTLY सामान्य इंडेक्स के लिए आमतौर पर सुरक्षित है, लेकिन यह भी छोटे लॉक की ज़रूरत रखता है और ट्रांज़ेक्शन ब्लॉक के अंदर नहीं चल सकता। अगर इंडेक्स किसी PRIMARY KEY या UNIQUE constraint का हिस्सा है, तो आपको आमतौर पर constraint को ALTER TABLE से बदलना होगा, जो ज्यादा मजबूत लॉक ले सकता है और अलग से प्लानिंग माँगेगा।
बिल्ड सेशन को कैंसल करें, फिर देखें कि क्या कोई invalid इंडेक्स बचा है। अगर indisvalid false है तो उसे DROP INDEX CONCURRENTLY से हटा दें और पुराना इंडेक्स वहीं रहने दें; अगर आपने पहले ही पुराना हटा दिया है तो उसे फिर से CREATE INDEX CONCURRENTLY से बनाकर पिछली स्थिति बहाल करें।


