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
haku on yksi niistä ominaisuuksista, joita kaikki aliarvioivat. Se vaikuttaa merkityksettömältä, kunnes todelliset käyttäjät alkavat kirjoittaa todellisia kysymyksiä. käsitteellisesti oikea mutta tekstinvastainen. Kun haku epäonnistuu näissä tapauksissaM SK1 käyttäjät eivät 'koskaa "silmäkohtainen tapaus
On myös järjetöntä, että ”'” on häirinnyt tätä sivustoa sen jälkeen kun se alun perin otettiin käyttöön. Avoin haku, on työskennellyt PostgreSQLin täytäntöönpanoa koskevassa tekstin etsinnössä sen kanssa ' fancy vector stuff, mutta ei koskaan ollut aidosti tyytyväinen siihen hätiköity minä erityisesti nyt I' olen rakentamassa hakuvälinettä ilmeinenRAG No, mielestäni minun olisi lopultakin korjattava se. Se, onko se totta vai ei, on toinen asia.
Tässä artiklassa ei ole kyse ' -tarkoituksista rakentaa hakukoneesta alusta alkaen, vaan .-tarkoituksesta korjata PostgreSQLin täsmälliset reunat, "M SK3" -tekstin etsintä todellisessa tuotantojärjestelmässä, . -niminen, "I"-niminen", "I5" -lähestyisin niihin erityisiin virheisiin, joita kohtasin, "SSK6" -kysymyksiin, jotka johtuvat tapahtuneista tapahtumista, "MSSK7" -tapaukseen ja käytännön korjauksiin, joita tarvitaan, jotta haku käyttäytyisi niin kuin käyttäjät jo odottavat.
Aikaisemman työn pohjalta: Tässä artiklassa laajennetaan semanttisten hakujen toteuttaminen , jossa lisättiin ristiriitainen haku Reciprocal Rank Fusionin kanssa. (RRF). Edellinen artikkeli käsitteli perustaa.
Semanttinen hakuinfrastruktuuri palvelee kahta tarkoitusta: ja tarjoaa jäljittämislaitteen Oikeusasiamies GPT RAG-järjestelmä -kirjanpitäjä, joka laatii uusia työpaikkoja käyttäen sivustoa' olemassa oleva tietopohjana oleva sisältöM SK2 syvemmän katsauksen taustalla oleviin tekniikkoihin "Lawyer GPTM SK1 – Osa SSK3 embeddings ja vector search.
Kun haku ei tuonut tulosta, järjestelmä oli näyttämässä sattumanvaraisia vanhoja artikloita hyödyllisten ehdotusten sijasta . Tämä rikkoi vähiten yllättämisen periaatetta M SK2 käyttäjät odottivat joko asiaankuuluvia tuloksia tai selvää |" |ei löydetty yhteensopivuutta | " |viesti viimeaikaisten toimien perusteella ehdotuksina |
Ratkaisu: Muutettu BlogSearchService.HybridSearchWithPagingAsync() tunnistaa, kun haku tuottaa nollatulokset ja palauttaa viimeisimpien, päivämäärää laskemalla järjestettyjen postitusten luetteloon. NoMatchFound lippu BasePagingModel niin että UI voi esittää asianmukaisen viestin, kuten "Ei löydetty yhdistelmää.
// 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's default English text search configuration technically indexes acronyms, but normalization and stemming make shortMSC2 caseM SK3significant terms unreliable in practice . Kun etsiä MST5DiSEMSP6 se laskee lowercased to SSK7diseMSL8 ja sitten englanninkielinen sanasto voi hylätä sen tai antaa sille alhaisen painon.
Ratkaisu: lisättiin acronym-selvitys (terms ≤6hedelmät, joissa on uppercase- अक्षरtM SK3 ja tapaus-insensitive substring fallback PostgreSQLin avulla ILIKE. Tämä täydentää täytäntöönpanoon liittyvää tekstitarkistusta, eikä korvaa sitä.
// 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}%"));
hakut "ASP.NETM SK2 tai "C#" epäonnistuisivat, koska PostgreSQL'n tekstin parsaattori treats periods and hash symbols as delimiters.
Ratkaisu: On luotu SearchQueryParser joka tunnustaa yhteiset tekniset termit ja korvaa ne hakukelpoisilla versioilla:
private static readonly Dictionary<string, string> TechnicalTerms = new(StringComparer.OrdinalIgnoreCase)
{
["asp.net"] = "aspnet",
["c#"] = "csharp",
[".net"] = "dotnet",
["f#"] = "fsharp",
["node.js"] = "nodejs",
// ... more terms
};
PostgreSQL kohtelee "ja" pysähdyssanaksi ja poistaa sen hakujen ulkopuolelle.
Ratkaisu: On otettu käyttöön Googlen mallia käyttävä kyselyparsaattori, joka käsittelee stop-sanoja älykkäästi ja tukee kehittyneitä hakuoperaattoreita.
Olen toteuttanut SearchQueryParser luokka, jossa analysoidaan kysymyksiä tukemalla:
"exact match" - etsii täsmällistä ilmausta-unwanted - ei sisällä tätä termiä sisältäviä tuloksiaASP* - vastaukset "ASPM SK2 "AspNET", "ASPNetCoreMST6 jneMSC7Parsaattori käyttää koottua regex-mallia kysymyksen merkitsemiseksi:
[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());
}
}
}
Parsaattori tuottaa rakenteellisia tietoja, jotka muunnetaan PostgreSQLiin. to_tsquery koska me ' luomme rakenteellista syntaxia - emme websearch_to_tsquery koska olemme jo itse analysoineet operaattorit, joten meillä on enemmän valvontaa siitä, miten ehdot yhdistetään.
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;
}
Miksi ei vain käyttää
websearch_to_tsquery? Koska se ei vieläkään toimi teknisellä tasolla, acronymit, ja domainM SK2erityinen syntax -ja ei tarjoa hakkeita hybridijärjestykseen tai fallbacks-lähestyksiinMska4 Analyysoimalla itseämmeMSC5 voimme käsitellä näitä etutapauksia ennen kuin ne saavuttavat PostgreSQLin
Euroopan unionin BuildSearchQuery menetelmä on kirjoitettu kokonaan uudelleen, jotta voitaisiin käyttää analysoitua kysymyksen rakennetta. baseQuery jo kielellä suoritettavat filtrit, päivämäärän ulottuvuus
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;
}
Tässä on joitakin esimerkkejä parannetusta hakutehokkuudesta:
DiSE
✅ Vastaa nyt artikloille, joiden otsikko tai sisältö on "DiSEM SK2
ASP.NET
✅ löytää artikkeleita ASP:staM SK1NET ( automaattisesti muunnettuna SSK3aspnet" täyttämään
"semantic search"
✅ löytää artikloissa täsmällisen lauseen
ASP.NET -Core
✅ löydetään ASP-artikkeleja, mutta jätetään pois ne artiklat, joissa mainitaan
ASP*
✅ Matches "ASPM SK2 "AspNET", "ASPNetCoreMSC6 jneMST7
"full text search" PostgreSQL -MySQL
✅ löytää artiklaa, jossa on täsmällinen sanamuoto "täydellinen tekstitutkimus" ja postgreSQLin mainitseminenM SK3, mutta lukuun ottamatta MySQL:tä mainitsevia artikloja
Kysymys: ASP.NET and Alpine
Ennen (kaikki
Sen jälkeen (fixed
haku integroidaan vastavuoroisen luokan fuusioon (RRFM SK1 sellaisena kuin se on toteutettu aikaisempi semanttinen hakuartikkeli. RRF:tä käytetään luokitus, ei palaa mieleen - se yhdistää jo nyt-BM:stä saatuja tuloksiaM SK3 täydellinenMSC4tekstin haku ja vektorin semanttinen hakuMST5
// Fuse using RRF with category/freshness boosts
var fusedDtos = _ranker.FuseResults(bm25Results, vectorResults, query);
RRF-algoritmi käyttää 1/(k+rank) jossa k=60 yhdistää tulokset monista lähteistä M SK1 ja soveltaa lisäyksiä :
Lisätietoja RRF:n täytäntöönpanosta ja hybriditutkimuksen toiminnasta, Semanttinen haku toiminnassa. syvempien syvennysten ja vektorin samankaltaisuuksien selvittämiseksi "Lawyer GPTM SK1 - Osa SSK3. Sama infrastruktuuri mahdollistaa sekä käyttäjän että RAG-tarkastamisen.
Uudet komponentit on rekisteröity palveluiksi:
services.AddSingleton<SearchQueryParser>();
services.AddSingleton<SearchRanker>();
services.AddScoped<BlogSearchService>();
SearchQueryParser ja SearchRanker ovat yksitonisia, koska ne' ovat valtiottomiaM SK1 teräs -varmat, ja niitä voidaan käyttää uudelleen kaikkialla BlogSearchService on laajennettu, koska se liittyy tietokannan kontekstiin.
Samaa semanttista hakukomponenttia käytetään myös Oikeusasiamies GPT järjestelmä, jonka avulla voidaan hakea asiaankuuluvia AI:n aikaisempia blog postsia Osa 4 lisätietoja juomaputkesta.
haku perustuu ennakkoarvioon SearchVector pilari BlogPosts taulukko:
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 (Yleinen käännetyn indeksin indexi) tarjoaa nopean täytäntöönpanoon perustuvan -tekstin etsinnän laajassa tekstissä
Kun ILIKE (caseM SK1insensitive LIKE
Tämä lähestymistapa ei muuttuisi mielivaltaiseen alastringin hakuun miljoonien rivien välillä -, mutta kohdennettujen acronymien fallback-tarkoituksiin se's appropriateM SK2
ts_rank_cd)PostgreSQL tarjoaa kaksi luokitustehtävää täysmääräiseen-tekstin etsintäänM SK1
ts_rank: Perustermien taajuusts_rank_cd: Kattodensiteettien luokitus (kuten läheiset ehdot näyttävät yhdessäMe käytämme ts_rank_cd koska se tarjoaa BM25- vastaava merkitys ottamalla huomioon tähtäimen läheisyys
// Order by cover density ranking - rewards term proximity
orderedQuery = searchQuery.OrderByDescending(x =>
x.SearchVector.RankCoverDensity(EF.Functions.ToTsQuery("english", tsQuery)));
Miksi? ts_rank_cd on parempi:
| Metriki | ts_rank |
ts_rank_cd |
|---|---|---|
| Algoritmi | Lyhyen aikavälin taajuus | |
| Useat-sanakysymykset | laskee ehtoja erikseen | Palkintojen ehtojen esiintyminen yhdessä |
| Esimerkki: M SK1kolikoiden kontit" | Tekijä, jolla on "kotteritM SK2 SSK3 prosenttiosuudet ovat korkeat | Tekiijä, jossa on "koterissa olevat kontit SSK6 yhdessä tulostaso on korkeampi S |
| Toimivuus | Nopeutettu | Poikkeuksellisen hitaampi , edelleen alijäämä GIN-indeksille M |
| Elintavat | Yksinkertainen laskeminen | Lyhyesti BM |
Tämä on nopeiden voittojen optimointi - parempi relevanssi-Ranking nollakäytännön kanssa -tasolaskentaM SK2 Kaikkien järjestelmien luominen tapahtuu PostgreSQLissa käyttämällä olemassa olevaa GIN-indeksiä SearchVector.
Viittaukset:
Tutkimuksen toiminnallisuuden korjaamisen lisäksi monet avainoptimointitoimet parantavat suorituskykyä:
Käytettävissä olevat kielet muuttuvat harvoin, mutta ne kysyttiin jokaisessa hakupyyntössä.. Nyt kaksoiscachoitu.
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);
Vaikutus: Poistaa 1 tietokannan kysymyksen kutakin hakupyyntöä kohden
Alkuperäinen koodi foreach loopit, jotka luovat useita WHERE-lausekkeita. Nyt koottu yhteen ainoaan ilmaisuun:
// 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}%")));
}
Vaikutus: puhtaampi SQL, ~5-10% nopeampi monenvälisissä kysymyksissä
Lisättiin osittainen indeksi INCLUDE-columnin kanssa, joka koskee usein saatavilla olevia menetelmiä:
CREATE INDEX idx_blog_posts_search_covering
ON mostlylucid."BlogPosts" ("LanguageId", "IsHidden", "ScheduledPublishDate")
INCLUDE ("Id", "Slug", "Title", "PublishedDate")
WHERE "IsHidden" = false;
Vaikutus: mahdollistaa index-onnilliset skannaukset - PostgreSQL:ssa ei ole tarpeen käyttää taulukkohauppaa, eikä ' tarvitse ottaa käyttöön taulukoita.
EF Core's Include() loads full navigation entities. Poistettu, jos vain ns. navigointiin liittyvät tekijät on käytetty KDE-lausekkeissa
// 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")
Vaikutus: ~5-10% muistin vähentäminenM SK2 vähemmän tietoja, jotka siirretään tietokannasta
Viittaukset:
etsintä rakentaa, Missä lausekkeita käytetään vähitellen EF Core's ilmaisupuu koostumus:
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(...); }
Tämä tuottaa yksi optimoitu SQL-kysymys useiden ympäriajojen sijasta. PostgreSQLin kyselysuunnitelman käyttäjä voi käyttää tilastoja ja indeksejä tehokkaasti, kun se katsoo täydellisen MERE-lausekkeen
Kaikkien optimointien kumulatiivinen vaikutus:
| Optimointi | Latenssivaikutus | DB-laadun vaikutukset S | ||||
|---|---|---|---|---|---|---|
| Kielten tallentamisjärjestelmä | vähimmäismäärä SSK2 -1 kysymys | |||||
| Poistaa IncludeM SK1 | 3 | 4 | Vähemmän tiedonsiirtoa | |||
| Batch ILIKE | -5-10% ♫ ♫ Cleaner SQL ♫ | |||||
| Katkaiseva indeksi SSK1 -20-30% | Indexi | SSK4 | Vain skansointi | |||
| tsM SK1rank_cd | Samankaltainen SSK4 Parempi merkitys | |||||
| Cache fix (route params) | NM SK4A M | Poistaa vanhentuneet tiedot |
Kaiken kaikkiaan odotettu parannus: 30-50% nopeampi haku tietokannan kuormitusta vähennetään merkittävästi.
Don't assume full-tekstin haku käsittää kaiken: Akronymien ja erityispiirteiden kaltaiset raja-asiat tarvitsevat erityistä käsittelyä
Yhdistetään useat lähestymistavat: TäsmällinenM SK1tekstin haku (BM25) + semanttinen haku | (vectorit \ ) \ + \substring fallbacks tarjoaa paremman kattamisen kuin mikään yksittäinen menetelmä
Googlen luomat käyttäjän odotukset: Mainitujen lausekkeiden tukeminen
Mahdollistetaan hyödylliset puutteet: Kun haku epäonnistuuM SK1 en' ei näy mitään - kuvaa äskettäin lähetettyjä posteja ja selventää, että mitään yhdistelmää ei löydetty
Parse, donM SK1t hack: Asianmukainen kysymyksen parsaattori on puhtaampi ja paremmin ylläpidettävä kuin joukko string-manipulaatioita
PostgreSQL:n käyttömahdollisuus: käyttö ts_rank_cd sen sijaan, että ts_rank BM:n osalta
Profili ennen optimointia: " ilmeiset" pullonkaulat ( FTS-analyysin parsiminenM SK4 eivät olleet todellista ongelmaaMSL5 MSL6 Kielten hakutMSR7 liiallinen mukaan lukienMST8 ja puuttuvat indeksit olivat odotettua suuremmat vaikutukset
Tappitietokannan toiminnot: Usein foreach loops creating separate WHERE clauses generate suboptimal SQL. Use Any() tai All() liitetään yhteen ainoaan ilmaisuun.
Mahdolliset parannukset tulevaisuutta varten
Takaamista koskevat parannukset:
Parsointiin liittyvät parannukset:
category:ASP.NET operaattoriafter:2025-01-01 operaattoriLuokittelun tarkistukset:
Näkyvyys:
Rakennustuotanto-laadun haku edellyttää äärien korjaamista ja suorituskyvyn parantaminen. Tässä artiklassa käsitellään sekä : PostgreSQLin täytäntöönpanon korjaamista että - tekstihuomautuksia. ts_rank_cd).
täytäntöönpanossa saavutetaan 30-50% nopeampi haku vähentämällä tietokannantaa through:
ts_rank_cd) asianmukaisuuden parantamiseksiKeskeinen näkemys: yksi ainoa lähestymistapa ei käsitellä kaikkia tapauksia. PostgreSQL FTS on erinomainen avainsanojen yhteensovittamisessaM SK2 semanttiset hakut käsittävät käsitteelliset kysymykset , ja kohdennetut virheet kiinnittävät etätapauksetMSC4 puhdas parsointilaaja yhdistää ne toisiinsa–, käsitellään operaattoreita ja teknisiä termejä ennen kuin ne pääsevät hakukoneisiin–.
Semanttinen hakuinfrastruktuuri palvelee kahta tarkoitusta:
Aiheita koskevat artiklat:
Virallinen asiakirja:
Kaikki saatavilla olevat koodit blog's GitHub-tietokanta.
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.