PostgreSQL से एडवांस्ड सर्च इंजन बनाना
(xata.io)- PostgreSQL का full-text search अलग search engine के बिना
tsvector,tsquery,@@,ts_rank, GIN index को मिलाकर search feature बनाने का तरीका है - Search terms और documents को lexeme यूनिट में normalize किया जाता है, और AND·OR·NOT·FOLLOWED BY जैसे operators से query को व्यक्त कर सामान्य search syntax का बड़ा हिस्सा implement किया जा सकता है
- GIN index ने example environment में search time को 200ms से अधिक से घटाकर लगभग 4ms कर दिया, लेकिन results बढ़ने पर
ts_rankकी ranking और sorting cost bottleneck बन सकती है - Relevance tuning को title weight, vote count, rating, genre, recency जैसे signals को sort expression में जोड़कर या
setweightसे column weights देकर implement किया जाता है - Typo tolerance, faceted search, autocomplete, exact phrase search, hybrid search भी संभव हैं, लेकिन PostgreSQL में components को खुद जोड़ना पड़ता है और बड़े datasets में performance limits जांचनी पड़ती हैं
PostgreSQL full-text search का approach
- PostgreSQL full-text search के लिए low-level components देता है, जिन्हें मिलाकर search engine की functionality बनाई जा सकती है
- यह तरीका flexible है, लेकिन जिनका मुख्य उपयोग full-text search है, ऐसे Elasticsearch, Typesense, Meilisearch की तुलना में इसमें ज्यादा implementation work चाहिए
- Example queries Kaggle के Wikipedia Movie Plots dataset का उपयोग करती हैं
- इसमें 34,000 movie titles शामिल हैं
- CSV format का size लगभग 81MB है
मुख्य components
- PostgreSQL full-text search मुख्य रूप से इन elements पर काम करता है
tsvector: search target text को normalized lexeme list के रूप में store करता हैtsquery: normalized search query को represent करता है@@: यह check करने वाला match operator किtsquery,tsvectorसे match होता है या नहींts_rank,ts_rank_cd: search results का relevance score calculate करते हैं- GIN index:
tsvectorको efficiently query करने के लिए inverted index
tsvector और search configuration
tsvectorsorted lexeme list store करता है- lexeme token जैसा होता है, लेकिन यह normalized string है ताकि एक ही word के अलग-अलग forms एक में align हो जाएं
- English configuration में uppercase को lowercase में fold किया जाता है और suffix हटाकर normalize किया जाता है
to_tsvectorसे English sentence parse करने पर “I”, “to”, “an” जैसे stop words हट जाते हैं- “refuse” और “Refusing” दोनों
refusमें convert होते हैं - punctuation ignore होता है
- original text में word positions और weights भी record होते हैं
- “refuse” और “Refusing” दोनों
englishsearch configuration की जगहsimpleconfiguration इस्तेमाल करने पर words text में जिस form में मिले हैं, उसी तरह include होते हैं- “refuse” और “refusing” अलग lexeme के रूप में रह जाते हैं
simpleconfiguration label या tag वाली columns के लिए खास तौर पर useful है
- PostgreSQL कई languages के built-in search configurations देता है, लेकिन CJK (Chinese·Japanese·Korean) configuration नहीं है
- unsupported languages के लिए
simpleconfiguration व्यावहारिक रूप से अच्छा काम कर सकता है - हालांकि CJK के लिए यह पर्याप्त है या नहीं, यह स्पष्ट नहीं है
- unsupported languages के लिए
tsquery और query expression
tsquerynormalized search query को represent करने वाला data type है- Search terms पहले से normalized lexeme होने चाहिए
- कई search terms को AND, OR, NOT, FOLLOWED BY operators से combine किया जा सकता है
to_tsquery,plainto_tsquery,websearch_to_tsqueryuser द्वारा input text को उपयुक्तtsqueryमें बदलने में मदद करते हैं- उनका core role input text में शामिल words को normalize करना है
websearch_to_tsqueryइस्तेमाल करने पर general search box जैसी query बनाई जा सकती हैdarth vaderको logical AND की तरह process किया जाता है, जिसमें दोनों words document में होने चाहिए- OR search और word exclusion भी संभव है
- Phrase search words के क्रम से लगातार आने वाले form को express करता है
- English configuration में “the” जैसे stop words हट जाते हैं, इसलिए कुछ phrase searches में लगभग पूरा phrase गायब हो सकता है
- ऐसे cases में
simpleconfiguration इस्तेमाल करने पर expected results मिल सकते हैं
- ऐसे cases में
@@operator का उपयोग यह check करने के लिए किया जाता है किtsquery,tsvectorसे match करता है या नहीं
GIN index और search performance
- GIN का मतलब Generalized Inverted Index है, और यह complex values के अंदर शामिल element values खोजने वाली queries के लिए design किया गया index type है
- GIN text search के अलावा JSON queries के लिए भी इस्तेमाल हो सकता है
- Searchable कई columns को मिलाकर एक
tsvectorcolumn बनाया जा सकता है, और उस column पर GIN index create किया जा सकता है - Example environment में GIN index ने search time को 200ms से अधिक से घटाकर लगभग 4ms कर दिया
Ranking और relevance calculation
- अच्छी search experience के लिए results को relevance के आधार पर sort करना चाहिए
- PostgreSQL दो predefined ranking functions देता है:
ts_rankऔरts_rank_cd- दोनों functions search term frequency को consider करते हैं
ts_rank_cdmatching lexemes के बीच proximity को भी reflect करता है
- Relevance application के nature पर बहुत depend करती है
- Default ranking function starting point जैसा है, और जरूरत के अनुसार custom ranking function बनाया जा सकता है या उसे दूसरे factors के साथ combine किया जा सकता है
ts_rankको हर result कीsearchcolumn access करनी पड़ती है- अगर
WHEREcondition में बहुत सारी rows match होती हैं, तो PostgreSQL को ranking calculation और sorting के लिए उन सभी rows को visit करना पड़ता है - Example environment में एक query 5~7ms में return हुई, लेकिन
darth OR vaderजैसी query जिसमें 1,000 से ज्यादा results rank करने थे, उसमें लगभग 80ms लगे
- अगर
Relevance tuning
- Word frequency-based relevance अच्छा default है, लेकिन data में frequency से ज्यादा important signals हो सकते हैं
- Movie dataset में relevance में ये signals reflect किए जा सकते हैं
- Title में match हुए results को description या plot में match हुए results से ज्यादा important मानना
- Rating या vote count के आधार पर ज्यादा popular movies को promote करना
- अगर user comedy पसंद करता है, तो comedy movies को ऊपर रखना
- पुराने titles की तुलना में latest titles को ज्यादा relevant मानना
- Dedicated search engines अलग-अलग columns या fields को ranking पर असर डालने के लिए configure करने की capability देते हैं
- Related documentation examples के रूप में Elastic, Typesense, Meilisearch देखे जा सकते हैं
Numbers, dates, exact values पर आधारित boosting
- PostgreSQL दूसरे columns पर आधारित boosting सीधे provide नहीं करता, लेकिन ranking आखिरकार एक sort expression है, इसलिए custom signals जोड़े जा सकते हैं
- Vote count reflect करने के लिए ranking score में vote count-based boost जोड़ने के तरीके से implement किया जा सकता है
- Example में impact को gradual बनाने के लिए log का उपयोग किया गया
0.01coefficient से booster को ranking score जैसी scale पर align किया गया
- Vote count एक निश्चित level पर होने पर ही rating को boost करने वाला अधिक complex function भी बनाया जा सकता है
- अगर किसी खास genre को ऊपर लाना हो, तो
valueBoosterजैसा function इस्तेमाल किया जा सकता है जो value के किसी specific column value से match होने पर ही coefficient return करे
Column weights
tsvectorके lexemes में weight लगाया जा सकता है- PostgreSQL A, B, C, D चार weights support करता है
- A सबसे ज्यादा weight है
- D सबसे कम और default value है
setweightfunction का उपयोग करकेtsvectorcolumn बनाते समय weights control किए जा सकते हैं- Title column को higher weight देने पर, search term title में शामिल वाली movies results के top पर आती हैं और ranking score भी बढ़ता है
- Weight classes सिर्फ चार हैं, यह limitation है, और weight
tsvectorcalculate करते समय apply करना पड़ता है
Typo tolerance और fuzzy search
- PostgreSQL
tsvectorऔरtsqueryइस्तेमाल करते समय fuzzy search या typo tolerance सीधे support नहीं करता - Query side में typo होने की assumption के तहत इसे इन तरीकों से implement किया जा सकता है
- Content से सभी lexemes को अलग table में index करना
- Query के हर word के लिए similarity या Levenshtein distance से candidate words search करना
- मिले हुए words शामिल करने के लिए query modify करना
- Modified query से search execute करना
- Example में search engines द्वारा fuzzy search में इस्तेमाल किए जाने वाले तरीके के कारण Levenshtein distance का उपयोग किया गया
- Candidate word list मिलने के बाद query को इस तरह adjust करना होता है कि वे सभी words शामिल हों
Faceted search
- Faceted search खासकर e-commerce sites में users को search scope को बार-बार narrow करने के लिए व्यापक रूप से इस्तेमाल होता है
- PostgreSQL में categories को manually define करके और उन्हें search की
WHEREcondition में जोड़कर इसे implement किया जा सकता है - Existing data के आधार पर algorithmically categories बनाने का तरीका भी संभव है
- Example में movie year के आधार पर “Decade” facet बनाया गया
- हर decade के match count को भी calculate करके parentheses में दिखाया जा सकता है
- कई facets को एक query में लाने के लिए CTEs combine किए जा सकते हैं
- यह तरीका small-to-medium datasets में अच्छा काम कर सकता है, लेकिन बहुत बड़े datasets में धीमा हो सकता है
PostgreSQL search engine का scope और limitations
- PostgreSQL के full-text search components को मिलाकर काफी advanced search engine बनाया जा सकता है
- PostgreSQL-based search joins और ACID transactions भी साथ में support करता है
- ये ऐसी capabilities हैं जो आम तौर पर दूसरे search engines में नहीं होतीं
- आगे कवर किए जा सकने वाले advanced search topics ये हैं
- Suggestions और autocomplete
- Exact phrase matching
pg-vectorके साथ combined hybrid search
- ये features PostgreSQL से संभव हैं, लेकिन components को खुद combine करना पड़ता है
- कुछ cases में बहुत बड़े datasets पर performance degrade हो सकती है
- अगला लेख part 2 PostgreSQL में search implement करने और infrastructure में Elasticsearch जोड़कर data sync करने की तुलना करता है
1 टिप्पणियां
Hacker News की रायें
इसे Elasticsearch की सुविधाओं की नकल करने वाले तरीके से करने की कोशिश नहीं करनी चाहिए
2000 के दशक में मैंने MySQL 3.x से image EXIF data parse करके 3-स्तरीय classification system और counts को index करने वाला search engine बनाया था। यह ऐसा काम था जिसे कई महंगे vendors भी ठीक से नहीं कर पाए थे, और Autonomy तो top-level classification भी नहीं कर पाया था
6 हफ्तों तक ऐसी नाजुक SQL लिखते हुए, जिसमें सिर्फ SELECT columns का order बदलने से भी performance बदल जाती थी, किसी तरह सफलता मिली, लेकिन मैं इसे दोबारा नहीं करना चाहूंगा। Databases मूल रूप से बड़े पैमाने पर समान हो सकते हैं, लेकिन search engines कहीं ज्यादा आगे बढ़ चुके हैं
बौद्धिक जिज्ञासा के लिए इसे आजमाया जा सकता है, लेकिन search सिर्फ tokenize करके खत्म हो जाने वाली चीज नहीं है। जल्द ही faceted navigation, multilingual support, automatic synonyms, "Did you mean" spelling suggestions, large-scale performance जैसी जरूरतें आने लगेंगी और आप खुद को dead end में फंसा लेंगे। मानसिक स्वास्थ्य के लिए search engine इस्तेमाल करना बेहतर है, और PG और ES sync के लिए ZomboDB या PGSync जैसे tools भी हैं
दूसरी तरफ, अगर data analysts या developers किसी बड़े database के text columns को
LIKE/ILIKEसे अधिक flexible तरीके से search करना चाहते हैं, तो उसी database के अंदर full-text search index/table बनाकर 90% तक पहुंचना ज्यादा आसान और तेज हो सकता हैभाग 2 में Postgres vs Elasticsearch तुलना का इंतजार है। हमारी कंपनी की एक application object CRUD के लिए PG और search के लिए Elastic इस्तेमाल करती है, लेकिन दो data stores को sync करने की मेहनत को हमने पूरी तरह कम आंका था, और अब सच में Elasticsearch हटाने पर विचार कर रहे हैं
हर 5 मिनट में cron DB में ऐसे index-target objects ढूंढता था जिनका
last_modified_at > last_indexing_started_timestampहो, उन्हें Elasticsearch में index करता था, और फिरlast_indexing_started_timestampको sync शुरू होने के समय से update कर देता था। इस तरह run के शुरू और खत्म होने के बीच modify हुए objects अगले run में पकड़े जाते थेअगर Elasticsearch rebuild करना हो, तो last indexing time खाली करके शुरुआत से फिर sync कर दें; इससे self-healing संभव हो जाता है और sync drift नहीं होता
async कामों में हमेशा की तरह monitoring और retries चाहिए होते हैं, लेकिन ES stable और तेज था, इसलिए दिक्कतें कम थीं। हालांकि consistency requirements ढीली थीं—PG की latest state reasonable time में ES तक पहुंच जाए तो काफी था—इसलिए requirements अलग हों तो बात अलग हो सकती है
Postgres full-text search में indexes और query weights जोड़ने की तरफ shift करने के बाद, update triggers और बेहद तेज search queries के जरिए हमें जो चाहिए था वह सब Postgres के अंदर ही संभल गया
कुल मिलाकर यह सफल रहा और operational burden बहुत घट गया, इसलिए लगाए गए engineering time की भरपाई अच्छी तरह हो गई, लेकिन इसे हल्के में शुरू करने वाला काम नहीं है
जरूरत के हिसाब से materialized views, normal views, triggers बेहतर हो सकते हैं। Built-in text search शायद आपके use case में fit न बैठे, और alternative बनाना हमेशा बहुत मुश्किल हो ऐसा भी नहीं है
realistic load पर p50/p99 lookup time के बिना इसे साबित मानना मुश्किल है। जो search engine results लौटाने में 1 मिनट लेता है वह "advanced" नहीं है, और Postgres जैसे relational database में भी paper पर तो यह निश्चित रूप से संभव है
मैं broadly सहमत हूं, और large scale पर—मसलन कुछ million records से ऊपर—शायद यह approach नहीं अपनाऊंगा। मुख्य रुचि यह थी कि features को कितना replicate किया जा सकता है
छोटे search use cases में maintain करने के लिए कम infrastructure होता है, और strong consistency, joins जैसे फायदे मिलते हैं। Xata में हम छोटे scale पर Postgres इस्तेमाल करने और फिर minimal breaking changes के साथ Elasticsearch पर जाने वाले smooth transition पर भी विचार कर रहे हैं
उस नजरिए से fast search results खुद में इतने चौंकाने वाले नहीं हैं। Background jobs से cached results लगातार update रखे जा सकते हैं और request आने पर उन्हें सीधे serve किया जा सकता है। ऐसी caching और response time actual search result calculation speed से अलग चीज लगती है
कुछ लोग slow connections इस्तेमाल करते हैं, और earthquake जैसे कुछ search terms किसी खास region में ही अचानक spike कर सकते हैं
अगर search terms बहुत random हों, तो cache में results नहीं होंगे और performance actual से खराब दिखेगी; और अगर वे पर्याप्त random न हों, तो actual से बेहतर दिखेगी
इसलिए ES आदि के फायदे कई nodes में horizontal scaling या main index के ऊपर रखी जाने वाली extra features में हैं
सिर्फ
ts_rankइस्तेमाल करें तो perfect है, लेकिन आम तौर पर आप ranking को दूसरे relevance metrics से adjust करना चाहेंगे। अगर आप यह limitation हल कर लें कि उस metric को primary ranking criterion नहीं बनाया जा सकता, तो results indexed normal DB table query जितने fast होते हैंकिशोर उम्र में, जब मुझे search engines और databases दोनों की अच्छी समझ नहीं थी, तब मैंने इन्हें शुरू से बनाकर देखा था। मैं देखना चाहता था कि मैं कितनी दूर जा सकता हूं, और कितनी तेजी से कितने relevant search results लौटा सकता हूं
एक basic database और search engine जल्दी बनाना amateur programmers के लिए भी काफी आसान है। अगर आप basic computer science algorithms और operating system/hardware का उपयोग समझते हैं, तो एक-दो महीने में बना सकते हैं
high-level language में भी speed खराब नहीं थी, और 2003 के laptop पर करीब 250K QPS मिल रहा था। sharding करने पर scalability भी कोई बड़ी समस्या नहीं है। storage और lookup से ज्यादा indexing, locking, consistency जटिल हैं
असली बड़ी समस्या search की subjectivity है। जब सवाल यह हो कि लोग सच में क्या खोजना चाहते हैं, जब मुझे खुद नहीं पता कि मैं क्या खोज रहा हूं तो कैसे खोजूं, system का दुरुपयोग करने वालों को कैसे रोकूं, और complex queries व datasets को कैसे संभालूं—तब कठिनाई कई orders of magnitude बढ़ जाती है
जानना चाहूंगा कि क्या आपने सच में general-purpose database इस्तेमाल किया था और क्या वह आंकड़ा सही है। इन engines से आगे निकलने के लिए आपने कौन-सी techniques इस्तेमाल कीं?
उससे बड़ा होते ही creative solutions चाहिए होते हैं, और वहीं से कई trade-offs सामने आते हैं
Postgres के अंदर search करने में कई समस्याओं में से एक यह है कि search CPU-heavy काम है, और धीरे-धीरे GPU इस्तेमाल करने की दिशा में भी जा रहा है। आदर्श रूप से database का CPU core data model के transactional updates के लिए बचाकर रखना चाहेंगे
मैंने कई बार देखा है कि ES और Solr clusters reindexing के दौरान 10+ nodes पर 100% पर चल रहे होते हैं, या सामान्य समय में भी 10+ nodes पर 30–50% CPU इस्तेमाल करते हैं। इसके मुकाबले database, उदाहरण के लिए AWS L/XL instance पर 50–100GB data और करीब 30% CPU usage वाला हो सकता है
अगर search CPU को पूरा main DB में ले आएं, तो अब DB को shard करना पड़ेगा। फिर भी side projects में search, recursive joins, vectors आदि के लिए PG extensions मजेदार और सरल लगते हैं
मैंने पहले इसे असल में करके देखा था और काफी तेज बनाया था
https://austingwalters.com/fast-full-text-search-in-postgres...
मौजूदा website https://askhn.ai है
[1]: https://en.wikipedia.org/wiki/Kerning
ऐसी techniques को pgvector के साथ जोड़ दें, तो embeddings से related content भी खोज सकते हैं। यह काफी जादू जैसा लगा
वैसे यह "Dark" Vader नहीं, Darth Vader है। बचपन में मैं भी "Dark" समझता था
बहुत अच्छा और स्पष्ट लेख है। SQLite भी standard plugin के रूप में advanced indexing features और stemming देता है
English के लिए SQLite भी अच्छा काम करता है
https://github.com/daitangio/knowledge
एक बार आजमाकर देखना चाहिए। काफी powerful है
लेख अच्छा है, लेकिन PostgreSQL fuzzy search support नहीं करता, यह हिस्सा आंशिक रूप से गलत है।
pg_trgmextension और GIN trigram indexes इस लेख के examples जैसे fuzzy search use cases support करते हैंhttps://www.postgresonline.com/article_pfriendly/169.html
query काफी तेज हो सकती है, लेकिन इसकी कीमत memory usage और updates के दौरान बढ़े हुए work time के रूप में चुकानी पड़ती है