Az orszĂĄg összes Ă©rtĂ©kesĂtĂ©si irodĂĄjĂĄnak ezrei rögzĂtik a naponta több tĂzezer kapcsolattartĂĄst â a potenciĂĄlis vagy mĂĄr velĂŒnk dolgozĂł ĂŒgyfelekkel folytatott kommunikĂĄciĂł tĂ©nyeit. Ehhez az ĂŒgyfĂ©lhez elĆször meg kell talĂĄlni, Ă©s lehetĆleg nagyon gyorsan. Ez ĂĄltalĂĄban a nĂ©v alapjĂĄn törtĂ©nik.
EzĂ©rt nem meglepĆ, hogy amikor Ășjra megvizsgĂĄltam a ânehĂ©zâ lekĂ©rdezĂ©seket az egyik legterheltebb adatbĂĄzisunkban â a sajĂĄt , felfedeztem a âtopâ-on a nĂ©v alapĂș âgyorsâ keresĂ©shez az ĂŒzleti kĂĄrtyĂĄkhoz.
SĆt, a tovĂĄbbi nyomozĂĄs Ă©rdekes pĂ©ldĂĄt törtĂ©nt elĆször az optimalizĂĄlĂĄs, majd a teljesĂtmĂ©nyromlĂĄs lekĂ©rdezĂ©s teljesĂtmĂ©nyĂ©nek fokozatos javĂtĂĄsa sorĂĄn, melynek sorĂĄn több csapat mƱködött, mindegyik a lehetĆ legjobb szĂĄndĂ©kkal.
0: mit akart a felhasznĂĄlĂł
[KDPV ]
Mit is jelent ĂĄltalĂĄban a felhasznĂĄlĂł szĂĄmĂĄra a âgyorsâ keresĂ©s a nĂ©v alapjĂĄn? Szinte soha nem jelent âĆszinteâ keresĂ©st egy substringre, mint pĂ©ldĂĄul ... LIKE '%rĂłzsa%' â hiszen akkor a talĂĄlatok közĂ© nem csak 'RĂłzsalia' Ă©s 'Bolt RĂłzsa', hanem 'GrĂłzsa' Kapcsolja össze sajĂĄt domainjĂ©t 'TĂ©lapĂł hĂĄzarĂłzsa'.
A felhasznĂĄlĂł pedig a hĂĄztartĂĄsi szinten azt Ă©rti, hogy biztosĂtani fogja neki a szĂł elejĂ©re törtĂ©nĆ keresĂ©st a nĂ©vben, Ă©s relevĂĄnsabbnak fogja mutatni azt, ami kezdĆdik a beĂrt kifejezĂ©ssel. ĂS ezt meg fogja tenni gyakorlatilag azonnal â a substring bevitelĂ©nĂ©l.
1: korlĂĄtozzuk a feladatot
AztĂĄn a felhasznĂĄlĂł biztosan nem fogja kĂŒlön beĂrni 'rĂłz bolt',hogy minden egyes szĂłt prefikszĂĄlva kelljen keresnie. Nem, a felhasznĂĄlĂłnak sokkal egyszerƱbb reagĂĄlni a legutolsĂł szĂł gyors javaslatĂĄra, mint szĂĄndĂ©kosan âhĂĄtrahagyniâ az elĆzĆket â nĂ©zd meg, hogy mƱködik ez bĂĄrmely keresĆnĂ©l.
AlapvetĆen, kell a követelmĂ©nyek megfogalmazĂĄsa â a megoldĂĄs több mint fele. NĂ©ha a felhasznĂĄlĂĄsi eset alapos elemzĂ©se .
Mit tesz egy elvont fejlesztĆ?
1.0: kĂŒlsĆ keresĆmotor
Jaj, a keresĂ©s bonyolult, valĂłjĂĄban nem is akarjuk ezzel foglalkozni â adjuk ĂĄt a devopsnak! Hadd telepĂtsenek nekĂŒnk egy kĂŒlsĆ keresĆ rendszert az adatbĂĄzisunkkal, mint pĂ©ldĂĄul Sphinx, ElasticSearch,âŠ
MƱködĆ, bĂĄr munkaigĂ©nyes a szinkronizĂĄciĂł Ă©s azonnali vĂĄltozĂĄsok szempontjĂĄbĂłl. De a mi esetĂŒnkben nem, mivel a keresĂ©s minden ĂŒgyfĂ©l esetĂ©ben csak az Ć fiĂłkjĂĄnak adatai között törtĂ©nik. Az adatok elĂ©g jelentĆs vĂĄltozĂ©konysĂĄggal bĂrnak â Ă©s ha most a menedzser lĂ©trehozott egy kĂĄrtyĂĄt, 'RĂłzsa Bolt', akkor 5-10 mĂĄsodperc mĂșlva mĂĄr emlĂ©kezhet arra, hogy elfelejtette megadni az emailt, Ă©s szeretnĂ© megtalĂĄlni Ă©s javĂtani azt.
EzĂ©rt â nĂ©zzĂŒk meg, hogyan kereshetĂŒnk "közvetlenĂŒl a adatbĂĄzis szerint". SzerencsĂ©re a PostgreSQL lehetĆvĂ© teszi szĂĄmunkra, Ă©s nem egyetlen megoldĂĄssal â ezeket fogjuk megvizsgĂĄlni.
1.1: "Ćszinte" rĂ©szstring
Kapcsolódjunk a "részstring" szóra. Hiszen pontosan az indexelésre a részstring (és akår regulåris kifejezések!) alapjån van egy nagyszerƱ modulunk! Csak aztån helyesen kell majd rendezni.
Próbåljunk meg egyszerƱség kedvéért egy ilyen tåblåt létrehozni:
CREATE TABLE firms(
id
serial
PRIMARY KEY
, name
text
);FeltöltjĂŒk 7,8 milliĂł valĂłs szervezet rekordjĂĄval Ă©s indexeljĂŒk:
CREATE EXTENSION pg_trgm;
CREATE INDEX ON firms USING gin(lower(name) gin_trgm_ops);KezdjĂŒk a rĂ©szstring keresĂ©sĂ©vel, keressĂŒk az elsĆ 10 rekordot:
SELECT
*
FROM
firms
WHERE
lower(name) ~ ('(^|s)' || 'rĂłzsa')
ORDER BY
lower(name) ~ ('^' || 'rĂłzsa') DESC -- elĆször a "kezdetben ĂĄllĂł"
, lower(name) -- a többi åbécé sorrendben
LIMIT 10;

