Tuhanded mĂŒĂŒgiĂŒlemad ĂŒle kogu riigi registreerivad igapĂ€evaselt kĂŒmneid tuhandeid kontakte â suhtlemise fakte potentsiaalsete vĂ”i juba meiega töötavate klientidega. Mida aga selle kliendi leidmiseks on kĂ”ige parem teha, on soovitav vĂ€ga kiiresti. See toimub enamasti nime pĂ”hjal.
SeetĂ”ttu pole ĂŒllatav, et vaadates taas "raskete" pĂ€ringute puhul ĂŒhte meie kĂ”ige koormatud andmebaasi â meie enda , avastasin, et "tipptaseme" pĂ€ringu jaoks "kiire" nime jĂ€rgi otsimiseks organisatsioonide kaardid.
Veelgi enam, edasine uurimine tÔi esile huvitava nÀite esmalt optimeerimisest ja seejÀrel toimivuse halvenemisest pÀringu jÀrjestikuse arendamise kÀigus mitme meeskonna poolt, kes tegutsesid ainul parimate kavatsustega.
0: mida tahtis kasutaja
[KDPV ]
Mida kasutaja tavaliselt mĂ”tleb, kui rÀÀgib "kiirest" otsingust nime jĂ€rgi? Peaaegu kunagi ei ole see "aus" otsing lĂ”igendiga nagu ... LIKE '%roosa%' â sest siis satub tulemustesse mitte ainult 'Roosalia' ja 'Pood Roosa', vaid ka 'Groosa' ja isegi 'Vanaema Roosa'.
Kasutaja mĂ”istab aga igapĂ€evaselt, et tagate talle otsingu sĂ”na algusele nimedes ja nĂ€itate asjakohasemaid tulemusi, mis algavad sisestatud. Ja teete seda praktiivselt koheselt â lĂ”ige sisestamise puhul.
1: piirame ĂŒlesande
Ja inimesel ei tule kindlasti eraldi sisestada 'roos pood', et iga sĂ”na peab teilt teatud eesliidet otsima. Ei, kasutajale on palju lihtsam reageerida viimase sĂ”na kiirele soovitusele, kui eesmĂ€rgipĂ€raselt "puudu jĂ€tta" eelnevad â vaadake, kuidas tĂ”husalt töötab iga otsingumootor.
Tegelikult, Ă”igesti nĂ”uete formuleerimine ĂŒlesandele â on juba ĂŒle poole lahendusest. MĂ”nikord vĂ”ib tĂ€helepanelik use case analĂŒĂŒs .
Mida aga abstraktne arendaja teeb?
1.0: vÀline otsingumootor
Oh, otsing on keeruline, pole ĂŒldse tahtmist sellega tegeleda â laseme selle devopsile! Las nad seadistavad meile vĂ€list, andmebaasist suhteliselt lahutatud otsingusĂŒsteemi: Sphinx, ElasticSearch,âŠ
Töötav, kuigi muutuste sĂŒnkroonimise ja operatiivsuse osas töömahukas variant. Kuid meie puhul mitte, kuna otsimine toimub iga kliendi jaoks ainult tema konto andmete raames. Andmed on piisavalt muutlikud - ja kui juhataja hetkel lisas kaardi, 'Rosa Pood', siis vĂ”ib 5-10 sekundi pĂ€rast meelde tulla, et unustas seal e-posti aadressi mĂ€rkida ja soovida seda leida ja parandada.
Seega - laseme otsida âotse andmebaasistâ. Ănneks vĂ”imaldab PostgreSQL meil seda teha, ja mitte ainult ĂŒhe variandiga - neid vaatamegi.
1.1: âausâ alamstring
Haarame kinni sĂ”nast âalamstringâ. Just selleks, et teostada indeksipĂ”hist otsingut alamstringide (ja isegi regulaaravaldiste) jĂ€rgi, on olemas suurepĂ€rane ! Ainult hiljem tuleb see korralikult jĂ€rjestada.
Proovime vÔtta lihtsuse huvides sellise lisa:
LOOMINE TABEL firms(
id
seeria
PRAEGUNE VĂTI
, nimi
tekst
);Laeme sinna 7,8 miljonit reaalset organisatsiooni ja indekseeri:
LISA UGAL pg_trgm;
LOOME INDeksi firms KASUTADES gin(lower(nimi) gin_trgm_ops);Otsime alamstringi otsinguks esimesed 10 kirjet:
VALI
*
FROM
firms
WHERE
lower(nimi) ~ ('(^|s)' || 'roosa')
ORDER BY
lower(nimi) ~ ('^' || 'roosa') DESC -- esmalt "alustavad"
, lower(nimi) -- ĂŒlejÀÀnud tĂ€hestiku jĂ€rgi
LIMIT 10;

