PostgreSQL में हर जगह खोज: पूर्ण-पाठ, ट्रिग्राम, आंशिक इंडेक्स
आंतरिक स्क्रीन के लिए तेज़ "हर जगह खोज" कैसे डिजाइन करें — PostgreSQL में पूर्ण-पाठ खोज, ट्रिग्राम इंडेक्स और आंशिक इंडेक्स कब और कैसे चुनें, ताकि परिणाम फ़ास्ट और प्रेडिक्टेबल हों।

आंतरिक टूल्स में “हर जगह खोज” का असली मतलब
एक internal स्क्रीन पर, “हर जगह खोज” आमतौर पर मतलब होता है: “मुझे वह बिल्कुल सही रिकॉर्ड जल्दी से दिखाओ जिस बारे में मैं सोच रहा हूँ, भले ही मुझे वह पूरी तरह याद न हो।” लोग ब्राउज़ नहीं कर रहे होते। वे सीधे किसी ग्राहक, टिकट, चालान, या डिवाइस पर जाना चाहते हैं।
इसीलिए धीमी खोज धीमे पेज से भी ज्यादा बुरा अनुभव देती है। एक पेज लोड एक बार होता है। खोज बार-बार होती है, अक्सर किसी कॉल या ट्रायज के दौरान। अगर परिणामों में 2–3 सेकंड लगते हैं, उपयोगकर्ता क्वेरी बदलते हैं, बैकस्पेस करते हैं, दूसरा शब्द आजमाते हैं, और परिणामस्वरूप अधिक लोड और निराशा बढ़ती है।
एक सिंगल सर्च बॉक्स से उपयोगकर्ता कई उम्मीदें रखते हैं: आंशिक मैच ("alex" से "Alexander" मिलता है), छोटे टाइपो के लिए सहनशीलता ("microsfot" से भी "Microsoft" मिले), समझदारी से “best result” ordering (exact IDs या emails ऊपर रहें), थोड़ी recency bias, और डिफ़ॉल्ट रूप से लागू फ़िल्टर (open tickets, active customers)।
कठिन हिस्सा यह है कि एक इनपुट कई इरादों को छुपा सकता है। एक एजेंट टिकट नंबर पेस्ट कर सकता है, नाम का अंश टाइप कर सकता है, ईमेल खोज सकता है, या फोन नंबर दर्ज कर सकता है। हर इरादा अलग रणनीति, अलग इंडेक्स, और कभी-कभी अलग ranking नियम चाहता है।
इसलिए इंडेक्स से शुरू मत करें। पहले उन कुछ search intents की सूची बनाएं जो आपके उपयोगकर्ता वास्तव में करते हैं, और identity fields (IDs, emails) को fuzzy fields (names, subjects) और लंबी टेक्स्ट (notes) से अलग रखें।
पहले डेटा और सर्च व्यवहार का नामकरण करें
इंडेक्स चुनने से पहले लिख लें कि लोग वास्तव में क्या टाइप करते हैं। “PostgreSQL search everywhere” एक फीचर की तरह लगता है, पर यह आमतौर पर बहुत अलग तरह की खोजों का मिश्रण होता है।
आंतरिक टूल “हार्ड” identifiers (order ID, ticket number, invoice code) को “सॉफ्ट” टेक्स्ट (customer name, email, notes, tags) के साथ मिलाते हैं। ये समूह PostgreSQL में अलग तरह से व्यवहार करते हैं, इसलिए इन्हें एक जैसा मानना तेज क्वेरीज पाने का तेज़ रास्ता नहीं है।
फिर व्यवहारों को अलग करें:
- Exact lookup: कोई
TCK-104883जैसे सटीक परिणाम की उम्मीद करता है। - Fuzzy lookup: कोई
john smthटाइप कर रहा है और नामों (और शायद ईमेल) पर एक सहनशील मैच चाहता है और छोटे परिणामों की सूची देखेगा। - Filter-driven search: कोई “Status = Open” और “Assigned to = Me” चुन रहा है; टेक्स्ट बॉक्स प्राथमिक नहीं है।
शुरू में तय करें कि क्या परिणामों को रैंक करना ज़रूरी है (best matches पहले) या बस फ़िल्टर करना पर्याप्त है। रैंकिंग notes और लंबी descriptions में मायने रखती है। IDs और emails के लिए रैंकिंग अक्सर बेतरतीब लगती है और लागत बढ़ाती है।
एक छोटा चेकलिस्ट काफी होता है:
- कौन से फ़ील्ड रोज़ Search होते हैं?
- कौन से इनपुट exact (IDs, codes), fuzzy (names), या long text (notes) हैं?
- कौन से फ़िल्टर लगभग हर खोज में लागू होते हैं?
- क्या आपको “best match” ordering चाहिए, या कोई भी मैच स्वीकार्य है?
- टेबल कितनी तेजी से बढ़ेगी: हजारों, लाखों या करोड़ों?
अगर आप ये निर्णय पहले कर लेते हैं, तो बाद में इंडेक्स चुनना अंदाज़ नहीं रहेगा।
बेसलाइन: exact matches और क्यों ILIKE अक्सर नुकसान पहुँचाता है
पहले आसान जीतें लॉक कर लें। कई internal स्क्रीन के लिए plain B-tree इंडेक्स से exact matches जैसे IDs, order numbers, emails, और external references पर तत्काल परिणाम मिल जाते हैं।
अगर लोग exact value पेस्ट करते हैं, तो सुनिश्चित करें कि आपकी क्वेरी वास्तव में exact है। WHERE id = ... या WHERE email = ... सामान्य इंडेक्स के साथ बेहद तेज़ हो सकती है। email पर unique index अक्सर दोहरे फायदे देता है: गति और बेहतर डेटा गुणवत्ता।
दिक्कत तब शुरू होती है जब “search everywhere” चुपके से ILIKE बन जाता है। name ILIKE '%ann%' जैसी क्वेरी में leading wildcard होती है, इसलिए PostgreSQL सामान्य B-tree इंडेक्स का उपयोग नहीं कर पाता। वह बहुत सारी पंक्तियाँ चेक करता है, और टेबल बढ़ने पर यह अपेक्षाकृत धीमा होता जाता है।
Prefix search काम कर सकती है, पर केवल तब जब पैटर्न शुरुआत में एंकर हो: name ILIKE 'ann%'. तब भी डिटेल्स मायने रखती हैं (collation, case handling, और क्या आपने वही expression इंडेक्स किया है जिसे आप क्वेरी कर रहे हैं)। अगर आपके UI को case-insensitive होना चाहिए, तो एक सामान्य तरीका है lower(name) पर क्वेरी करना और उसी एक्सप्रेशन पर इंडेक्स बनाना।
यह तय करना भी मदद करता है कि “snappy” का क्या अर्थ है:
- वॉर्म कैश पर database काम के लिए ~200 ms या कम
- नेटवर्क और रेंडरिंग सहित 1 सेकंड से कम end-to-end
- सामान्य खोजों के लिए कोई स्पष्ट loading state न दिखे
ऐसे लक्ष्य होने से यह तय करना आसान हो जाता है कि आप exact और prefix पर टिकें या full-text या trigram की ज़रूरत है।
कब full-text search सही टूल है
Full-text search तब सबसे उपयुक्त है जब लोग प्राकृतिक भाषा टाइप करते हैं और अपेक्षा करते हैं कि सिस्टम सही आइटम ढूंढे, ना कि केवल exact मैच। सोचिए टिकट संदेश, internal नोट्स, लंबी descriptions, knowledge base लेख, और कॉल लॉग्स।
बड़ी जीत है ranking। लंबी सूची लौटाने के बजाय जहाँ सर्वश्रेष्ठ परिणाम दबे होते हैं, full-text search relevance के आधार पर sort कर सकता है। internal टूल्स में यह मायने रखता है: किसी को सेकंडों में उत्तर चाहिए, न कि 50 rows स्कैन करने के बाद।
उच्च स्तर पर, full-text search में तीन भाग होते हैं:
- एक
tsvector(searchable text, stored या generated) - एक
tsquery(उपयोगकर्ता द्वारा टाइप किया हुआ, क्वेरी में बदलकर) - एक language configuration (शब्दों को normalize कैसे किया जाए)
Language configuration वह जगह है जहाँ व्यवहार दिखता है। PostgreSQL सामान्य stop words (जैसे “the” या “and”) हटाता है और stemming लागू करता है, तो “pay”, “paid”, और “payment” मेल खा सकते हैं। यह नोट्स और संदेशों के लिए अच्छा है, पर जब कोई छोटा सामान्य शब्द खोजता है तो कभी-कभी आश्चर्य होता है कि कुछ नहीं मिला।
Synonyms भी निर्णय का पॉइंट हैं। जब आपकी कंपनी एक ही चीज़ के लिए अलग शब्द उपयोग करती है (उदाहरण के लिए, “refund” बनाम “chargeback”), तब वे मदद करते हैं, पर उन्हें समय के साथ संभालना पड़ता है। synonym सूची छोटी रखें और उसे समर्थन/ऑप्स की वास्तविक टाइपिंग पर आधारित रखें।
एक व्यावहारिक उदाहरण: “can’t login after reset” खोजने पर उसे ऐसे टिकट्स दिखाने चाहिए जिनमें संदेश कहता है “cannot log in after password reset” भले ही शब्द अलग हों। यही “relevant ढूँढना” व्यवहार है जिसके लिए full-text search बनाया गया है, और अक्सर ILIKE को search engine जैसा बनाने के बजाय यह बेहतर विकल्प होता है।
कब ट्रिग्राम इंडेक्स बेहतर हैं
Trigram इंडेक्स तब मजबूत विकल्प हैं जब उपयोगकर्ता fragments टाइप करते हैं, टाइपो करते हैं, या केवल “कुछ जैसा” याद रखते हैं। ये छोटे टेक्स्ट फ़ील्ड्स पर चमकते हैं जहाँ full-text बहुत सख्त है: person names, company names, ticket subjects, SKUs, order numbers, और product codes।
एक trigram 3-अक्षर का chunk होता है। PostgreSQL दो स्ट्रिंग्स की तुलना इस बात से करता है कि वे कितने trigrams साझा करती हैं। इसीलिए यह "Jon Smth" को "John Smith" से और "ACM" को "ACME" से मैच कर सकता है, और यह तब भी परिणाम ढूंढ सकता है जब क्वेरी शब्द के बीच का हिस्सा हो।
यह अक्सर उस तरह के “मानव के अनुकूल” सर्च के लिए सबसे तेज़ रास्ता होता है जब काम है “मुझे सही row ढूँढो,” न कि “किसी विषय के बारे में दस्तावेज़ ढूँढो।”
जहाँ यह full-text से बेहतर है
Full-text लंबी टेक्स्ट और अर्थ के आधार पर रैंकिंग के लिए शानदार है, पर यह छोटے फ़ील्ड्स पर आंशिक स्ट्रिंग्स और छोटे टाइपो को स्वाभाविक रूप से संभालता नहीं। Trigram खोज उस तरह की fuzziness के लिए बनाई गई है।
write लागत को वाजिब रखें
Trigram इंडेक्स बड़े होते हैं और writes पर ओवरहेड जोड़ते हैं, इसलिए चुनिंदा बनें। उन कॉलम्स को इंडेक्स करें जिनका उपयोग लोग वास्तव में करते हैं:
- Name, email, company, username
- Short identifiers (SKU, code, reference)
- एक संक्षिप्त title फ़ील्ड (न कि बड़ा notes/comments फ़ील्ड)
यदि आप नामित कर सकते हैं कि टीम search बॉक्स में किन फ़ील्ड्स को टाइप करती है, तो आम तौर पर आप ट्रिग्राम इंडेक्सिंग को छोटा और तेज़ रख पाएँगे।
जो फ़िल्टर लोग अक्सर उपयोग करते हैं उनके लिए Partial indexes
एक “search everywhere” बॉक्स अक्सर छिपे हुए डिफ़ॉल्ट्स रखता है। लोग किसी workspace के अंदर खोजते हैं, active items पर, और deleted को बाहर रखते हैं। अगर ये फ़िल्टर लगभग हर रिक्वेस्ट में होते हैं, तो आम केस को तेज़ बनाने के लिए केवल उन rows को इंडेक्स करें जो उन शर्तों को पूरा करते हैं।
Partial index एक सामान्य इंडेक्स है जिसमें WHERE क्लॉज़ होता है। PostgreSQL इसे छोटा रखता है क्योंकि यह केवल उन rows के entries रखता है जिनकी आपको सबसे ज़्यादा ज़रूरत है। इसका मतलब अक्सर कम पेज पढ़ना और बेहतर cache hit rates है।
सामान्य partial-index लक्ष्य हैं active rows (status = 'active'), soft deletes (deleted_at IS NULL), tenant scoping, और “recent” विंडो (उदा. पिछले 90 दिन)।
कुंजी यह है कि यह आपके UI से मेल खाए। अगर स्क्रीन हमेशा deleted rows छुपाती है, तो आपकी क्वेरी में हमेशा deleted_at IS NULL होना चाहिए, और आपके partial index को वही condition उपयोग करनी चाहिए। छोटे mismatches—जैसे एक जगह is_deleted = false और दूसरी जगह deleted_at IS NULL—planner को इंडेक्स उपयोग करने से रोक सकती हैं।
Partial indexes full-text और trigram इंडेक्स के साथ भी काम करते हैं। उदाहरण के लिए, non-deleted rows के लिए text search को इंडेक्स करना इंडेक्स का आकार नियंत्रण में रखता है।
ट्रेड-ऑफ: partial indexes दुर्लभ क्वेरीज के लिए कम मददगार होते हैं। अगर कोई कभी-कभी deleted records पर खोज करता है या सभी workspaces में खोजता है, तो PostgreSQL धीमे प्लान पर जा सकता है। इसे admin-only path से हैंडल करें, या केवल तभी दूसरा इंडेक्स जोड़ें जब दुर्लभ क्वेरी सामान्य हो जाए।
विभिन्न तरीकों को मिलाते हुए बिना खोज को रहस्य बनाये रखना
अधिकांश टीमें तकनीकें मिलाकर प्रयोग करती हैं क्योंकि एक search बॉक्स को अलग-अलग इरादों को संभालना पड़ता है। लक्ष्य यह है कि ऑपरेशन का क्रम स्पष्ट रहे ताकि परिणाम predictable लगें।
एक सरल priority order मदद करता है, चाहे आप इसे अलग क्वेरी के रूप में लागू करें या एक क्वेरी में स्पष्ट CASE लॉजिक के साथ।
एक predictable priority ladder
कठोर से शुरू करें, फिर जरूरत पड़ने पर और fuzzier बनें:
- Exact match पहले (IDs, email, ticket number, SKU) B-tree इंडेक्स का उपयोग करके
- Prefix match जहाँ उपयुक्त हो
- Trigram match उसके बाद typos और fragments के लिए names और titles पर
- Full-text search अंत में लंबी notes, descriptions और free-form content के लिए
जब आप एक ही ladder फॉलो करते हैं, उपयोगकर्ता सीख जाते हैं कि बॉक्स का क्या मतलब है। वे यह सोचना बंद कर देते हैं कि सिस्टम टूटा हुआ है जब "12345" तुरंत टिकट ढूँढ ले रहा है जबकि "refund policy" लंबी टेक्स्ट खोज रहा है।
पहले फ़िल्टर, फिर फजी
फजी खोज महँगी हो जाती है जब उसे पूरी टेबल पर विचार करना पड़ता है। candidate सेट को उन फ़िल्टरों से संकुचित करें जो लोग वास्तव में उपयोग करते हैं (status, assigned team, date range, account), फिर शेष पर trigram या full-text चलाएं। एक तेज़ ट्रिग्राम इंडेक्स भी धीमा महसूस कर सकता है अगर आप इसे लाखों rows पर score करने के लिए कह रहे हैं।
यह भी उपयोगी है कि एक non-technical सहकर्मी समझ सके ऐसा एक पैराग्राफ नियम लिखें, जैसे: “हम पहले ticket number को exact मिलाते हैं, फिर typo-tolerant customer name, फिर notes को खोजते हैं।” यह साझा परिभाषा बाद में बहसों को रोकेगी कि किसी row का दिखना क्यों हुआ।
चरण-दर-चरण: एक दृष्टिकोण चुनें और सुरक्षित रूप से लागू करें
एक तेज़ “search everywhere” बॉक्स छोटे फैसलों का सेट है। उन्हें पहले लिखें, और डेटाबेस का काम सरल हो जाएगा।
- Define the inputs. क्या यह सिर्फ एक बॉक्स है, या एक बॉक्स + फ़िल्टर (status, owner, date range)?
- Choose match types per field. IDs और codes exact match चाहते हैं। Names और emails अक्सर prefix या fuzzy matching चाहते हैं। Long notes और descriptions के लिए natural language search बेहतर है।
- Add the right indexes and confirm they’re used. इंडेक्स बनाएं, फिर अपनी असली क्वेरी को
EXPLAIN (ANALYZE, BUFFERS)से चेक करें। - Add ranking or sorting that matches intent. अगर उपयोगकर्ता “invoice 1042” टाइप करते हैं तो exact matches ऊपर आना चाहिए। अगर वे गलत टाइप किया नाम टाइप करते हैं तो similarity ranking प्रमुख होना चाहिए।
- Test with real queries. टाइपो, बहुत छोटे शब्द (जैसे “al”), लंबा पेस्ट किया हुआ टेक्स्ट, खाली इनपुट, और “केवल फ़िल्टर” मोड आजमाएं।
सुरक्षित शिपिंग के लिए एक समय में एक बदलाव करें और rollback आसान रखें। बड़े टेबल पर नए इंडेक्स के लिए CREATE INDEX CONCURRENTLY चुनें ताकि आप writes को ब्लॉक न करें। अगर संभव हो तो feature flag के पीछे शिप करें और latency पहले और बाद में तुलना करें।
“PostgreSQL search everywhere” के लिए व्यावहारिक पैटर्न है: पहले exact match (तेज़ और सटीक), फिर ट्रिग्राम matching उन फ़ील्ड्स के लिए जहाँ लोग misspell करते हैं, और लंबी टेक्स्ट के लिए full-text search।
एक यथार्थवादी उदाहरण: support admin panel में एक search बॉक्स
कल्पना कीजिए एक support admin panel जहाँ टीम एक search बॉक्स चाहती है, पर उम्मीद है कि वह customers, tickets और यहाँ तक कि notes को भी ढूंढे। यह क्लासिक “एक इनपुट, कई मायने” समस्या है।
पहली जीत है intent को friction के बिना दिखाना। अगर क्वेरी एक ईमेल या फोन नंबर जैसी दिखती है तो उसे customer lookup के रूप में लें। अगर यह टिकट ID जैसी दिखती है (उदा. "TKT-10482") तो सीधे tickets पर रूट करें। बाकी सब ticket subject और notes पर text search के fallback में जाए।
customer lookup के लिए ट्रिग्राम इंडेक्स आम तौर पर सबसे अच्छा अनुभव देते हैं। नाम और company strings गंदे हो सकते हैं, और लोग fragments टाइप करते हैं। ट्रिग्राम इंडेक्स “jon smi” या “acm” जैसी खोजों को तेज़ और सहनशील बना सकता है।
ticket notes के लिए full-text search का उपयोग करें। notes वाक्य होते हैं, और आप आम तौर पर प्रासंगिक मैच चाहते हैं, न कि केवल substring। जब कई टिकट एक ही कीवर्ड का जिक्र करते हैं तो ranking मदद करती है।
फ़िल्टर उस से कहीं अधिक मायने रखते हैं जितना कई टीमें सोचती हैं। अगर agents “open tickets” में रहते हैं, तो एक partial index जोड़ें जो केवल open rows को कवर करे। active customers के लिए भी ऐसा ही करें। यह इंडेक्स छोटे रखता है और सामान्य path को तेज़ बनाता है।
बहुत छोटे क्वेरियों के लिए नियम बनाएं, अन्यथा डेटाबेस शोर के लिए महँगा काम करेगा:
- 1–2 characters: हाल के open tickets और हाल ही में अपडेट हुए customers दिखाएँ
- 3+ characters: ग्राहक फ़ील्ड्स पर trigram और टिकट टेक्स्ट पर full-text चलाएँ
- स्पष्ट intent न होने पर: मिश्रित सूची दिखाएँ, पर प्रत्येक समूह को cap करें (उदा. 10 customers और 10 tickets)
सामान्य गलतियाँ जो खोज को धीमा या भ्रमित कर देती हैं
अधिकांश “सर्च धीमी क्यों है?” बग खुद-सृजित होते हैं। लक्ष्य हर चीज़ पर इंडेक्स करना नहीं है, बल्कि उन चीज़ों पर इंडेक्स करना है जो लोग वास्तव में करते हैं।
एक सामान्य फँसने वाली गलती है कई कॉलम्स पर “बस किसी भी स्थिति में” इंडेक्स जोड़ना। पढ़ाई तेज़ हो सकती है, पर हर insert और update अब अतिरिक्त काम करता है। internal tools में जहाँ रिकॉर्ड दिन भर बदलते रहते हैं (tickets, orders, users), write speed मायने रखती है।
एक और गलती है full-text search का उपयोग करना जब आपको असल में नामों या ईमेल्स पर typo-tolerant lookup चाहिए होता है। Full-text दस्तावेज़ों और विवरणों के लिए उत्कृष्ट है। यह “Jon” बनाम “John” या “gmail.con” बनाम gmail.com जैसी चीज़ों के लिए जादू का इलाज नहीं है—यह आमतौर पर trigram का मामला है।
फ़िल्टर्स भी चुपके से आपके प्लान को तोड़ सकते हैं। अगर अधिकांश खोजें एक फिक्स्ड फ़िल्टर के साथ होती हैं (जैसे status = 'open' या org_id = 42), तो सबसे अच्छा इंडेक्स partial index हो सकता है जो उस condition से मेल खाता है। अगर आप इसे भूल जाते हैं तो PostgreSQL अपेक्षा से बहुत अधिक rows स्कैन कर सकता है।
कुछ गलतियाँ बार-बार दिखती हैं:
- बिना write लागत मापे बहुत सारे इंडेक्स जोड़ना
- full-text से error-tolerant autocomplete की उम्मीद रखना
- सामान्य फ़िल्टर कैसे बदलते हैं यह न समझना कि कौन सा इंडेक्स उपयोग होगा
- छोटे, साफ़ डेटा पर टेस्ट करना बजाए वास्तविक term frequency पर (common words बनाम rare IDs)
- बिना supporting index के किसी कॉलम पर sort करना, जिससे slow sort मजबूर होता है
उदाहरण: एक सपोर्ट स्क्रीन tickets को subject, customer name, और ticket number से खोजती है, फिर latest activity द्वारा sort करती है। अगर latest_activity_at filtered set (उदा. open tickets) के लिए इंडेक्स नहीं है, तो वह sort आपने search index से जो गति पाई थी उसे नष्ट कर सकता है।
शिप करने से पहले त्वरित चेक
“Search everywhere” फीचर को पूरा कहने से पहले व्यवहार के बारे में ठोस बनें जो आप वादा कर रहे हैं।
- क्या लोग एक exact identifier (ticket number, email) से रिकॉर्ड ढूँढना चाहते हैं?
- क्या वे टाइपो के लिए fuzzy matching की अपेक्षा रखते हैं?
- क्या वे लंबी नोट्स और विवरणों से ranked results चाहते हैं?
अगर आप मोड्स को मिला रहे हैं, तो तय करें कि टकराव होने पर कौन जीतता है।
फिर 2–3 फ़ील्ड पहचानें जो अधिकांश खोजों को प्रेरित करते हैं। अगर 80% खोजें email, name, और ticket ID से हैं, तो पहले उन्हें optimize करें और बाकी को द्वितीयक मानें।
एक छोटा pre-ship चेकलिस्ट:
- प्रति फ़ील्ड मुख्य match मोड की पुष्टि करें (exact lookup, fuzzy match, या ranked text)
- उपयोगकर्ता रोज़ जो फ़िल्टर लगाते हैं उन्हें सूचीबद्ध करें और सुनिश्चित करें कि इंडेक्स उन संयोजनों से मेल खाते हैं
- बहुत छोटे और खाली क्वेरियों (उदा. fuzzy search के लिए 2–3 characters की आवश्यकता) को कैसे हैंडल किया जाएगा तय करें; खाली के लिए हालिया दिखाएँ
- ordering को explainable बनाएं: most recent, best text match, या सरल combined rule
अंत में, वास्तविक डेटा साइज़ और समय के साथ प्रदर्शन टेस्ट करें, सिर्फ़ correctness के साथ नहीं। 1,000 rows पर तुरंत दिखने वाली क्वेरी 1,000,000 पर भारी पड़ सकती है।
अगले कदम: योजना को तेज internal search स्क्रीन में बदलें
एक search बॉक्स तब तेज़ रहता है जब टीम इस पर सहमत हो कि यह क्या करेगी। स्पष्ट भाषा में नियम लिखें: “मिलना” का मतलब क्या है (exact, prefix, typo-tolerant), कौन से फ़ील्ड खोजे जाएंगे, और फ़िल्टर परिणाम सेट को कैसे बदलते हैं।
एक छोटा test set रखें जिसमें वास्तविक खोजें हों और उसे regression suite की तरह देखें। दस से बीस क्वेरी आमतौर पर पर्याप्त हैं: कुछ सामान्य नाम, कुछ आंशिक ईमेल, एक टाइपो, एक लंबा नोट स्निपेट, और एक “कोई परिणाम नहीं” केस। परिवर्तनों से पहले और बाद में इन्हें चलाएँ ताकि प्रदर्शन काम चुपके से relevance को न तोड़ दे।
अगर आप internal tools AppMaster (appmaster.io) के साथ बना रहे हैं, तो उन search नियमों को डेटा मॉडल और बिजनेस लॉजिक के साथ परिभाषित करना सहायक होता है, ताकि UI व्यवहार और डेटाबेस विकल्प आवश्यकताओं के बदलने पर अलग न हो जाएँ।
सामान्य प्रश्न
इसे “उस विशिष्ट रिकॉर्ड को तेज़ी से ढूँढो” के रूप में देखें, ब्राउज़िंग की तरह नहीं। पहले उन कुछ वास्तविक उद्देश्यों को लिखें जो उपयोगकर्ता करते हैं (ID lookup, नाम/ईमेल lookup जिसमें टाइपो हो सकते हैं, लंबी नोट्स में खोज) और वे डिफ़ॉल्ट फ़िल्टर जो वे अक्सर लगाते हैं। ये निर्णय बतायेंगे कि कौन सी क्वेरियाँ चलानी हैं और किन इंडेक्सों के लिए खर्च करना सार्थक है।
ILIKE '%term%' में leading wildcard होता है, इसलिए PostgreSQL आमतौर पर सामान्य B-tree इंडेक्स का उपयोग नहीं कर पाता और बहुत सारे rows स्कैन कर देता है। यह छोटे टेबल पर ठीक लग सकता है, लेकिन जैसे-जैसे डेटा बढ़ेगा यह तेज़ी से धीमा हो जाता है। अगर आपको substring या typo-tolerant matching चाहिए तो ILIKE पर भरोसा करने के बजाय trigram या full-text के लिए प्लान बनाएं।
WHERE id = $1 या WHERE email = $1 जैसी exact comparisons का उपयोग करें और उन्हें B-tree (अक्सर emails या codes के लिए unique) से सपोर्ट कराएं। Exact lookups सबसे सस्ते searches हैं और परिणाम भी predictable बनाते हैं। अगर उपयोगकर्ता पूरा ticket number या email paste कर रहे हैं तो पहले इस रास्ते पर राउट करें।
पसंदीदा तरीका है prefix पैटर्न जैसे name ILIKE 'ann%' और यह सुनिश्चित करना कि आपने वही एक्सप्रेशन इंडेक्स किया है जिसे आप क्वेरी कर रहे हैं। विश्वसनीय case-insensitive व्यवहार के लिए कई टीमें lower(name) को क्वेरी करती हैं और उसी एक्सप्रेशन पर इंडेक्स बनाती हैं ताकि प्लानर इसका उपयोग कर सके। अगर पैटर्न शुरुआत पर anchor नहीं है तो prefix search पर्याप्त नहीं होगा।
जब उपयोगकर्ता fragments टाइप करते हैं, हल्की गलतियाँ करते हैं, या सिर्फ़ “कुछ जैसा” याद रखते हैं—खासकर छोटे फ़ील्ड्स (names, subjects, codes, usernames)—तो trigram इंडेक्सिंग उपयोगी होती है। यह शब्द के बीच के हिस्से से भी मैच कर सकती है और misspellings के लिए अच्छा काम करती है। किन्तु ट्रिग्राम इंडेक्स बड़े होते हैं और writes पर ओवरहेड जोड़ते हैं, इसलिए केवल उन्हीं कॉलम्स को इंडेक्स करें जिनका उपयोग लोग सचमुच करते हैं।
जब लोग वाक्य या कीवर्ड्स के साथ खोजते हैं—जैसे नोट्स, संदेश, विवरण या knowledge-base—तो full-text search बेहतर होता है। इसकी बड़ी जीत relevance ranking है, जिससे सबसे उपयुक्त परिणाम ऊपर आते हैं। ध्यान रखें कि भाषा कॉन्फ़िगरेशन stemming और stop-word removal करती है, जो prose के लिए अच्छा है पर बहुत छोटे सामान्य शब्दों पर उपयोगकर्ता चौंक सकते हैं।
जब अधिकांश खोजों में एक ही फ़िल्टर लगातार लगे हों—जैसे deleted_at IS NULL, status = 'open', या tenant/workspace constraint—तो partial indexes जोड़ें। क्योंकि यह इंडेक्स केवल सामान्य subset को कवर करता है, यह छोटा रहता है और वास्तविक वर्कलोड में तेज़ रहता है। ध्यान रखें कि आपकी क्वेरी में वही बिल्कुल समान condition होनी चाहिए जो partial index में है, वरना PostgreSQL उसे अनदेखा कर सकता है।
एक consistent priority ladder का उपयोग करें ताकि परिणाम predictable लगें: IDs/emails के लिए exact match पहले, फिर जहां लागू हो prefix, उसके बाद names/titles पर ट्रिग्राम, और लंबी सामग्री के लिए अंत में full-text। डिफ़ॉल्ट फ़िल्टर जल्दी लागू करें ताकि fuzzy search को कम rows पर काम करना पड़े। इससे डेटा बढ़ने पर प्रदर्शन और प्रासंगिकता रेंडम महसूस नहीं होगी।
सरल नियम रखें जैसे fuzzy search चलाने से पहले 3+ characters की आवश्यकता, और बहुत छोटे इनपुट पर हाल की या सामान्य रिकॉर्ड दिखाएं। 1–2 वर्ण वाले सर्च शोर पैदा करते हैं और अक्सर कम वैल्यू के लिए महंगा काम ट्रिगर करते हैं। empty input के लिए भी तय करें कि UI क्या दिखाएगा ताकि डेटाबेस पर “match everything” का भार न पड़े।
इंडेक्स बनाते ही EXPLAIN (ANALYZE, BUFFERS) के साथ असली क्वेरी को वास्तविक डेटा साइज़ पर जांचें, सिर्फ़ dev dataset पर नहीं। परिवर्तन एक-एक करके रोलआउट करें और rollback आसान रखें; बड़े टेबल पर नए इंडेक्स CREATE INDEX CONCURRENTLY से बनाएं ताकि writes ब्लॉक न हों। अगर आप screen AppMaster में बना रहे हैं, तो search नियमों को डेटा मॉडल और बिजनेस लॉजिक के साथ परिभाषित करें ताकि UI व्यवहार समय के साथ न बदले।


