कैसे एक अनइंडेक्स्ड कॉलम ने हमारे डेटाबेस को ठप कर दिया

एक छूटे हुए इंडेक्स ने 8ms की API को 8-सेकंड के बुरे सपने में बदल दिया।

हमने एक बड़े रिलीज़ से पहले लोड टेस्ट किया। लोकल डेवलपमेंट में 100 रोज़ (rows) ठीक से काम कर रहे थे। स्टेजिंग में हमने 1.5 मिलियन रोज़ पर 500 यूज़र्स का सिम्युलेशन किया।

सब कुछ ठप हो गया।

  • API रिस्पॉन्स टाइम 45ms से बढ़कर 8,000ms हो गया।
  • डेटाबेस CPU 100% तक पहुँच गया।
  • कनेक्शन पूल (connection pools) खत्म हो गए।
  • रिक्वेस्ट्स टाइम आउट हो गईं।

इसकी वजह एक साधारण क्वेरी थी जो user_id और status के आधार पर ऑर्डर हिस्ट्री फेच कर रही थी।

PostgreSQL पर EXPLAIN ANALYZE चलाने पर 'Sequential Scan' दिखाई दिया। user_id पर कोई इंडेक्स न होने के कारण, इंजन हर रिक्वेस्ट के लिए सभी 1.5 मिलियन रोज़ को पढ़ रहा था।

100 कॉन्करेंट (concurrent) रिक्वेस्ट्स के दौरान डेटाबेस ने एक साथ 150 मिलियन रोज़ को स्कैन किया।

इसका समाधान करने में केवल पाँच मिनट लगे। हमने CREATE INDEX CONCURRENTLY का उपयोग करके (user_id, status, created_at DESC) पर एक कंपोजिट इंडेक्स बनाया ताकि टेबल ऑनलाइन बनी रहे।

परिणाम:

  • क्वेरी टाइम 8,150ms से घटकर 0.14ms रह गया।
  • API लेटेंसी 8 सेकंड से घटकर 12ms हो गई।
  • CPU यूसेज 100% से घटकर 8% से भी कम हो गया।

सीखे गए सबक:

  • लोकल टेस्ट गुमराह कर सकते हैं; 100 रोज़, लाखों रोज़ का पैमाना नहीं हो सकते।
  • फॉरेन कीज़ (foreign keys) को इंडेक्स करें—ज़्यादातर ORMs इसे छोड़ देते हैं।
  • EXPLAIN ANALYZE चलाएँ; डेटाबेस खुद समस्या की ओर इशारा करता है।
  • ज़्यादा हार्डवेयर खरीदने से पहले क्वेरीज़ को ऑप्टिमाइज़ करें।

सबसे पहले सर्वर को स्केल न करें। क्वेरीज़ को स्केल करें।

स्रोत: https://dev.to/mia_keller_ffd2584c046ecb/how-an-unindexed-column-silently-killed-our-database-under-load-and-the-5-minute-fix-m32