HĂĄt, ilyen... 26ms, 31MB olvasott adatok Ă©s több mint 1,7K szƱrt rekord â a 10 keresett közĂŒl. A költsĂ©gek tĂșl magasak, nem lehetne hatĂ©konyabb megoldĂĄs?
1.2: keresĂ©s szöveg alapĂș? ez FTS!
ValĂłban, a PostgreSQL nagyon erĆs (Full Text Search) mechanizmust biztosĂt, beleĂ©rtve a prefix keresĂ©st is. NagyszerƱ megoldĂĄs, mĂ©g kiterjesztĂ©seket sem szĂŒksĂ©ges telepĂteni! NĂ©zzĂŒk meg:
CREATE INDEX ON firms USING gin(to_tsvector('simple'::regconfig, lower(name)));SELECT
*
FROM
firms
WHERE
to_tsvector('simple'::regconfig, lower(name)) @@ to_tsquery('simple', 'rĂłzsa:*')
ORDER BY
lower(name) ~ ('^' || 'rĂłzsa') DESC
, lower(name)
LIMIT 10; 
Itt segĂtett nekĂŒnk egy kicsit a lekĂ©rdezĂ©s pĂĄrhuzamosĂtĂĄsa, amely megfelezte az idĆt 11ms. Ăs 1,5-ször kevesebbet kellett olvasnunk â mindössze 20MB. Itt a kevesebb jobb, hiszen minĂ©l nagyobb mennyisĂ©get olvasunk ki, annĂĄl nagyobb a cache miss lehetĆsĂ©ge, Ă©s minden egyes felesleges olvasott adatlap a lemezrĆl potenciĂĄlisan "lassĂtja" a lekĂ©rdezĂ©st.
1.3: vĂ©gĂŒl is LIKE?
Az elĆzĆ lekĂ©rdezĂ©s nagyszerƱ, de ha naponta szĂĄzezerszer meghĂvjĂĄk, akkor mĂĄr 2TB olvadt adatok. Legjobb esetben is a memĂłriĂĄbĂłl, de ha nem vagyunk szerencsĂ©sek, akkor a lemezrĆl. SzĂłval prĂłbĂĄljuk meg kisebbre venni.
EmlĂ©kezzĂŒnk, hogy a felhasznĂĄlĂł mit szeretne lĂĄtni elĆször a "...ra kezdĆdĆket". De hĂĄt ez tisztĂĄn van segĂtsĂ©gĂ©vel text_pattern_ops! Ăs csak akkor, ha "nem elĂ©g" 10 keresett bejegyzĂ©sig, akkor a FTS-keresĂ©ssel kell folytatnunk az olvasĂĄst:
INDĂTS INDEX A firms(lower(name) text_pattern_ops);VĂLASZD
*
BĆVĂTSD
firms
HOL
lower(name) LIKE ('rĂłzsa' || '%')
LIMIT 10; 
KivĂĄlĂł mutatĂłk â mindössze 0.05ms Ă©s kissĂ© több mint 100KB elolvasva! Csak elfelejtettĂŒk a nevek szerinti rendezĂ©st, hogy a felhasznĂĄlĂł ne tĂ©vedjen el az eredmĂ©nyek között:
VĂLASZD
*
BĆVĂTSD
firms
HOL
lower(name) LIKE ('rĂłzsa' || '%')
RENDEZĂS
lower(name)
LIMIT 10; 
Ă, mĂĄr nem nĂ©z ki olyan jĂłl â Ășgy tƱnik, hogy van index, de a rendezĂ©s mellette elszĂĄll... Persze, mĂĄr sokkal hatĂ©konyabb, mint az elĆzĆ vĂĄltozat, de...
1.4: "finomĂtsuk a fĂĄjlt"
De van egy index, amely lehetĆvĂ© teszi a tartomĂĄny szerinti keresĂ©st Ă©s a rendezĂ©st is â normĂĄl btree!
INDĂTS INDEX A firms(lower(name));Csak a lekĂ©rdezĂ©st "kĂ©zzel" kell összerakni:
VĂLASZD
*
BĆVĂTSD
firms
HOL
lower(name) >= 'rĂłzsa' ĂS
lower(name) <= ('rózsa' || chr(65535)) -- UTF8 esetén, egybåjtosoknak - chr(255)
RENDEZĂS
lower(name)
LIMIT 10; 
KivĂĄlĂł â Ă©s a rendezĂ©s mƱködik, Ă©s az erĆforrĂĄs-felhasznĂĄlĂĄs "mikroszkopikus" maradt, ezerszer hatĂ©konyabb, mint a "tisztĂĄn" FTS! MĂĄr csak egy összetett lekĂ©rdezĂ©st kell kĂ©szĂteni:
(
VĂLASZD
*
BĆVĂTSD
firms
HOL
lower(name) >= 'rĂłzsa' ĂS
lower(name) <= ('rózsa' || chr(65535)) -- UTF8, egybåjtos kódolås esetén - chr(255)
RENDEZĂS
lower(name)
LIMIT 10
)
UNION ALL
(
VĂLASZD
*
BĆVĂTSD
firms
HOL
to_tsvector('simple'::regconfig, lower(name)) @@ to_tsquery('simple', 'rĂłzsa:*') ĂS
lower(name) NOT LIKE ('rĂłzsa' || '%') -- "...ra kezdĆdĆket" mĂĄr megtalĂĄltuk
RENDEZĂS
lower(name) ~ ('^' || 'rĂłzsa') DESC -- ugyanazt a rendezĂ©st hasznĂĄljuk, hogy NE lĂ©pjĂŒnk ĂĄt a btree-indexen
, lower(name)
LIMIT 10
)
LIMIT 10; Megjegyzem, hogy a mĂĄsodik alkĂ©rdezĂ©s csak akkor fut le ha az elsĆ kevesebb elvĂĄrt sorok szĂĄmĂĄt adta vissza. ErrĆl a lekĂ©rdezĂ©sek optimalizĂĄlĂĄsĂĄrĂłl korĂĄbban mĂĄr Ărtam LIMIT Igen, most mĂĄr egyidĆben van btree Ă©s gin a tĂĄblĂĄn, de statisztikailag kiderĂŒlt, hogy .
. TehĂĄt ezekkel a hĂresen elĆre meghatĂĄrozott tipikus korlĂĄtokkal a feladatnĂĄl a szerver erĆforrĂĄs-felhasznĂĄlĂĄsĂĄt szinte ezerszer csökkentettĂŒk! 1.5*: megoldjuk anĂ©lkĂŒl a finomĂtĂłtFent
Megjegyzem, hogy
Fent LIKE Zavaradtuk a helytelen rendezĂ©s hasznĂĄlatĂĄba. De a USING operĂĄtor megadĂĄsĂĄval ârĂĄ tudjuk vezetni az igaz Ăștraâ:
AlapĂ©rtelmezĂ©s szerint Ă©rtendĆ
ASC. Ezen kĂvĂŒl megadhatunk egy specifikus rendezĆ operĂĄtor nevĂ©t a lekĂ©rdezĂ©sbenUSING. A rendezĆ operĂĄtornak egy âkisebbâ vagy ânagyobbâ B-fa operĂĄtoral kell lennie.ASCĂĄltalĂĄban egyenĂ©rtĂ©kƱUSING <Ă©sDESCĂĄltalĂĄban egyenĂ©rtĂ©kƱUSING >.
A mi esetĂŒnkben a âkisebbâ a ~<~:
SELECT
*
FROM
firms
WHERE
lower(name) LIKE ('rĂłzsa' || '%')
ORDER BY
lower(name) USING ~<~
LIMIT 10; 
2: ahogy a lekĂ©rdezĂ©sek âmegromlanakâ
Most hagyjuk, hogy a lekĂ©rdezĂ©sĂŒnk âĂ©rjenâ fĂ©l Ă©vig egy Ă©vet, Ă©s meglepetĂ©sĂŒnkre Ășjra felfedezzĂŒk a âtopâ rĂ©szen a napi összesĂtett âmemĂłria feltornĂĄszĂĄsâ mutatĂłkkal (buffers shared hit) a 5.5TB â tehĂĄt mĂ©g több, mint amennyi eredetileg volt.
Nem, termĂ©szetesen, a vĂĄllalkozĂĄsunk is nĆtt, Ă©s a terhelĂ©s is növekedett, de nem ennyire! TehĂĄt itt valami nem stimmel â nĂ©zzĂŒk meg közelebbrĆl.
2.1: a lapozĂĄs szĂŒletĂ©se
Egy ponton a mĂĄsik fejlesztĆcsapat meg akarta valĂłsĂtani, hogy a gyors alsĂłkeresĂ©sbĆl âugraniâ lehessen a tĂĄrolĂłba az ugyanazokkal, de kibĆvĂtett eredmĂ©nyekkel. De milyen tĂĄrolĂł lapozĂĄs nĂ©lkĂŒl? Csatlakoztassuk!
( ... LIMIT + 10)
UNION ALL
( ... LIMIT + 10)
LIMIT 10 OFFSET ;EzutĂĄn a fejlesztĆ könnyedĂ©n prezentĂĄlhatta a keresĂ©si eredmĂ©nyek listĂĄjĂĄt âtĂpusĂș-lapozĂĄsosâ alĂĄmerĂtĂ©ssel.
Persze, valĂłjĂĄban minden egyes következĆ adatoldalhoz egyre nagyobb mennyisĂ©gƱ adatot olvasunk el (az összes az elĆzĆ alkalommal, amelyet eldobunk, plusz a szĂŒksĂ©ges âfarokâ) â tehĂĄt ez egyĂ©rtelmƱ antipattern. Ami helyesebb lenne â a keresĂ©st a felhasznĂĄlĂłi felĂŒleten megjegyzett kulcsrĂłl indĂtani, de errĆl majd egy mĂĄsik alkalommal.
2.2: vĂĄgyakozĂĄs az egzotikumra
Egy ponton a fejlesztĆ vĂĄgyott rĂĄ hogy szĂnesĂtse az eredmĂ©nyhalmazt adatokkal egy mĂĄsik tĂĄblĂĄzatbĂłl, ezĂ©rt a teljes korĂĄbbi lekĂ©rdezĂ©s CTE-be kerĂŒlt:
WITH q AS (
...
LIMIT + 10
)
SELECT
*
, (SELECT ...) sub_query -- egy lekérdezés a kapcsolódó tåblåhoz
FROM
q
LIMIT 10 OFFSET ;Ăs mĂ©g Ăgy is â nem rossz, mivel a beĂĄgyazott lekĂ©rdezĂ©st csak a 10 visszaadott rekordra szĂĄmoljĂĄk ki, ha nem lenneâŠ
2.3: DISTINCT értelmetlen és könyörtelen
Valahol ebben a fejlĆdĂ©s sorĂĄn a 2. allekĂ©rdezĂ©sbĆl kivilĂĄgosodott NOT LIKE a feltĂ©tel. NyilvĂĄnvalĂł, hogy ezutĂĄn UNION ALL kezdett visszaadni bizonyos rekordokat duplĂĄn â elĆször a sorok elejĂ©n talĂĄlhatĂłakat, majd ismĂ©t az ezen sor elsĆ szavĂĄnĂĄl. A hatĂĄron belĂŒl a mĂĄsodik lekĂ©rdezĂ©s összes bejegyzĂ©se megegyezhetett az elsĆ bejegyzĂ©seivel.
Mit csinĂĄl a fejlesztĆ a hiba keresĂ©se helyett?.. Nem kĂ©rdĂ©s!
- kĂ©tszeresĂ©re bĆvĂtjĂŒk a mĂ©retet forrĂĄsmintĂĄk
- alkalmazzuk a DISTINCT-ot, hogy csak egyedi példånyok maradjanak meg az egyes sorokból
WITH q AS (
( ... LIMIT + 10)
UNION ALL
( ... LIMIT + 10)
LIMIT + 10
)
SELECT DISTINCT
*
, (SELECT ...) sub_query
FROM
q
LIMIT 10 OFFSET ;TehĂĄt egyĂ©rtelmƱ, hogy a vĂ©geredmĂ©ny pont ugyanaz, de a mĂĄsodik CTE lekĂ©rdezĂ©s âkiugrĂĄsĂĄnakâ esĂ©lye jelentĆsen megnĆtt, Ă©s ezen kĂvĂŒl is, nyilvĂĄnvalĂłan többet olvas..
De ez mĂ©g nem a legrosszabb. Mivel a fejlesztĆ azt kĂ©rte, hogy ne specifikus, hanem az összes mezĆ alapjĂĄn vĂĄlogassunk DISTINCT az bejegyzĂ©sek között, Ăgy automatikusan bekerĂŒlt a sub_query mezĆ is â a lekĂ©rdezĂ©s eredmĂ©nye. Most a vĂ©grehajtĂĄs Ă©rdekĂ©ben , a rendszernek mĂĄr DISTINCTnem 10, hanem mind + 10 lekĂ©rdezĂ©st kellett vĂ©grehajtania. 2.4: a kooperĂĄciĂł mindennĂ©l fontosabb!!
Ăgy Ă©ltek a fejlesztĆk â nem panaszkodtak, mert a nyilvĂĄntartĂĄs âfinomhangolĂĄsaâ Ă©rdekĂ©ben a kritikus N Ă©rtĂ©kek elĂ©rĂ©sĂ©hez a következĆ âoldalâ megjelenĂtĂ©sekor a felhasznĂĄlĂłnak nyilvĂĄnvalĂłan nem volt elĂ©g tĂŒrelme.
AmĂg nem jöttek a mĂĄsik osztĂĄly fejlesztĆi, Ă©s nem akartĂĄk hasznĂĄlni ezt a kĂ©nyelmes mĂłdszert
az iteratĂv keresĂ©shez. â azaz veszĂŒnk egy darabot a mintĂĄbĂłl, szƱrjĂŒk tovĂĄbbi feltĂ©telek szerint, megrajzoljuk az eredmĂ©nyt, majd a következĆ darabot (ami az esetĂŒnkben N növelĂ©sĂ©vel Ă©rhetĆ el), Ă©s Ăgy folytatjuk, amĂg ki nem töltjĂŒk a kĂ©pernyĆt. ĂsszessĂ©gĂ©ben a megfogott pĂ©ldĂĄnyban
N csaknem 17K-ra nĆtt, Ă©s mindössze egy nap alatt âlĂĄncbanâ nem kevesebb mint 4K ilyen lekĂ©rdezĂ©st hajtottak vĂ©gre. Az utolsĂłkat mĂĄr bĂĄtran vizsgĂĄltĂĄk1 GB memĂłria minden iterĂĄciĂłnĂĄl. Ezer Ă©rtĂ©kesĂtĂ©si menedzser az orszĂĄg irodĂĄibĂłl naponta tucatnyi ezer kapcsolatot rögzĂt a CRM rendszerĂŒnkben â a kommunikĂĄciĂł tĂ©nyĂ©t.âŠ
Ăsszesen

ForrĂĄs: habr.com
