Eelmise aasta detsembris sain VWO tugimeeskonnalt huvitava veateate. Ăhe analĂŒĂŒsiaruande laadimisaeg suure ettevĂ”tte kliendi jaoks nĂ€is olevat ĂŒlemÀÀra pikk. Kuna see kuulub minu vastutusalasse, keskendusin viivitamatult probleemi lahendamisele.
Eelalugu
Et oleks selge, millest jutt, rÀÀgin pisut VWO-st. See on platvorm, millega saab oma saitidel kĂ€ivitada erinevaid sihitud kampaaniaid: teostada A/B katseid, jĂ€lgida kĂŒlastajaid ja konversioone, teha mĂŒĂŒgivihje analĂŒĂŒse, kuvada soojuskaarte ja esitada kĂŒlastuse salvestusi.
Kuid kĂ”ige olulisem platvormi aspekt on aruannete koostamine. KĂ”ik eelnevalt mainitud funktsioonid on omavahel seotud. Suur hulk teavet oleks ettevĂ”tte klientidele lihtsalt kasutuks, kui see ei oleks esitatud analĂŒĂŒsimiseks mĂ”eldud platvormi kaudu.
Platvormi abil saab teha vabalt valitud pÀringu suure hulga andmete pealt. Siin on lihtne nÀide:
Kuva kĂ”ik klĂ”psud lehel "abc.com" PERIOODILT <kuupĂ€ev d1> KUNI <kuupĂ€ev d2> inimeste jaoks, kes kasutasid Chrome'i VĂI (olid Euroopas JA kasutasid iPhone'i)
Pange tÀhele loogika operaatorite olemust. Need on saadaval klientide pÀringu liideses, et luua piisavalt keerulisi pÀringuid andmestike saamiseks.
Aeglane pÀring
Kliendil, millest me rÀÀgime, oli soov teha midagi, mis intuitiivselt peaks töötama kiiresti:
Kuva kĂ”ik seansi salvestused kĂŒlastajate jaoks, kes on kĂ€inud mis tahes lehel URL-iga, mis sisaldab "/jobs".
Saidil oli tohutult liiklust, ja me sĂ€ilitasime ĂŒle miljoni unikaalse URL-aadressi vaid selle jaoks. Nad soovisid leida ĂŒsnagi lihtsa URL-malli, mis seondub nende Ă€rimudeliga.
Eeluurimine
Vaatame, mis toimub andmebaasis. Allpool on algne aeglane SQL-pÀring:
SELECT
count(*)
FROM
acc_{account_id}.urls as recordings_urls,
acc_{account_id}.recording_data as recording_data,
acc_{account_id}.sessions as sessions
WHERE
recording_data.usp_id = sessions.usp_id
AND sessions.referrer_id = recordings_urls.id
AND ( urls && array(select id from acc_{account_id}.urls where url ILIKE '%enterprise_customer.com/jobs%')::text[] )
AND r_time > to_timestamp(1542585600)
AND r_time < to_timestamp(1545177599)
AND recording_data.duration >=5
AND recording_data.num_of_pages > 0 ;Siin on ajad:
Eeldatav aeg: 1.480 ms TĂ€itmise aeg: 1431924.650 ms
PÀring lÀbis 150 tuhat rida. PÀringu planeerija nÀitas mÔningaid huvitavaid detaile, kuid mingeid ilmseid kitsaskohti ei olnud.
Vaatame pÀringut pÔhjalikumalt. Nagu nÀha, tegeleb see JOIN kolme tabeliga:
- sessions: seansi teabe kuvamiseks: brauser, kasutajaagent, riik jne.
- recording_data: salvestatud URL-id, lehekĂŒljed, visiitide kestus.
- urls: et vÀltida ÀÀrmiselt suurte URL-ide dubleerimist, sÀilitame need eraldi tabelis.
Pange tĂ€hele, et kĂ”ik meie tabelid on juba jagatud account_id. Seega elimineeritakse olukord, kus ĂŒhe ĂŒlemÀÀra suure konto tĂ”ttu tekivad probleemid ĂŒlejÀÀnutele.
TÔendite otsing
SĂŒvenedes, mĂ€rkame, et konkreetse pĂ€ringu korral on midagi valesti. Tasub tĂ€helepanu pöörata sellele reale:
urls && array(
select id from acc_{account_id}.urls
where url ILIKE '%enterprise_customer.com/jobs%'
)::text[]Esimene mĂ”te oli, et Ă€kki, kuna ILIKE kĂ”ikide nende pikkade URL-ide korral (meil on ĂŒle 1,4 miljoni unikaalse URL-aadressi, mis on selle konto jaoks kogutud) vĂ”ib jĂ”udlus langetada.
Aga ei â asi pole selles!
SELECT id FROM urls WHERE url ILIKE '%enterprise_customer.com/jobs%';
id
--------
...
(198661 real)
Aeg: 5231.765 msOtsingupĂ€ring mallide jĂ€rgi vĂ”tab ainult 5 sekundit. Mallide pĂ”hjal otsimine ĂŒle miljoni unikaalse URL-i osas pole kindlasti probleem.
JĂ€rgmine kahtlane kahtlane â mitu JOIN. VĂ”ib-olla nende ĂŒlemÀÀrane kasutamine viis aeglustumise juurde? Tavaliselt JOINâid on kĂ”ige ilmsemad kandidaadid jĂ”udlusprobleemide osas, aga ma ei uskunud, et meie juhtum oleks tavaline.
analytics_db=# SELECT
count(*)
FROM
acc_{account_id}.urls as recordings_urls,
acc_{account_id}.recording_data_0 as recording_data,
acc_{account_id}.sessions_0 as sessions
WHERE
recording_data.usp_id = sessions.usp_id
AND sessions.referrer_id = recordings_urls.id
AND r_time > to_timestamp(1542585600)
AND r_time < to_timestamp(1545177599)
AND recording_data.duration >=5
AND recording_data.num_of_pages > 0 ;
count
-------
8086
(1 rida)
Aeg: 147.851 msJa see ei olnud ka meie juhtum. JOINâid osutusid olema ĂŒsna kiired.
Kitsendame kahtlusi
Olin valmis hakkama pÀringut muutma, et saavutada vÔimalikult suur jÔudluse paranemine. Meie meeskond vÀlja töötas kaks peamist ideed:
- Kasuta EXISTS-i URLi alampĂ€ringu suhtes: Soovisime veel kord kontrollida, kas URL-i alampĂ€ringuga on probleeme. Ăks vĂ”imalus selleks on lihtsalt kasutada
EXISTS.EXISTStĂ”hususe suurendamiseks, kuna see lĂ”ppeb kohe, kui leiab ĂŒksiku rea vastavalt tingimusele.
VALI
count(*)
FROM
acc_{account_id}.urls as recordings_urls,
acc_{account_id}.recording_data as recording_data,
acc_{account_id}.sessions as sessions
WHERE
recording_data.usp_id = sessions.usp_id
AND ( 1 = 1 )
AND sessions.referrer_id = recordings_urls.id
AND (exists(select id from acc_{account_id}.urls where url ILIKE '%enterprise_customer.com/jobs%'))
AND r_time > to_timestamp(1547585600)
AND r_time =5
AND recording_data.num_of_pages > 0 ;
count
32519
(1 row)
Time: 1636.637 msJah, alampĂ€ring, kui see on ĂŒmber. EXISTS, muudab kĂ”ik ĂŒlikiireks. JĂ€rgmine loogiline kĂŒsimus on, miks pĂ€ringud koos JOIN-dega ja alampĂ€ringud töötavad eraldi kiiresti, aga koos on nad hirmsasti aeglased?
- Viime alampĂ€ringu CTE-sse : kui pĂ€ring töötab kiiresti iseenesest, saame kĂ”igepealt kiire tulemuse arvutada ja seejĂ€rel esitada selle pĂ”hikĂŒsimusele.
WITH matching_urls AS (
select id::text from acc_{account_id}.urls where url ILIKE '%enterprise_customer.com/jobs%'
)
SELECT
count(*) FROM acc_{account_id}.urls as recordings_urls,
acc_{account_id}.recording_data as recording_data,
acc_{account_id}.sessions as sessions,
matching_urls
WHERE
recording_data.usp_id = sessions.usp_id
AND ( 1 = 1 )
AND sessions.referrer_id = recordings_urls.id
AND (urls && array(SELECT id from matching_urls)::text[])
AND r_time > to_timestamp(1542585600)
AND r_time =5
AND recording_data.num_of_pages > 0;Aga isegi see oli endiselt vÀga aeglane.
Leidke sĂŒĂŒdlane
Kogu selle aja silme ees oli ĂŒks detail, millest ma pidevalt eemale tĂ”ukasin. Aga kuna rohkem ei olnud jÀÀnud, otsustasin sellele siiski pilgu heita. Ma rÀÀgin && operaator. Seni EXISTS lihtsalt parandas jĂ”udlust, && oli ainus veel alles jÀÀnud ĂŒhine tegur kĂ”igis aeglaste pĂ€ringute versioonides.
Vaadates , nĂ€eme, et && kasutatakse, kui on vaja leida ĂŒhiseid elemente kahe massiivi vahel.
Originaalses pÀringus on see:
AND ( urls && array(select id from acc_{account_id}.urls where url ILIKE '%enterprise_customer.com/jobs%')::text[] )See tĂ€hendab, et me teeme mustriotsingut meie URL-ide ĂŒle ja seejĂ€rel leiame lĂ”ike kĂ”igist URL-idest, millel on ĂŒhised salvestused. See on veidi segane, kuna 'urls' ei viita tabelile, mis sisaldab kĂ”iki URL-e, vaid veerule 'urls' tabelis recording_data.
Kasvatades kahtlusi &&, ĂŒritasin leida neile tĂ”estust pĂ€ringu plaani kaudu, mille genereeris EXPLAIN ANALYZE (mul oli juba salvestatud plaan, kuid mulle meeldib tavaliselt SQL-is eksperimenteerida pigem, kui pĂŒĂŒda mĂ”ista pĂ€ringute planeerijate lĂ€bipaistmatust).
Filter: ((urls && ($0)::text[]) AND (r_time > '2018-12-17 12:17:23+00'::timestamp with time zone) AND (r_time = '5'::double precision) AND (num_of_pages > 0))
Rows Removed by Filter: 52710Seal oli mitmeid filtreerimisridasid ainult &&. Mis tÀhendas, et see operatsioon polnud mitte ainult kulukas, vaid toimus mitu korda.
Kontrollisin seda, isoleerides tingimuse
SELECT 1
FROM
acc_{account_id}.urls as recordings_urls,
acc_{account_id}.recording_data_30 as recording_data_30,
acc_{account_id}.sessions_30 as sessions_30
WHERE
urls && array(select id from acc_{account_id}.urls where url ILIKE '%enterprise_customer.com/jobs%')::text[]See pÀring töötas aeglaselt. Kuna JOIN-d on kiire ja alampÀringud kiired, jÀi alles ainult && operaator.
Kuid see on vÔtmeoperatsioon. Me peame alati otsima pealt suures URL-de tabelis, et mustri pÔhjal otsida, ja me peame alati leidma lÔike. Me ei saa otsida URL-i salvestusi otse, kuna need on lihtsalt ID-d, mis viitavad urls.
Lahenduse poole liikudes
&& aeglaseks, kuna mÔlemad komplektid on tohutud. Operatsioon on suhteliselt kiire, kui asendan urls jÀrgnevaga { "http://google.com/", "http://wingify.com/" }.
Alustasin viisi otsimist, kuidas teha Postgresis kogumite lÔikeid ilma &&, kuid ei olnud erilisi edusamme.
LĂ”puks otsustasime lihtsalt probleemi isoleeritult lahendada: andke mulle kĂ”ik urls read, mille URL vastab mustrile. Ilma tĂ€iendavate tingimusteta oleks see âÂ
SELECT urls.url
FROM
acc_{account_id}.urls as urls,
(SELECT unnest(recording_data.urls) AS id) AS unrolled_urls
WHERE
urls.id = unrolled_urls.id AND
urls.url ILIKE '%jobs%'Asetame JOIN SĂŒntaksit kasutasin lihtsalt alampĂ€ringut ja lahti keritud recording_data.urls massiiv, et saaksime otse rakendada tingimust WHERE.
KÔige olulisem on see, et && kasutatakse selle kontrollimiseks, kas antud salvestusel on vastav URL-aadress. Veidi pilku keerates vÔib nÀha, et see operatsioon liigub massiivi (vÔi tabeli ridade) elementide kaudu ja peatub, kui tingimust (vastavust) tÀidetakse. Kas see ei tuleta meelde midagi? Ah, EXISTS.
Kuna recording_data.urls vĂ”ib viidata konteksti vĂ€ljastpoolt alampĂ€ringut, kui see juhtub, saame naasta tagasi meie vanale sĂ”brale EXISTS ja ĂŒmbritseda selle alampĂ€ringuga.
KokkuvÔttes saame lÔpliku optimeeritud pÀringu:
VALI
count(*)
FROM
acc_{account_id}.urls as recordings_urls,
acc_{account_id}.recording_data as recording_data,
acc_{account_id}.sessions as sessions
WHERE
recording_data.usp_id = sessions.usp_id
AND ( 1 = 1 )
AND sessions.referrer_id = recordings_urls.id
AND r_time > to_timestamp(1542585600)
AND r_time = 5
AND recording_data.num_of_pages > 0
AND EXIST(
SELECT urls.url
FROM
acc_{account_id}.urls as urls,
(SELECT unnest(urls) AS rec_url_id FROM acc_{account_id}.recording_data)
AS unrolled_urls
WHERE
urls.id = unrolled_urls.rec_url_id AND
urls.url ILIKE '%enterprise_customer.com/jobs%'
);
Ja lÔplik tÀitmise aeg Aeg: 1898.717 ms Kas on pidu pidada?!?
Aga mitte nii kiiresti! Esiteks peame kontrollima korrektsust. Olin vÀga ettevaatlik EXISTS optimeerimise suhtes, kuna see muudab loogikat varasemaks lÔpetamiseks. Me peame olema kindlad, et me ei ole lisanud pettumust valmistavat viga pÀringusse.
Lihtne kontroll seisnes selles, et tegin count(*) ja aeglaste ja kiirete pÀringute kohta suure hulga erinevate andme komplektide jaoks. SeejÀrel, vÀikese andmekogumi jaoks kontrollisin kÔigi tulemuste Ôigsust kÀsitsi.
KÔik kontrollid andsid pidevalt positiivseid tulemusi. Me oleme kÔik korda teinud!
TĂ”ukude Ăppetunnid
Selle looga saab vÔtta palju Ôppetunde:
- PÀringu plaanid ei rÀÀgi kogu lugu, kuid vÔivad anda vihjeid
- Peamised kahtlusalused ei ole alati tegelikud sĂŒĂŒdlased
- Aeglaseid pÀringuid saab jagada, et kitsaskohti isoleerida
- Kaugel ei ole kÔik optimeerimised oma olemuselt reduktiivsed
- Kasutamine
EXIST, kus see on vÔimalik, vÔib viia oluliseks jÔudluse kasvuks
KokkuvÔte
Oleme liikunud pĂ€ringu ajast ~24 minutist 2 sekundini â vĂ€ga tĂ”sine jĂ”udluse kasv! Kuigi see artikkel on suur, toimusid kĂ”ik meie eksperimendid ĂŒhe pĂ€eva jooksul ja nende teostamiseks ja testimiseks kulus umbes 1,5 kuni 2 tundi.
SQL on imeline keel, kui mitte karta seda, vaid proovida mÔista ja kasutada. HÀid teadmisi, kuidas SQL pÀringud toimuvad, kuidas andmebaasid genereerivad pÀringuplaanid, kuidas indeksid töötavad ja lihtsalt andmekoguse kohta, millega tegelete, saate pÀringute optimeerimises vÀga hÀsti hakkama. Samuti on oluline jÀtkata erinevate lÀhenemiste testimist ja aeglaselt probleeme lahendada, leides kitsaskohad.
Parim osa selliste tulemuste saavutamisel on mĂ€rgatav kiirusetĂ”us â kui raport, mis varem ei launud, launeb nĂŒĂŒd peaaegu koheselt.
Eriline tĂ€nu minu kolleegidele meeskonnast Adithya Mishrale, Adithya Gaurile ja ajurĂŒnnakute eest ja Dinkar Pandirile selle eest, et leidis meie lĂ”pppĂ€ringus olulise vea, enne kui me sellega lĂ”plikult hĂŒvasti jĂ€tsime!
Allikas: habr.com