Noh, nii... 26ms, 31MB loetud andmeid ja rohkem kui 1,7K filtreeritud kirjeid - 10 otsitava jaoks. Kulu on liiga suur, kas ei saaks kuidagi efektiivsemalt?
1.2: tekstipÔhine otsing? see on FTS!
TÔepoolest, PostgreSQL pakub vÀga vÔimsat (Full Text Search), sealhulgas eelduseotsinguga. SuurepÀrane variant, pole isegi laiendusi vaja installida! Proovime:
LOOME INDeksi firms KASUTADES gin(to_tsvector('simple'::regconfig, lower(nimi)));VALI
*
FROM
firms
WHERE
to_tsvector('simple'::regconfig, lower(nimi)) @@ to_tsquery('simple', 'roosa:*')
ORDER BY
lower(nimi) ~ ('^' || 'roosa') DESC
, lower(nimi)
LIMIT 10; 
Siin aitas natuke paralleelne pĂ€ringu tĂ€itmine, vĂ€hendades aega poole vĂ”rra kuni 11ms. Ja pidime lugema 1,5 korda vĂ€hem - kĂ”igest 20MB. Ja siin kehtib, et mida vĂ€hem - seda parem, sest mida suurem maht me tĂ”mbame, seda suurem on vĂ”imalus saada vahemĂ€lu vahelejĂ€tmiseks, ja iga liigne loetud andmefaili leht - potentsiaalne âpidurdusâ pĂ€ringu jaoks.
1.3: ikkagi LIKE?
KÔik eelmine pÀring on hea, aga kui seda tÔmmata sada tuhat korda pÀevas, siis mahtude jÀlgimine vÔib hakata ulatuma juba 2TB lÀbitud andmed. Parimal juhul - mÀlust, aga kui ei vea, siis ka kettalt. Nii et proovime seda vÀiksemaks teha.
KÀidame meeles, et kasutaja tahab nÀha esmalt "mis algab ...". Noh, see on ju tÔeline kasutades text_pattern_ops! Ja ainult juhul, kui meil "ei piisa" 10 otsitavast kirjest, tuleb neid lugeda FTS-otsinguga:
Loo indeks ettevÔtete kohta (lower(name) text_pattern_ops);VALI
*
KINNITUS
ettevÔtete
KUS
lower(name) LIKE ('roos' || '%')
PIIRANG 10; 
SuurepĂ€rased nĂ€itajad - kokku 0,05 ms ja veidi ĂŒle 100 KB loetud! Ainult me unustasime nimede jĂ€rgi sorteerimise, et kasutaja ei eksiks tulemuste seas:
VALI
*
KINNITUS
ettevÔtete
KUS
lower(name) LIKE ('roos' || '%')
SORTEERI
lower(name)
PIIRANG 10; 
Oi, midagi on juba vÀhem ilus - tundub, et indeks on olemas, aga sorteerimine jÀtab teda kÔrvale... See on kindlasti juba oluliselt efektiivsem kui eelmine variant, aga...
1.4: "töötleme kÀsitsi"
Aga on ju indeks, mis vÔimaldab otsida ka vahemikus ja kasutada sorteerimist normaalselt - tavaline btree!
Loo indeks ettevÔtete kohta (lower(name));Aga pÀring tuleb "koguda kÀsitsi":
VALI
*
KINNITUS
ettevÔtete
KUS
lower(name) >= 'roos' JA
lower(name) <= ('roos' || chr(65535)) -- UTF8 jaoks, ĂŒhesĂŒgavuse puhul - chr(255)
SORTEERI
lower(name)
PIIRANG 10; 
SuurepĂ€rane - ja sortimine töötab, ja ressursikasutus jĂ€i "mikroskoopiliseks", tuhandetes kordi efektiivsem kui "puhas" FTS! JÀÀnud on koguda ĂŒhte pĂ€ringusse:
(
VALI
*
KINNITUS
ettevÔtete
KUS
lower(name) >= 'roos' JA
lower(name) <= ('roos' || chr(65535)) -- UTF8 jaoks, ĂŒhesĂŒgavuse puhul - chr(255)
SORTEERI
lower(name)
PIIRANG 10
)
UNION ALL
(
VALI
*
KINNITUS
ettevÔtete
KUS
to_tsvector('simple'::regconfig, lower(name)) @@ to_tsquery('simple', 'roos:*') JA
lower(name) NOT LIKE ('roos' || '%') -- "mis algavad" oleme juba leitud ĂŒleval
SORTEERI
lower(name) ~ ('^' || 'roos') DESC -- kasutame sama sortimist, et mitte minna btree-indeksisse
, lower(name)
PIIRANG 10
)
PIIRANG 10; TĂ€heldan, et teine alamkĂŒsitlus kĂ€ivitatakse ainult siis, kui esimene tagastas oodatust vĂ€hem rida. LIMIT Sellest optimeerimise meetodist olen ma .
Nii on, meil on nĂŒĂŒd tabelil samaaegselt btree ja gin, kuid statistiliselt selgus, et alla 10% pĂ€ringutest jĂ”uavad teise ploki tĂ€itmise juurde. See tĂ€hendab, et selliste ette teadaolevate tĂŒĂŒpiliste piirangute puhul suudame me vĂ€hendada serveri kogukulutusi praktiliselt tuhandetes kordades!
1.5*: saame hakkama ilma viimistlemiseta
Ăleval LIKE meile segas vale sorteerimine. Kuid selle saab "Ă”igele teele suunata" USING- operaatori nĂ€itamisega:
Tavaliselt eeldatakse
ASC. Lisaks on vÔimalik mÀÀrata konkreetse sorteerimisoperaatori nimi lausesUSING. Sorteerimisoperaator peab olema "vÀiksem" vÔi "suurem" mÔnest B-puu opsioonide perekonnast.ASCtavaliselt on samavÀÀrneUSING <jaDESCtavaliselt on samavÀÀrneUSING >.
Kohapeal "vÀiksem" on see ~<~:
SELECT
*
FROM
firms
WHERE
lower(name) LIKE ('roosa' || '%')
ORDER BY
lower(name) USING ~<~
LIMIT 10; 
2: kuidas pÀringud "hapenduvad"
NĂŒĂŒd jĂ€tame meie pĂ€ringu "seisma" kuue kuu kuni aastani, ja avastame ĂŒllatusega, et see on taas "tipphetkes" kogutud pĂ€evase "mĂ€lu tĂ”stmise" nĂ€itajatega (buffers shared hit) 5.5TB â seega veelgi rohkem kui algselt.
Muidugi on meie Ă€ri kasvanud ja koormus on suurenenud, kuid mitte nii palju! Siis on midagi kahtlast â uurime lĂ€hemalt.
2.1: lehepteo sĂŒnn
MĂ”nes etapis tahtis teine arendajate meeskond vĂ”imaldada kiire altotsingu kaudu "hĂŒppamist" registrisse, mis sisaldas samu, kuid laiendatud tulemusi. Aga milline register ilma lehe navigeerimiseta? Loodame selle Ă€ra teha!
( ... LIMIT + 10)
UNION ALL
( ... LIMIT + 10)
LIMIT 10 OFFSET ;NĂŒĂŒd oli arendajal vĂ”imalik hĂ”lpsasti nĂ€idata otsingutulemuste registrit "lehe-tĂŒĂŒpi" alalaadimisega.
Muidugi, tegelikult kui iga jĂ€rgmise lehe andmeid loetakse ĂŒha rohkem (kĂ”ik eelmisest korrast, mida me kĂ”rvale viskame, pluss vajalik "sabajupp") â see on selgelt vale muster. Ăigem oleks olnud kĂ€ivitada otsing jĂ€rgmise iteratsiooni juurest, salvestatud vĂ”tmega, aga sellest rÀÀgime teisel korral.
2.2: tahaks eksotikat
MÔnes etapis soovis arendaja mitmekesistada tulemuste valikut andmetega teise tabeli seest, milleks kogu eelnev pÀring saadeti CTE-sse:
WITH q AS (
...
LIMIT + 10
)
SELECT
*
, (SELECT ...) sub_query -- mingisugune pÀring seotud tabelisse
FROM
q
LIMIT 10 OFFSET ;Ja isegi nii â mitte halb, kuna siseriikide pĂ€ring arvutatakse ainult 10 tagastatud kirje jaoks, kui just ei olnud...
2.3: DISTINCT mÔttetu ja halastamatu
Kusagil sellise evolutsiooni protsessis kadusid 2. alampĂ€ringust kadus NOT LIKE tingimus. Selge on see, et pĂ€rast seda UNION ALL hakkas tagasi tooma mĂ”ningad kirjed kaks korda â esialgu leitud aluse jĂ€rgi, ja siis veel kord â vastavalt esimese sĂ”na algusele selle reas. Limiteeritud, kĂ”ik teise aluses kĂŒsimuse kirjed vĂ”isid kattuda esimese kirjedega.
Mida teeb arendaja pĂ”hjuseta otsimise asemel?.. Pole kĂŒsimust!
- kordame suurust kahekordseks algsete valimite
- rakendame DISTINCT, et saada igast reast ainult ĂŒhekordseid eksemplare
WITH q AS (
( ... LIMIT + 10)
UNION ALL
( ... LIMIT + 10)
LIMIT + 10
)
SELECT DISTINCT
*
, (SELECT ...) alussĂŒgavus
FROM
q
LIMIT 10 OFFSET ;Seega on selge, et tulemus on lÔpuks ikka sama, kuid vÔimalus "lÀpata" teises aluses CTE-s on palju suurem, ja ilma selleta loetakse selgelt rohkem.
Aga see ei ole kĂ”ige halvem. Kuna arendaja palus valida STDEV mitte konkreetse, vaid kohe kĂ”ikide vĂ€ljade jĂ€rgi kirjed, siis pÀÀses automaatselt ka vĂ€li sub_query â aluskĂŒsimuse tulemus. NĂŒĂŒd, et tĂ€ita STDEV, pidi andmebaas tegema juba mitu 10 aluskĂŒsimust, vaid kĂ”ik + 10!
2.4: koostöö on kĂ”ige ĂŒle!
Nii nad elavad, arendajad â ei muretse, sest registris "teha" oluliseks vÀÀrtusteks N pideva edasiviimise juures igas jĂ€rgmises "lehekĂŒljas" oli kasutajal ilmselt kannatust puudu.
Kuni teise osakonna arendajad tulid nende juurde ja soovisid kasutada nii mugavat meetodit iteratiivseks otsimiseks â st vĂ”tame mingist valimist tĂŒkikese, filtreerime lisatingimuste jĂ€rgi, joonistame tulemuse, siis jĂ€rgmise tĂŒkikese (mida meie puhul saavutatakse N suurendamise teel), ja nii edasi, kuni tĂ€idame ekraani.
ĂhesĂ”naga, pĂŒĂŒtud eksemplaris N saavutas vÀÀrtusi peaaegu 17K, ja viimase 24 tunni jooksul tehti "ahelas" mitte vĂ€hem kui 4K sellist kĂŒsimust. Viimased neist skaneerisid julgelt 1GB mĂ€lu igal iteratsioonilâŠ
KokkuvÔttes

Allikas: habr.com
