This is a viewer only at the moment see the article on how this works.
To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk
This is a preview from the server running through my markdig pipeline
Wednesday, 14 January 2026
खोज उन विशेषताओं में से एक है जो सभी कम मानते हैं। सैद्धांतिक रूप-से सही लेकिन पाठ्य रूप से गलत
यह' also somethign है कि ' ने इस साइट पर बिगाड़ पैदा किया है क्योंकि यह' के शुरूआत से ओपन सर्चQuery, PostgreSQL पूर्ण पाठ खोज के साथ काम किया है, इसके साथ '\s फैशनी भेक्टर सामान लेकिन कभी भी सचमुच उससे खुश नहीं था भ्रमित मुझे विशेष रूप से अब मैं एक खोज उपकरण बना रहा हूँ सुदृढ़आरएजी अच्छी तरह से मैंने सोचा कि मैं अंततः इसे ठीक करना चाहिए क्या यह है या नहीं एक अन्य बात है
यह लेख नहीं है-' शून्य से एक खोज इंजिन का निर्माण करने के बारे में नहीं है।
पूर्ववर्ती कार्यों पर निर्माण: यह अनुच्छेद अर्थात्मक खोज क्रियान्वयन जिसने पारस्परिक रैंक फ्यूजन के साथ हाइब्रिड खोज जोड़ी।
सेमेटिक खोज अवसंरचना दो उद्देश्यों का काम करती है और के लिए पुनर्प्राप्ति परत प्रदान करता है वकील जीपीटी RAG प्रणाली -एक लेखन सहायक जो साइट के उपयोग से नए पदों का प्रारूप करता है। अधिवक्ता GPT का निर्माण एम्बेडिंग और भेक्टर खोज पर
जब एक खोज ने कोई परिणाम नहीं दिखाया, तो सिस्टम सहायक सुझावों के बजाय अनियमित पुराने लेख दिखा रहा था।
द फिक्स: परिवर्धित BlogSearchService.HybridSearchWithPagingAsync() पता लगाने के लिए जब खोज शून्य परिणाम लौटाता है और वापस पिछले पोस्टों को दिखाने के लिए नीचे आने वाले तिथि से क्रमबद्ध करने में गिर जाता है NoMatchFound फ्लैग को BasePagingModel यूआई एक उपयुक्त संदेश प्रदर्शित कर सकता है जैसे " कोई मिलान नहीं मिला
// No match found - return recent posts as suggestions
if (noMatchFound)
{
Log.Logger.Information("No search results for '{Query}', returning recent posts as suggestions", query);
return await GetRecentPostsAsSuggestions(targetLanguage, startDate, endDate, page, pageSize);
}
PostgreSQL के पूर्वनिर्धारित अंग्रेजी पाठ खोज कॉन्फ़िगरेशन में तकनीकी रूप से एक्रोनिमों को अनुक्रमित करता है, लेकिन सामान्यीकरण और समापन से छोटा होता है।
द फिक्सà¤à¥à¤°à¥à¤ ILIKE. यह इसे बदलने के बजाय पूर्ण पाठ खोज को संपूरित करता है।
// Detect if query looks like an acronym or short term
var isAcronymLike = query.Length <= 6 && query.Any(char.IsUpper);
// Add substring search for acronyms
searchQuery = searchQuery.Where(x =>
EF.Functions.ILike(x.Title, $"%{acronym}%")
|| EF.Functions.ILike(x.PlainTextContent, $"%{acronym}%"));
के लिए खोजें असफल होंगी क्योंकि PostgreSQL की पाठ खोज पदवर्णक अवधि और हैश प्रतीकों को डिलिमिटर के रूप में व्यवहार करता है।
द फिक्स: एक बनाया गया SearchQueryParser जो सामान्य तकनीकी शब्दों को पहचानता है और उन्हें खोज योग्य संस्करणों से बदलता है
private static readonly Dictionary<string, string> TechnicalTerms = new(StringComparer.OrdinalIgnoreCase)
{
["asp.net"] = "aspnet",
["c#"] = "csharp",
[".net"] = "dotnet",
["f#"] = "fsharp",
["node.js"] = "nodejs",
// ... more terms
};
पोस्टग्रेएसक्यूएल ने " और" को एक रोक शब्द के रूप में व्यवहार किया है तथा इसे खोजों से हटाता है।
द फिक्स: एक गूगल - शैली क्वेरी पारसर को लागू किया है जो stop शब्दों को कुशलता से नियंत्रित करता है और उन्नत खोज ऑपरेटरों का समर्थन करता है
मैंने एक कार्यान्वित किया SearchQueryParser वर्ग जो : के लिए समर्थन सहित प्रश्नों को पद वर्णन करता है
"exact match" - सही वाक्यांश के लिए खोजता है-unwanted - इस शब्द को समाहित परिणामों को छोड़ता हैASP* मिलाएँपदवर्णक क्वेरी को टोकनाइज करने के लिए एक संकलित रेगेक्स पैटर्न का उपयोग करता है
[GeneratedRegex(@"""([^""]+)""|(-)?(\S+)", RegexOptions.Compiled)]
private static partial Regex QueryTokenRegex();
public ParsedQuery Parse(string query)
{
var matches = QueryTokenRegex().Matches(processedQuery);
foreach (Match match in matches)
{
// Quoted phrase
if (match.Groups[1].Success)
{
var phrase = match.Groups[1].Value.Trim();
result.Phrases.Add(phrase);
continue;
}
// Excluded term (starts with -)
if (match.Groups[2].Success)
{
var term = match.Groups[3].Value.Trim();
result.ExcludeTerms.Add(term.ToLowerInvariant());
continue;
}
// Regular term or wildcard
var token = match.Groups[3].Value.Trim();
if (token.Contains('*'))
{
result.WildcardTerms.Add(token.Replace("*", ""));
}
else if (!StopWords.Contains(token))
{
result.IncludeTerms.Add(token.ToLowerInvariant());
}
}
}
पदवर्णक संरचनात्मक डेटा आउटपुट करता है जो तब PostgreSQL में परिवर्तित किया जाता है to_tsquery क्योंकि हम निर्मित वाक्य संरचना उत्पन्न कर रहे हैं websearch_to_tsquery क्योंकि हम पहले से ही ऑपरेटरों का विश्लेषण कर चुके हैं
public string BuildTsQuery(ParsedQuery parsed)
{
var queryParts = new List<string>();
// Add include terms with AND
foreach (var term in parsed.IncludeTerms)
{
queryParts.Add(term);
}
// Add wildcard terms with :* suffix
foreach (var term in parsed.WildcardTerms)
{
queryParts.Add($"{term}:*");
}
return queryParts.Count > 0 ? string.Join(" & ", queryParts) : string.Empty;
}
क्यों न सिर्फ उपयोग
websearch_to_tsquery? चूंकि यह अभी भी तकनीकी संदर्भों पर असफल है।
BuildSearchQuery विधि को पूर्ण रूप से फिर से लिखा गया है पद वर्णन क्वेरी संरचना का उपयोग करने के लिए baseQuery भाषा के अनुसार पहले से ही फ़िल्टर करता है, तिथि शृंखला
private IOrderedQueryable<BlogPostEntity> BuildSearchQuery(
string query,
string language,
DateTime? startDate,
DateTime? endDate,
string order)
{
var parsed = _queryParser.Parse(query);
IQueryable<BlogPostEntity> searchQuery = baseQuery;
// Handle phrases (exact substring matching)
foreach (var phrase in parsed.Phrases)
{
searchQuery = searchQuery.Where(x =>
EF.Functions.ILike(x.Title, $"%{phrase}%")
|| EF.Functions.ILike(x.PlainTextContent, $"%{phrase}%")
|| x.Categories.Any(c => EF.Functions.ILike(c.Name, $"%{phrase}%")));
}
// Build tsquery for include terms and wildcards
var tsQuery = _queryParser.BuildTsQuery(parsed);
// Apply full-text search if we have terms
if (!string.IsNullOrWhiteSpace(tsQuery))
{
searchQuery = searchQuery.Where(x =>
x.SearchVector.Matches(EF.Functions.ToTsQuery("english", tsQuery))
|| x.Categories.Any(c =>
EF.Functions.ToTsVector("english", c.Name)
.Matches(EF.Functions.ToTsQuery("english", tsQuery))));
}
// Handle acronyms with case-insensitive substring search
// This supplements full-text search (additive OR), not replaces it
var acronymTerms = parsed.IncludeTerms
.Concat(parsed.WildcardTerms)
.Where(t => _queryParser.IsAcronymLike(t))
.ToList();
foreach (var acronym in acronymTerms)
{
searchQuery = searchQuery.Where(x =>
EF.Functions.ILike(x.Title, $"%{acronym}%")
|| EF.Functions.ILike(x.PlainTextContent, $"%{acronym}%"));
}
// Handle excluded terms (must NOT contain these)
foreach (var excludeTerm in parsed.ExcludeTerms)
{
searchQuery = searchQuery.Where(x =>
!EF.Functions.ILike(x.Title, $"%{excludeTerm}%")
&& !EF.Functions.ILike(x.PlainTextContent, $"%{excludeTerm}%")
&& !x.Categories.Any(c => EF.Functions.ILike(c.Name, $"%{excludeTerm}%")));
}
return orderedQuery;
}
यहाँ उन्नत खोज कार्यात्मकता के कुछ उदाहरण हैं
DiSE
अब शीर्षक या अंतर्वस्तु में लेखों को "DiSE" के साथ मिलाता है
ASP.NET
ASP के बारे में लेख ढूंढता है
"semantic search"
✅ वस्तुओं में सही वाक्यांश खोजता है
ASP.NET -Core
à¤à¥à¤°à¥à¤
ASP*
मिलान
"full text search" PostgreSQL -MySQL
✅ सही वाक्यांश के साथ आलेखों को खोजता है.
क्वेरी: ASP.NET and Alpine
पहले टूटा
इसके बाद स्थिर
खोज प्रतिस्परिक रैंक फ्यूजन (RRF) के साथ एकीकृत करता है जैसा कि इस में लागू किया गया पूर्व सेमेटिक खोज लेख. आरआरएफ यहाँ के लिए उपयोग किया जाता है वरीयता, रिकार्ड नहीं करता है।
// Fuse using RRF with category/freshness boosts
var fusedDtos = _ranker.FuseResults(bm25Results, vectorResults, query);
आरआरएफ एल्गोरिथ्म का उपयोग करता है 1/(k+rank) जहां k=60 कई स्रोतों से परिणामों को जोड़ने के लिए M SK1 तब boosts के लिए लागू करता है
पूर्ण आरआरएफ क्रियान्वयन और हाइब्रिड खोज कैसे काम करता है क्रिया में सांकेतिक खोज. embeddings और वेक्टर समानता में गहरे खोज के लिए अधिवक्ता GPT का निर्माण. एक ही मूल संरचना उपयोगकर्ता को दोनों शक्तियां प्रदान करता है
नए घटक सेवाओं के रूप में पंजीकृत हैं
services.AddSingleton<SearchQueryParser>();
services.AddSingleton<SearchRanker>();
services.AddScoped<BlogSearchService>();
SearchQueryParser और SearchRanker एकलटन होते हैं क्योंकि वे stateless है BlogSearchService स्कॉप किया गया है क्योंकि यह डाटाबेस संदर्भ पहुँचता है
एक ही सेमेटिक खोज अवयव भी द्वारा उपयोग किया जाता है वकील जीपीटी AI के लिए प्रासंगिक पिछली ब्लॉग पोस्टों को प्राप्त करने की प्रणाली भाग 4 इंजेक्शन पाइपलाइन के बारे में जानकारी के लिए
खोज पूर्वानुमानित पर निर्भर करता है SearchVector स्तंभ में BlogPosts तालिका
ALTER TABLE mostlylucid."BlogPosts"
ADD COLUMN "SearchVector" tsvector
GENERATED ALWAYS AS (
to_tsvector('english',
coalesce("Title", '') || ' ' ||
coalesce("PlainTextContent", '')
)
) STORED;
CREATE INDEX idx_blog_posts_search_vector
ON mostlylucid."BlogPosts"
USING GIN ("SearchVector");
GIN (Generalized Inverted Index) provides fast full
जब ILIKE @item: inlistbox Adjective type
यह दृष्टिकोण लाखों पंक्तियों के बीच स्वैच्छिक उपस्ट्रिंग खोज पर मापन नहीं करेगा, लेकिन लक्षित लघुकवि के लिए वापसी यह
ts_rank_cd)पोस्टग्रेएसक्यूएल पूरा के लिए दो रैंकिंग फ़ंक्शन्स प्रदान करता है
ts_rankबुनियादी शब्द आवृत्तिts_rank_cdकवर घनत्व रैंकिंगहम उपयोग करते हैं ts_rank_cd क्योंकि यह प्रदान करता है BM25-सदृश प्रासंगिकता समय निकटता को ध्यान में रखते हुए
// Order by cover density ranking - rewards term proximity
orderedQuery = searchQuery.OrderByDescending(x =>
x.SearchVector.RankCoverDensity(EF.Functions.ToTsQuery("english", tsQuery)));
क्यों ts_rank_cd बेहतर है
| मीट्रिक | ts_rank |
ts_rank_cd |
|||
|---|---|---|---|---|---|
| एल्गोरिथम | सावधि आवृत्ति | ||||
| बहुत से शब्द क्वेरी | शर्तों को अलग से गणना करता है | पुरस्कार शर्तें एक साथ प्रकट होती हैं | |||
| उदाहरण के साथ | अनुच्छेद1से अधिक गुण प्राप्त करता है | docker | " | 50 | times scores high |
| निष्पादन | तेजी से | मार्जिनally slower, अभी भी GIN सूचकांक को लेबरेज करता है | |||
| व्यवहार साधारण गणना |
यह एक है त्वरित जीत अनुकूलन 0 अनुप्रयोग के साथ बेहतर प्रासंगिकता रैंकिंग SearchVector.
संदर्भ
खोज कार्यात्मकता को सुधारने के अलावा कई प्रमुख सुधार निष्पादन में सुधार करते हैं
उपलब्ध भाषाएँ दुर्लभ रूप से बदलती हैं लेकिन प्रत्येक खोज अनुरोध पर क्वेरी की जाती है
private static readonly TimeSpan LanguageCacheDuration = TimeSpan.FromHours(1);
private static List<string>? _cachedLanguages;
private static DateTime _languageCacheExpiry = DateTime.MinValue;
private static readonly SemaphoreSlim _cacheLock = new(1, 1);
प्रभाव: खोज अनुरोध प्रति डाटाबेस क्वेरी को समाप्त करता है
मूल कोड का प्रयोग foreach बहुविध WHERE खंडों को बनाने वाले लूप्स. अब एकल एक्सप्रेशन में बैच किया गया
// BEFORE: Multiple WHERE clauses
foreach (var acronym in acronymTerms)
{
searchQuery = searchQuery.Where(x =>
EF.Functions.ILike(x.Title, $"%{acronym}%"));
}
// AFTER: Single batched WHERE
if (acronymTerms.Count > 0)
{
searchQuery = searchQuery.Where(x =>
acronymTerms.Any(acronym =>
EF.Functions.ILike(x.Title, $"%{acronym}%")));
}
प्रभावक्लीनर एसक्यूएल
आंशिक अनुक्रमणिका जोड़ी गई है INCLUDE स्तम्भों के साथ बार-बार अभिगम पैटर्न
CREATE INDEX idx_blog_posts_search_covering
ON mostlylucid."BlogPosts" ("LanguageId", "IsHidden", "ScheduledPublishDate")
INCLUDE ("Id", "Slug", "Title", "PublishedDate")
WHERE "IsHidden" = false;
प्रभावसक्षम करता है सिर्फ स्कैन करता है PostgreSQL को तालिका ढेर पहुँचने की जरूरत नहीं है
ईएफ कोर Include() पूर्ण नेविगेशन इकाइयों को लोड करता है. जहाँ केवल WHERE खंडों में प्रयुक्त नेविगेशन गुणों को हटाया जाता है
// BEFORE: Loads full LanguageEntity into memory
.Include(x => x.LanguageEntity)
.Where(x => x.LanguageEntity.Name == "en")
// AFTER: EF translates navigation property without loading entity
.Where(x => x.LanguageEntity.Name == "en")
प्रभावमेमोरी कमी
संदर्भ
खोज बनाता है जहाँ खंडों को क्रमशः उपयोग करते हुए EF Core's एक्सप्रेशन ट्री संरचना:
IQueryable<BlogPostEntity> searchQuery = baseQuery;
// Each filter added conditionally - PostgreSQL optimizes the final query
if (parsed.Phrases.Count > 0) { searchQuery = searchQuery.Where(...); }
if (!string.IsNullOrWhiteSpace(tsQuery)) { searchQuery = searchQuery.Where(...); }
if (acronymTerms.Count > 0) { searchQuery = searchQuery.Where(...); }
यह एक उत्पन्न करता है एकल अनुकूलित एसक्यूएल क्वेरी बहुविध राउंड टाइप के बजाय
सभी optimizations का संकलित प्रभाव
अनुकूलन |-------------|----------------|----------------| भाषा कैशिंग हटाएँ शामिल करें बैच इलाइक कवरिंग अनुक्रमणिका | tsM SK1rank_cd | समान | बेहतर प्रासंगिकता कैश फिक्स (मार्ग पैरामीज़
कुल प्रत्याशित सुधार: 30-50% तेज खोज काफी कम डाटाबेस लोड के साथ
Don't assume full-text search handles everything: एक्रोनिम और विशेष अक्षरों जैसे किनारे के मामलों को विशेष व्यवहार की आवश्यकता होती है
बहुविध दृष्टिकोणों को संयोजित करेंपाठ खोज (BM
गूगल ने उपयोगकर्ताओं की अपेक्षाओं को आकार दिया: उद्धरण वाक्यांशों को समर्थित करता है।
सहायक अपवर्त उपलब्ध कराएँजब खोज असफल होती है तो
पार्सेएक उचित क्वेरी पार्सर स्ट्रिंग परिचालनों की श्रृंखला से अधिक साफ और रख-रखाव करने योग्य है
PostgreSQL लाईवरेजउपयोग ts_rank_cd के बजाय ts_rank के लिए BM25-सदृश प्रासंगिकता. आवरण घनत्व रैंकिंग शब्द समीपता को मानता है | - | एक त्वरित जीत जो शून्य अनुप्रयोग के साथ परिणाम गुणवत्ता में सुधार करता है
अनुकूलन से पहले प्रोफ़ाइल: त्रुटियों को स्पष्ट करता है।
बैच डाटाबेस ऑपरेशन: बहुविध foreach लूप अलग अलग WHERE खंड बनाने के लिए उपoptimal SQL उत्पन्न करता है Any() या All() एकल एक्सप्रेशन में बैच करने के लिए
भविष्य के लिए संभावित सुधार
पुनःप्राप्ति सुधार:
पद वर्णन संवर्धन:
category:ASP.NET प्रचालकafter:2025-01-01 प्रचालकरैंकिंग परिमार्जन:
प्रेक्षणशीलता:
निर्माण उत्पादन-श्रेणी खोज को किनारा केसों को स्थिर करने की आवश्यकता होती है और निष्पादन को अनुकूलित करना. इस लेख में दोनों शामिल हैं। ts_rank_cd).
क्रियान्वयन प्राप्त करता है 30-50% तेज खोज के माध्यम से डाटाबेस लोड को कम करते हुए
ts_rank_cdबेहतर प्रासंगिकता के लिए )प्रमुख अंतर्दृष्टि: कोई एकल दृष्टिकोण सभी केसों को नियंत्रित करता है।
सांकेतिक खोज अवसंरचना दो उद्देश्यों की सेवा करती है
संबंधित लेख:
आधिकारिक दस्तावेज़
सभी कोड उपलब्ध है ब्लॉग's GitHub रिपोजिटरी.
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.