मेरा Database "Correct" था। लेकिन 296x Too Slow भी।
मुझे पता चला कि मेरे Postgres database में 89 foreign keys थीं लेकिन उन पर zero indexes—millisecond queries को 843ms nightmares में बदल रही थीं और मेरी alpha launch लगभग तबाह कर रही थीं।
25 अक्टूबर, 2025। मेरा SaaS application आखिरकार feature-complete था। नौ major features, multi-tenant architecture, progressive learning — सब काम कर रहा था। लेकिन धीमा था। बहुत धीमा।
Dashboard queries रेंग रहीं थीं। Lists जो instantly load होनी चाहिए उन्हें कई seconds लग रहे थे। Alpha testers पूछने लगे "क्या site broken है?"
मैं सिर्फ performance की चिंता नहीं कर रहा था। मैं survival की चिंता कर रहा था।
दो दिन queries optimize करने, RLS policies rewrite करने, caching जोड़ने में बिताए। कुछ फर्क नहीं पड़ा। Frustration बढ़ रही थी — planned alpha launch से 5 दिन पहले और मुझे समझ नहीं आ रहा कि technically-correct database इतना खराब perform क्यों कर रहा।
फिर मैंने एक diagnostic query चलाई जिसने पेट गिरा दिया।
89 foreign keys। Zero indexes।
यह एक Postgres "feature" की कहानी है जो कोई नहीं बताता — 2 हफ्ते जो मेरी alpha launch लगभग तबाह कर गए — और 4 मिनट का fix जिसने सब बचा लिया।
Revelation: Foreign Keys Auto-Index नहीं होतीं
यह मैंने उस दिन सीखा, कठिन तरीके से:
Postgres automatically PRIMARY KEYs और UNIQUE constraints के लिए indexes बनाता है।
Postgres foreign keys के लिए automatically indexes नहीं बनाता।
फिर से कहता हूँ, क्योंकि इस एक गलतफहमी ने मेरी ज़िंदगी के 2 हफ्ते खर्च कराए:
FOREIGN KEYS ≠ INDEXES
जब आप यह लिखते हैं:
```sql
CREATE TABLE user_data (
id UUID PRIMARY KEY, -- ✅ Automatically indexed
org_id UUID REFERENCES organizations(id), -- ❌ NOT indexed!
resource_id UUID REFERENCES resources(id), -- ❌ NOT indexed!
created_at TIMESTAMPTZ
);
```
Postgres foreign key constraint (referential integrity) बनाता है, लेकिन `org_id` या `resource_id` पर index **नहीं** बनाता।
Diagnostic: कितना बुरा था?
मैंने query लिखी हर बिना index वाली foreign key ढूंढने के लिए:
```sql
SELECT
c.conrelid::regclass AS table_name,
a.attname AS column_name
FROM pg_constraint c
JOIN pg_attribute a ON a.attnum = ANY(c.conkey) AND a.attrelid = c.conrelid
WHERE c.contype = 'f'
AND NOT EXISTS (
SELECT 1 FROM pg_index i
WHERE i.indrelid = c.conrelid AND a.attnum = ANY(i.indkey)
);
```
Result ने freight train की तरह मारा:
89 rows।
32 tables में 89 foreign keys। Zero indexes।
हर join, हर filter, हर RLS policy check full table scans कर रहा था।
Fix: The Index Apocalypse
25 अक्टूबर, 2025। सुबह 7:51 बजे।
एक migration। 89 indexes। मैंने इसे "The Index Apocalypse" कहा।
```sql
-- Organization-scoped tables
CREATE INDEX idx_users_org_id ON users(org_id);
CREATE INDEX idx_resources_org_id ON resources(org_id);
-- Multi-column indexes for common query patterns
CREATE INDEX idx_user_data_org_resource ON user_data(org_id, resource_id);
-- Total: 89 indexes across 32 tables
```
Migration time: 4 मिनट।
Results: Disaster से Launch-Ready
पहले (बिना Indexes)
```sql
Seq Scan on user_data
Execution Time: 843.271 ms
Rows Removed by Filter: 12,834
```
बाद में (Indexes के साथ)
```sql
Index Scan using idx_user_data_org_resource
Execution Time: 2.847 ms
```
843ms → 2.8ms
यह एक single query के लिए 296x तेज़ है।
Real-world impact:
- Dashboard load: 2-3 seconds → 120ms
- Resource lists: 1+ second → 45ms
- Data queries: 850ms → 12ms
Average improvement: 20-40x तेज़। कुछ complex joins multiple foreign keys के साथ? 100x तेज़।
अंतर रात और दिन का था। Platform "क्या यह broken है?" से "वाह, यह तेज़ है" हो गया। :D
---
यह Multi-Tenant SaaS के लिए क्यों मायने रखता है
अगर आप Row-Level Security के साथ multi-tenant SaaS बना रहे हैं, यह बिल्कुल critical है।
RLS policies हर single query पर चलती हैं:
```sql
CREATE POLICY resources_org_isolation ON resources
USING (org_id = get_user_org_id());
```
`org_id` पर index के बिना, यह policy हर query पर sequential scan force करती है।
सीखे गए सबक
1. Postgres assumptions खतरनाक हैं
मैंने मान लिया foreign keys indexed हैं। नहीं हैं। हमेशा verify करें।
2. RLS को indexes चाहिए
Row-Level Security proper indexes के बिना useless है — actually useless से भी बदतर। हर query में overhead जोड़ती है।
3. Multi-tenant = हर जगह org_id index करो
Multi-tenant architecture में, `org_id` लगभग हर query में आता है। हर table पर index करो। कोई exception नहीं।
4. Performance एक business problem है
Users को परवाह नहीं कि आपका SQL technically correct है अगर page load होने में 3 seconds लगते हैं।
5. Indexes जल्दी जोड़ो
Empty tables में indexes जोड़ना instant है। Millions of rows वाली tables में जोड़ना घंटे लगते हैं और table lock होती है।
---
अंतिम विचार
89 missing indexes। 2 हफ्ते debugging। एक diagnostic query सबको ढूंढने के लिए। Fix करने में 4 मिनट।
Irony? Postgres आपको इसे diagnose करने के सब tools देता है। मुझे बस पता नहीं था कहाँ देखना है।
क्या आपके database का कभी "technically correct लेकिन painfully slow" moment आया? Fix क्या था — और ढूंढने में कितना समय लगा?
शुभकामनाओं सहित,
Chandler
STRAŦUM architecture series: यह performance crisis multi-tenancy puzzle का आखिरी piece था जो शुरू हुआ Day 2 पर multi-tenancy बनाने से, जारी रहा Day 67 पर complete schema rebuild से, और शामिल था lost navigation context से 31 blank screens fix करना।
---
अभी भी सीख रहा हूँ कि "यह काम करता है" और "यह तेज़ काम करता है" अलग-अलग लक्ष्य हैं — और सिर्फ एक ship होता है।
---





