Am leschte Dezember krut ech en interessante Feelerbericht vum VWO Support Team. D'LaaschtzÀit fir ee vun den Analyseberichter fir e groussen Entreprise Client schéngt verbueden ze sinn. A well dëst mÀi Verantwortungsgebitt ass, hunn ech mech direkt op d'Léisung vum Problem konzentréiert.
Virgeschicht
Fir kloer ze maachen wat ech schwĂ€tzen, wĂ€ert ech Iech e bĂ«ssen iwwer VWO erzielen. DĂ«st ass eng Plattform mat dĂ€r Dir verschidde geziilte Kampagnen op Ăre WebsĂ€iten lancĂ©iere kĂ«nnt: A/B Experimenter maachen, Besucher a Konversiounen verfollegen, de Verkafstriichter analysĂ©ieren, HĂ«tztkaarten affichĂ©ieren a Visiteopnamen spillen.
Awer déi wichtegst Saach iwwer d'Plattform ass Berichterstattung. All déi uewe genannte Funktiounen sinn matenee verbonnen. A fir Firmeclienten wier eng enorm Quantitéit un Informatioun einfach nëtzlos ouni eng mÀchteg Plattform déi se an analytesch Form presentéiert.
Mat der Plattform kënnt Dir eng zoufÀlleg Ufro op engem groussen Datesaz maachen. Hei ass en einfacht Beispill:
All Klicks op der SÀit "abc.com" VUN <Datum d1> BIS <Datum d2> fir Leit weisen, déi Chrome benotzt hunn ODER (an Europa waren AN benotzt hunn) iPhone)
Opgepasst op Boolschen Bedreiwer. Si sinn verfĂŒgbar fir Clienten an der Ufro-Interface fir arbitrĂ€r komplex Ufroen ze maachen fir Proben ze krĂ©ien.
Lues Ufro
De Client a Fro huet probéiert eppes ze maachen wat intuitiv séier sollt funktionnéieren:
Weist all Sessiounsrecords fir Benotzer déi all SÀit besicht hunn mat enger URL mat "/jobs"
Dëse Site hat eng Tonne Traffic a mir hunn iwwer eng Millioun eenzegaarteg URLen gelagert just dofir. A si wollten eng zimlech einfach URL-Schabloun fannen déi mat hirem GeschÀftsmodell verbonnen ass.
Virleefeg EnquĂȘte
Loosst eis kucken wat an der Datebank lass ass. Drënner ass déi ursprénglech lues SQL Ufro:
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 ;An hei sinn d'ZĂ€iten:
Geplangte ZÀit: 1.480 ms AusféierungszÀit: 1431924.650 ms
D'Ufro ass 150 Tausend Reihen gekrabbelt. De Query Planner huet e puer interessant Detailer gewisen, awer keng offensichtlech Flaschenhals.
Loosst eis d'Demande weider studéieren. Wéi Dir kënnt gesinn, mécht hien JOIN drÀi Dëscher:
- Manifestatiounen: Sessiounsinformatioun ze weisen: Browser, User Agent, Land, asw.
- recording_data: opgeholl URLen, SĂ€iten, Dauer vun de Visiten
- URLen: Fir ze vermeiden datt extrem grouss URLen duplizéieren, spÀichere mir se an enger separater Tabell.
Notéiert och datt all eis Dëscher scho opgedeelt sinn account_id. Esou gëtt eng Situatioun ausgeschloss, wou ee besonnesch grousse Kont Problemer fir anerer mécht.
Sich no Hiweiser
Bei enger méi genauer Inspektioun gesi mer datt eppes mat enger bestëmmter Ufro falsch ass. Et ass derwÀert dës Linn méi no ze kucken:
urls && array(
select id from acc_{account_id}.urls
where url ILIKE '%enterprise_customer.com/jobs%'
)::text[]Den éischte Gedanken war, datt vlÀicht well ILIKE op all dës laang URLen (mir hunn iwwer 1,4 Milliounen eenzegaarteg URLen gesammelt fir dëse Kont) Leeschtung kann leiden.
Awer nee, dat ass net de Punkt!
SELECT id FROM urls WHERE url ILIKE '%enterprise_customer.com/jobs%';
id
--------
...
(198661 rows)
Time: 5231.765 msD'Schabloun Sich Ufro selwer dauert nëmme 5 Sekonnen. Sich no engem Muster an enger Millioun eenzegaarteg URLen ass kloer kee Problem.
Déi nÀchst VerdÀchteg op der Lëscht sinn e puer JOIN. VlÀicht huet hir Iwwerverbrauch de VerlÀngerung verursaacht? Normalerweis JOIN's sinn déi offensichtlechst Kandidate fir Leeschtungsproblemer, awer ech hunn net gegleeft datt eise Fall typesch war.
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 row)
Time: 147.851 msAn dat war och net eise Fall. JOINEt huet sech zimlech séier erausgestallt.
De Krees vu VerdÀchtegen verklengert
Ech war prett fir d'Ufro ze Ànneren fir all méiglech Leeschtungsverbesserungen z'erreechen. Meng Equipe an ech hunn 2 Haaptideeën entwéckelt:
- Benotzt EXISTS fir Subquery URL: Mir wollten nach eng KĂ©ier kucken, ob et Problemer mat der Ănnerquery fir d'URLen gĂ«tt. Ee Wee fir dĂ«st z'erreechen ass einfach ze benotzen
EXISTS.EXISTSd'Performance staark verbesseren well se direkt ophÀlt soubal et déi eenzeg String fënnt déi d'Konditioun entsprécht.
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 ( 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 < to_timestamp(1549177599)
AND recording_data.duration >=5
AND recording_data.num_of_pages > 0 ;
count
32519
(1 row)
Time: 1636.637 msGutt, jo. Subquery wann agewĂ©ckelt EXISTS, mĂ©cht alles super sĂ©ier. DĂ©i nĂ€chst logesch Fro ass firwat d'Demande mat JOIN-ami an d'Ănnerquery selwer si sĂ©ier individuell, awer si schrecklech lues zesummen?
- Beweegt d'Subquery op den CTE : Wann d'Ufro eleng séier ass, kënne mir einfach dat séier Resultat als éischt berechnen an et dann un d'Haaptufro ubidden
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 < to_timestamp(1545107599)
AND recording_data.duration >=5
AND recording_data.num_of_pages > 0;Mee et war nach ganz lues.
Den TĂ€ter ze fannen
DĂ©i ganz ZĂ€it huet eng kleng Saach virun mengen Ae geblĂ©nkt, dĂ©i ech dauernd op der SĂ€it gebastelt hunn. Mee well et soss nĂ€ischt mĂ©i war, hunn ech dĂ©cidĂ©iert hatt och ze kucken. Ech schwĂ€tzen iwwer && Bedreiwer. Ăddi EXISTS just verbessert Leeschtung && war deen eenzege verbleiwen gemeinsame Faktor iwwer all Versioune vun der lueser Ufro.
Kucken op , mir gesinn dat && benotzt wann Dir gemeinsam Elementer tëscht zwee Arrays muss fannen.
An der ursprénglecher Ufro ass dëst:
AND ( urls && array(select id from acc_{account_id}.urls where url ILIKE '%enterprise_customer.com/jobs%')::text[] )Wat heescht datt mir eng Muster Sich op eise URLen maachen, dann d'KrÀizung mat all den URLen mat gemeinsame Posts fannen. Dëst ass e bëssen konfus well "URL" hei net op d'Tabell bezitt déi all d'URLen enthÀlt, mee op d'"URL" Kolonn an der Tabell recording_data.
Mat wuessende Mësstrauen iwwer &&, Ech hu probéiert eng BestÀtegung fir si am Ufroplang ze fannen EXPLAIN ANALYZE (Ech hat schonn e Plang gespÀichert, awer ech si meeschtens méi bequem an SQL ze experimentéieren wéi d'Opazitéit vun den Ufroplaner ze verstoen).
Filter: ((urls && ($0)::text[]) AND (r_time > '2018-12-17 12:17:23+00'::timestamp with time zone) AND (r_time < '2018-12-18 23:59:59+00'::timestamp with time zone) AND (duration >= '5'::double precision) AND (num_of_pages > 0))
Rows Removed by Filter: 52710Et waren e puer Linnen vun Filtere nëmmen aus &&. Wat bedeit datt dës Operatioun net nëmmen deier war, mÀ och e puer Mol gemaach gouf.
Ech hunn dëst getest andeems ech d'Konditioun isoléiert
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[]Dës Ufro war lues. Well déi JOIN-s sinn séier an subqueries si séier, déi eenzeg Saach lénks war && Bedreiwer.
Dëst ass just eng Schlësseloperatioun. Mir mussen ëmmer déi ganz ënnerierdesch Tabell vun URLen sichen fir no engem Muster ze sichen, a mir mussen ëmmer KrÀizungen fannen. Mir kënnen net direkt no URL-Records sichen, well dës sinn nëmmen IDen déi op referenzéieren urls.
Um Wee fir eng Léisung
&& lues well béid SÀtz enorm sinn. D'Operatioun wÀert relativ séier sinn wann ech ersetzen urls op { "http://google.com/", "http://wingify.com/" }.
Ech hunn ugefaang no engem Wee ze sichen fir Set KrÀizung am Postgres ze maachen ouni ze benotzen &&, awer ouni vill Erfolleg.
Um Enn hu mir décidéiert de Problem just an Isolatioun ze léisen: gitt mir alles urls Linnen fir déi d'URL mam Muster entsprécht. Ouni zousÀtzlech Konditioune wÀert et sinn -
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%'Amplaz ewechzehuelen JOIN Syntax Ech hunn just eng Ănnerquery benotzt an erweidert recording_data.urls Array sou datt Dir d'Konditioun direkt an WHERE.
Dat Wichtegst hei ass dat && benotzt fir ze kontrolléieren ob e bestëmmten Entrée eng passende URL enthÀlt. Wann Dir e bëssen squint, Dir kënnt gesinn, datt dës Operatioun duerch d'Elementer vun enger Array (oder Zeile vun engem Dësch) bewegt a stoppt wann eng Konditioun (Match) erfëllt ass. Erënnert Iech un nÀischt? jo, EXISTS.
Well op recording_data.urls kĂ«nne vu baussent dem Ănnerquery-Kontext referenzĂ©iert ginn, wann dat passĂ©iert kĂ«nne mir op eisen ale FrĂ«nd zrĂ©ckfalen EXISTS a wĂ©ckelt d'Subquery domat.
Alles zesumme setzen, kréie mir déi lescht optimiséiert Ufro:
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 ( 1 = 1 )
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
AND EXISTS(
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%'
);
An déi lescht LeadzÀit Time: 1898.717 ms ZÀit fir ze feieren?!?
Net sou sĂ©ier! Als Ă©ischt musst Dir d'Korrektheet kontrollĂ©ieren. Ech war extrem verdĂ€chteg iwwer EXISTS OptimisĂ©ierung wĂ©i et d'Logik Ă€nnert fir mĂ©i frĂ©i opzehalen. Mir musse sĂ©cher sinn datt mir keen net offensichtleche Feeler op d'Ufro bĂ€igefĂŒĂŒgt hunn.
En einfachen Test war ze lafen count(*) op bĂ©id lues a sĂ©ier Ufroe fir eng grouss Zuel vu verschiddenen Datesets. Dann, fir e klengen Ănnerdeel vun den DonnĂ©eĂ«n, hunn ech manuell verifizĂ©iert datt all Resultater richteg waren.
All Tester hunn konsequent positiv Resultater ginn. Mir hunn alles fixéiert!
Lektioune geléiert
Et gi vill Lektioune vun dëser Geschicht ze léieren:
- Query PlÀng erzielen net déi ganz Geschicht, awer si kënne Hiweiser ubidden
- Déi HaaptverdÀchteg sinn net ëmmer déi richteg TÀter
- Luesen Ufroe kënnen opgedeelt ginn fir Flaschenhalsen ze isoléieren
- Net all Optimisatiounen sinn reduktiv an der Natur
- Benotzt
EXIST, wa méiglech, kann zu dramateschen Erhéijunge vun der Produktivitéit féieren
Konklusioun
Mir sinn vun enger UfrozÀit vu ~ 24 Minutten op 2 Sekonnen gaang - zimmlech eng bedeitend Leeschtungserhéijung! Och wann dësen Artikel grouss erauskomm ass, sinn all d'Experimenter, déi mir gemaach hunn, an engem Dag geschitt, an et gouf geschat datt se tëscht 1,5 an 2 Stonnen fir Optimisatiounen an Tester gedauert hunn.
SQL ass eng wonnerbar Sprooch wann Dir keng Angscht dofir hutt, awer probéiert se ze léieren an ze benotzen. Andeems Dir e gutt VerstÀndnis hutt wéi SQL Ufroen ausgefouert ginn, wéi d'Datebank UfroplÀng generéiert, wéi Indizes funktionnéieren, an einfach d'Gréisst vun den Donnéeën mat deem Dir beschÀftegt, kënnt Dir ganz erfollegrÀich sinn fir Ufroen ze optimiséieren. Et ass awer glÀich wichteg fir weider verschidden Approchen ze probéieren an de Problem lues a lues ofzebriechen, d'Flaschenhals ze fannen.
Dee beschten Deel iwwer d'Resultater wéi dëst z'erreechen ass déi merkbar, siichtbar Geschwindegkeetsverbesserung - wou e Bericht dee virdru net emol lued elo bal direkt lued.
Besonnesche Merci un meng Komeroden um Kommando vun Aditya Mishra, Aditya Gauru Đž fir Brainstorming an Dinkar Pandir fir e wichtege Feeler an eiser leschter Ufro ze fannen ier mer endlech Ăddi gesot hunn!
Source: will.com
