Në raport paraqiten disa qasje që lejojnë të monitoroni performancën e kërkesave SQL kur ato janë miliona në ditë, dhe serverëve të kontrolluar PostgreSQL — qindra.
Cilat zgjidhje teknike na lejojnë të përballojmë një volum kaq të madh informacioni, dhe si e lehtësojnë jetën e zhvilluesve të zakonshëm.

Kush është i interesuar për analizën e problemeve të caktuara dhe teknikat e ndryshme të optimizimeve të kërkesave SQL dhe zgjidhjeve të detyrave tipike DBA në PostgreSQL — gjithashtu mund të për këtë temë.

Është një kënaqësi, unë quhem Kirill Borovikov, dhe përfaqësoj . Konkretisht, unë specializohem në punën me bazat e të dhënave në kompaninë tonë.
Sot do t'ju flas për mënyrat se si ne merremi me optimizimin e kërkesave, kur ju duhet të zgjidhni jo 'të gërmoni' performancën e një kërkese specifike, por të zgjidhni një problem masiv. Kur ka miliona kërkesa, dhe ju duhet të gjeni disa qasje për zgjidhjen e këtij problemi të madh.
Në të vërtetë, 'Tensor' për milionin tonë të klientëve është : një rrjet social korporativ, zgjidhje për video Thirrje, për qarkullimin e dokumenteve të brendshëm dhe të jashtëm, sisteme llogarie për kontabilitetin dhe magazinën,… Prandaj, një 'megakombajn' për menaxhimin kompleks të biznesit, ku ka më shumë se 100 projekte të brendshme të ndryshme.
Që të gjitha ato të funksionojnë normalisht dhe të zhvillohen — ne kemi 10 qendra zhvillimi në të gjithë vendin, në to janë më shumë se 1000 zhvillues.
Me PostgreSQL ne punojmë që nga viti 2008 dhe kemi grumbulluar një volum të madh të të dhënave që përpunojmë — këto janë të dhënat e klientëve, statistikore, analitike, të dhëna nga sisteme të jashtme informacioni — më shumë se 400TB.Vetëm në 'produksion' ka rreth 250 serverë, ndërsa në total serverët e DB që ne monitorojmë — janë rreth 1000.

SQL është një gjuhë deklarative. Ju përshkruani jo 'si' duhet të funksionojë diçka, por 'çfarë' dëshironi të merrni. DBMS e di më mirë si të bëjë JOIN — si të lidhë tabelat tuaja, cila kushte të aplikohet, çfarë do të shkojë përmes indeksit, çfarë jo…
Disa DBMS pranojnë sugjerime: 'Jo, këto dy tabela lidhen në këtë radhë', por PostgreSQL nuk e bën këtë. Kjo është një pozicion e vetëdijshme nga zhvilluesit kryesorë: 'Më mirë të përmirësojmë optimizuesin e kërkesës sesa të lejojmë zhvilluesit të përdorin ndonjë hidh.'
Por, përkundër faktit se PostgreSQL nuk lejon 'nga jashtë' të menaxhohet, ai lejon shkëlqyeshëm të shihni se çfarë ndodh 'brenda', kur ju ekzekutoni kërkesën tuaj, dhe ku ndodhin problemet.

Në të vërtetë, me cilat probleme klasike vijnë zakonisht zhvilluesit [tek DBA] ? 'Ne e ekzekutuan kërkesën dhe kemi ngadalësi, gjithçka është ngrirë, diçka po ndodh… Një problem!'
Arsyet pothuajse gjithmonë janë të njëjta:
- algoritmi i kërkesës është joefikas
Zhvilluesi: 'Tani unë po i bashkoj 10 tabela përmes JOIN…' — dhe pret që kushtet e tij magjikshëm të zgjidhen në mënyrë eficente, dhe ai do të marrë gjithçka shpejt. Por nuk ka mrekulli, dhe çdo sistem me një variabilitet të tillë (10 tabela në një FROM) gjithmonë do të japë ndonjëherë ndonjë devijim. [] - statistika të prishura
Moment shumë relevant për PostgreSQL, kur ju 'krahasoni' një dataset të madh në server, bëni një kërkesë — dhe ai 'skanon' tabelën. Sepse dje kishte 10 regjistra, e sot ka 10 milion, por PostgreSQL nuk është informuar akoma në lidhje me këtë, dhe duhet t'i japim informacionin. [] - "ndalese" për burimet
Ju keni vendosur një bazë të madhe dhe të ngarkuar në një server të dobët, i cili nuk ka hapësirë, memorie, performancë të procesorit të vet. Dhe gjithë… Diku ka një kufi të performancës, mbi të cilin nuk mund të kaloni. - bllokime
Një moment i komplikuar, por ato janë më të rendësishme për kërkesa të ndryshueshme (INSERT, UPDATE, DELETE) — kjo është një temë e madhe e veçantë.
Marrja e planit
… Dhe për gjithçka tjetër na duhet një plan! Na nevojitet të shohim se çfarë po ndodh brenda serverit.

Plani i ekzekutimit të kërkesës për PostgreSQL është një pemë e algoritmit të ekzekutimit të kërkesës në formën tekstuale. I atij algoritmi, i cili në fund të analizës nga planifikuesi është njohur si më efikas.
Çdo degë e pemës është një operacion: nxjerrja e të dhënave nga tabela ose indeksi, ndërtimi i një hapsire bitore, lidhja e dy tabelave, bashkimi, prerja ose përjashtimi i zgjedhjeve. Ekzekutimi i kërkesës — kalimi nëpër degët e kësaj peme.
Për të marrë planin e kërkesës, mënyra më e thjeshtë — është të ekzekutoni operatorin EXPLAIN. Për të marrë me të gjithë atributet reale, pra, për të ekzekutuar kërkesën në bazë — EXPLAIN (ANALYZE, BUFFERS) SELECT ....
Moment i keq: kur e kryen atë, ndodh "këtu dhe tani", prandaj është e përshtatshme vetëm për debugimin lokal. Nëse merrni ndonjë server me ngarkesë të lartë, që është nën një fluks të fortë ndryshimesh të të dhënave, dhe shihni: "Ah! Këtu kemi një kërkesë të ngadaltë,"ndodh kërkesa." Një gjysmë ore, një orë më parë - ndërsa ju po shkoni dhe e gjejmë këtë kërkesë në log, e merrni përsëri në server, tërë dataset-i dhe statistikat tuaja kanë ndryshuar. E kryeni atë për ta debuguar - dhe ajo ekzekutohet shpejt! Dhe nuk mund të kuptoni "pse", pse të ketë ngadalë.

Për të kuptuar se çfarë ndodhi në momentin kur kërkesa ekzekutohet në server, njerëzit e zgjuar shkruan . Ai është i pranishëm praktikisht në të gjitha shpërndarjet më të zakonshme të PostgreSQL dhe mund të aktivizohet lehtësisht në skedarin e konfigurimit.
Nëse kupton se një kërkesë po ekzekutohet përtej kufirit që i ke caktuar, ai bën "një snapshot" të planit të kësaj kërkese dhe e shkruan atë në log..

Duket se gjithçka tani është në rregull, shkojmë në log dhe shohim atje… [një sasi teksti]. Por nuk mund të themi asgjë për të, përveç faktit se ky është një plan i shkëlqyer, sepse u ekzekutua për 11ms.
Duket se gjithçka është mirë - por asgjë nuk është e qartë se çfarë po ndodhte në të vërtetë. Sepse të shikosh një "latuhë" plain text është mjaft e vështirë.
Por madje, pavarësisht se është e vështirë, ndihma është më e madhe:
- Në nod hyjnë shumat e burimeve të të gjithë nënpërgjigjeve poshtë tij. Kështu që nuk mund të dini se sa kohë u shpenzua konkretisht për këtë Index Scan - nuk mund të bëhet, nëse ka ndonjë kusht të brendshëm. Ne duhet të shikojmë dinamikisht, nëse ka "fëmijë" të brendshëm dhe variabla kusht, CTE - dhe të gjitha këto t'i heqim "në mendje".
- Momenti i dytë: koha që është e vendosur në nod është koha e ekzekutimit të një nodi të vetëm.Nëse ky nod është ekzekutuar si rezultat, për shembull, i një cikli për regjistrime të tabelës, disa herë, atëherë në plan rritet numri loops - ciklet e këtij nodi. Por vetë koha e ekzekutimit atomik mbetet e pandryshuar në plan. Kështu që për të kuptuar, sa herë është ekzekutuar ky nod në total, duhet të shumëzojmë ushqimin me tjetrin - përsëri "në mendje".
Në këto rrethana, të kuptosh "Cili është elementi më i dobët?" është praktikisht e pamundur. Prandaj edhe vetë zhvilluesit në "manual" shkruajnë se "Të kuptosh planin - është një art, që duhet të mësosh, një përvojë…".
Por ne kemi 1000 zhvillues dhe çdo njëri prej tyre nuk mund ta kalojë këtë përvojë në mendje. Unë, ti, ai - e dinë, por dikush atje - nuk e di. Ndoshta, do të mësojë, ndoshta jo, por duhet të punojë që tani - e nga ku do ta marrë këtë përvojë.
Vizualizimi i planit
Prandaj e kuptuam - për të adresuar këto probleme, na nevojitet një vizualizim i mirë i planit.

Ne filluam fillimisht "në treg" - le të shohim në internet se çfarë ekziston në të vërtetë.
Por, rezultoi se në lidhje me zgjidhjet "live", që janë më shumë apo më pak në zhvillim, janë shumë të pakta - në fakt, një: nga Hubert Lubaczewski. Në hyrjen e tij jepet një përmbledhje tekstuale e planit, ai të tregon një tabelë me të dhëna të analizuar:
- koha e vetme e ekzekutimit të nodit
- koha totale për të gjithë nënpërgjigjet
- numri i regjistrimeve që është nxjerrë dhe atë që pritej statistikisht
- trupi i vetë nodit
Gjithashtu, ky shërbim ka mundësinë për të ndarë arkiv të lidhjeve. Ti e heqe planin tënd atje dhe thua: "Hej, Pandi, ja një lidhje, atje është diçka që nuk shkon".

Por ka edhe disa probleme të vogla.
Së pari, një sasi e madhe "kopipaste". Ti merrni një copë logu, e vënë atje, dhe përsëri, e përsëri.
Së dyti, nuk ka analizë të sasisë së të dhënave të lexuara - ato buffers që nxjerr EXPLAIN (ANALYZE, BUFFERS), këtu ne nuk shohim. Ai thjesht nuk di t’i analizojë, të kuptojë dhe të punojë me to. Kur lexoni shumë të dhëna dhe kuptoni se mund të "shpërndaheni" gabimisht në disk dhe cache në memorie, kjo informacion është shumë e rëndësishme.
Momentet negative të tretë - zhvillimi shumë i dobët i këtij projekti. Komitetet janë shumë të vogla, mirë është nëse një herë në gjashtë muaj, dhe kodi është në Perl.

Por kjo është "lyrikë", me këtë mund të jetojmë ndonjëherë, por ka një gjë që na ka larguar shumë nga ky shërbim. Këto janë gabime në analizën e Common Table Expression (CTE) dhe nodet e ndryshme dinamike si InitPlan/SubPlan.
Nëse besojmë këtë figurë, atëherë koha totale e ekzekutimit të çdo nodi të veçantë është më shumë se koha totale e ekzekutimit të gjithë kërkesës. E thjeshtë - nga nodi CTE Scan nuk u hoq koha e krijimit të çdo CTE.Prandaj nuk e dimë më se sa kohë ka zgjatur vetë skanimi i CTE.

Këtu kuptuam se ishte koha për të shkruar tonën — urime! Çdo zhvillues thotë: «Tani ne do ta shkruajmë tonë, do të jetë shumë e lehtë!»
Merr një Stack tipik për shërbimet web: bërthama në Node.js + Express, u përgatit Bootstrap dhe për grafikë të bukura — D3.js. Dhe pritjet tona u përmbushën — prototipi i parë e morëm brenda 2 javëve:
- parserin e planit tonë
Kështu që tani mund të analizojmë çfarëdo plani nga ata që gjeneron PostgreSQL. - analizë e saktë e nyjeve dinamike — CTE Scan, InitPlan, SubPlan
- analizë e shpërndarjes së buffers — ku lexohen faqet e të dhënave nga memoria, ku nga cache lokal, ku nga disku
- të fituam qartësi
Për të mos e kërkuar të gjithë këtë në log, por ta shohim «nyjën më të dobët» menjëherë në figurë.

Ne morëm diçka si kjo — menjëherë me ndriçimin e sintaksës. Por zakonisht zhvilluesit tanë punojnë tashmë jo me një pamje të plotë të planit, por me atë që është më e shkurtër. Sepse të gjitha numrat i kemi analizuara dhe i kemi nxjerrë majtas e djathtas, dhe në mes kemi lënë vetëm rreshtin e parë, se çfarë lloj nyje është: CTE Scan, gjenerimi i CTE ose Seq Scan për ndonjë tabelë.
Këtë paraqitje të shkurtuar e quajmë shabllon plani.

Çfarë tjetër do të ishte e dobishme? Do të ishte mirë të shihnim se cila pjesë e kohës i alokohet çdo nyjeje nga koha totale — dhe thjesht e «ngjitëm» në anë grafikun pie.
Duke kaluar mbi nyjen dhe duke parë — mrekulli, kemi zbuluar se Seq Scan ka marrë më pak se një të katërtën e kohës totale, ndërsa ¾ e tjera kanë zënë CTE Scan. Paskë! Ky është një vërejtje e vogël lidhur me «shpejtësinë» e CTE Scan, nëse i përdorni aktivisht në kërkesat tuaja. Ata nuk janë shumë të shpejtë — humbasin madje edhe ndaj skanimit të zakonshëm të tabelave.
Por zakonisht grafiket e tilla janë më interesante, më të komplikuara, kur tregojmë menjëherë mbi segmentin dhe shohim, për shembull, se më shumë se gjysma e gjithë kohës e ka marrë ndonjë Seq Scan. Po ashtu, brenda atij segmenti ka qenë ndonjë Filter, një mori regjistrash të hequr… Mund ta dërgojmë këtë imazh direkt te zhvilluesi dhe t'i themi: «Vasya, këtu ka diçka që nuk shkon! Merre me mend, ka diçka të çuditshme!»

Sigurisht, pa «pengesa» nuk u kalua.
E para që «kurseva» — ishte problemi i përzgjedhjes. Koha e nyjës çdo të veçantë në plan tregohet me një saktësi deri në 1μs. Dhe kur numri i cikleve të nyjës kalon, për shembull, 1000 — pas ekzekutimit PostgreSQL e ndan «me saktësi deri», kështu që në rikthimin materializohet kohe totale «diku mes 0.95ms dhe 1.05ms». Kur llogaritja shkon mbi mikrosekonda — nuk është problem, por kur merret në [milisekonda] — kur është e nevojshme në «zgjidhjen» e burimeve sipas nyjeve të planit «kush ka konsumuar sa», duhet ta kemi parasysh këtë informacion.

Momenti i dytë, më i komplikuar, është shpërndarja e burimeve (ato buffers) mes nyjeve dinamike. Kjo na kushtoi katër javë shtesë mbi dy javët e para për prototipin.
Për të marrë një problem të tillë është mjaft e thjeshtë — krijojmë CTE dhe aty lexojmë diçka, sipas dukjes. Në të vërtetë, PostgreSQL është «inteligjent» dhe nuk do të lexojë asgjë direkt aty. Më pas ne marrim regjistrin e parë prej saj, dhe 101-in nga e njëjta CTE.

Shikojmë planin dhe kuptojmë - çuditërisht, kishim 3 buffers (faqet e të dhënave) që ishin «konsumuar» në Seq Scan, 1 tjetër në CTE Scan, dhe 2 në CTE Scan të dytë. Pra, nëse i shtojmë të gjithë, do të marrë 6, por me tabelën realisht lexuam vetëm 3! CTE Scan nuk lexon asgjë nga askund, por punon direkt me memorien e procesit. Kështu që këtu duket se diçka nuk shkon!
Në të vërtetë, rezulton se të gjitha ato 3 faqe të dhënash, që ishin kërkuar nga Seq Scan, fillimisht i kërkoi 1 CTE Scan, dhe më pas 2 CTE Scan, dhe atij i janë lexuar edhe 2 më shumë. Pra, gjithsej janë lexuar 3 faqe të dhënash, e jo 6.

Dhe kjo figurë na çoi në kuptimin se ekzekutimi i planit nuk është një pemë, por thjesht një grafik aciklik. Dhe ne kemi krijuar diçka si kjo grafikë, për të kuptuar «çfarë ka ardhur nga ku». Kështu që këtu krijuam CTE nga pg_class, dhe e kërkuam atë dy herë, dhe pothuajse gjithë koha kaloi për degën kur e kërkuam të dytën herë. E qartë, të lexosh regjistrin e 101-të është shumë më e shtrenjtë sesa thjesht të lexosh të parin nga tabelat.

Ne morëm frymë thellë për një çast. I thamë: «Tani, Neo, ti e di kung fu! Tani, përvoja jonë është për ty drejtpërdrejt në ekran. Tani mund ta përdorësh atë.»
Konsolidimi i logëve
1000 zhvilluesit tanë morën një frymë lehtësimi. Por ne e dinim se kishim vetëm qindra serverë 'luftarakë', dhe ky 'kopipast' nga ana e zhvilluesve nuk ishte aspak i përshtatshëm. Kuptuam se duhej ta mbledhim vetë.

Në të vërtetë, ekziston një modul standard që di të mbledhë statistikë, megjithatë, duhet gjithashtu ta aktivizosh atë në konfigurimin — kjo është . Por nuk na përmbushi.
Së pari, të njëjtave kërkesa në skema të ndryshme brenda të njëjtës bazë i jep ID të ndryshme Kërkese. Domethënë, nëse fillimisht bëjmë SET search_path = '01'; SELECT * FROM user LIMIT 1;, dhe pastaj SET search_path = '02'; dhe një kërkesë të ngjashme, atëherë në statistikat e këtij moduli do të jenë regjistrime të ndryshme, dhe nuk do t'i kem mundësitë për të mbledhur statistika të përgjithshme saktësisht në lidhje me këtë profil kërkese, pa marrë parasysh skemat.
Momenti i dytë, që na pengoi ta përdorim — mungesa e planit. Domethënë, nuk ka plan, vetëm kërkesa vetë. Ne shohim se çfarë e ngadalëson, por nuk kuptojmë se përse. Këtu kthehemi në problemin e dataset-it që ndryshon shpejt.
Dhe momenti i fundit — mungesa e “fakteve”. Domethënë, nuk mund të referoheni në një eksemplar të caktuar të ekzekutimit të kërkesës — nuk ka, ka vetëm statistika të agreguara. Me këtë, ndonëse mund të punojmë, është shumë e vështirë.

Prandaj ne vendosëm të luftojmë me “kopjimin dhe ngjitjen” dhe filluam të shkruajmë kolektorin.
. Kolektori lidhet përmes SSH, “tërheq” me ndihmën e një certifikate një lidhje të sigurt deri në serverin me bazën dhe tail -F vendoset në skedarin log. Kështu, në këtë seancë ne marrim një “pasqyrë” të plotë të gjithë skedarit log, që gjeneron serveri. Ngarkesa në vetë serverin është minimale, pasi ne nuk po e analizojmë atje, thjesht po pasqyrojmë trafik.
Pas asaj që filluam të shkruajmë ndërfaqen në Node.js, vazhduam ta zhvillojmë kolektorin në të njëjtën. Dhe kjo teknologji u tregua e suksesshme, sepse për të punuar me të dhëna tekstuale që nuk janë shumë të formatuara, siç është log-u, përdorimi i JavaScript është shumë i leverdisshëm. Dhe vetë infrastruktura Node.js si platformë backend lejon të punosh lehtë dhe me qetësi me lidhjet rrjetërore, po ashtu dhe me ndonjë rrjedhë të dhënash.
Për pasojë, ne “tërheqim” dy lidhje: e para, për të “dëgjuar” vetë log-un dhe për ta marrë atë, dhe e dyta — për të pyetur herë pas here bazën. “Ja, në log na erdhi se tabela me oid 123 është bllokuar”, por kjo nuk i thotë asgjë zhvilluesit, dhe do të ishte mirë të pyesnim bazën “Por çfarë është bashkërisht OID = 123?” Dhe kështu ne herë pas here pyesim bazën për atë që ende nuk e dimë.

“Vetëm një gjë nuk e ke marrë parasysh, ekziston tipi i bletëve si elefant!...” Ne filluam të zhvillonim këtë sistem kur dolem për të monitoruar 10 serverë. Më kritikët në kuptimin tonë, ku ishin ndonjë problem që ishte e vështirë të zgjidhnim. Por gjatë tremujorit të parë morëm për monitorim njëqind — sepse sistemi “u pranuar”, të gjithë donin, ishte gjithashtu i përshtatshëm për të gjithë.
I gjithë ky informacion duhet mbledhur, fluksi i të dhënave është i madh, aktiv. Në thelb, atë që monitorojmë, me çfarë dimë të merremi — atë përdorim. Po ashtu, përdorim PostgreSQL si depo të dhënash. Dhe asnjë gjë më të shpejtë, për të “derdhur” të dhënat në të, përveç operatorit COPY nuk ekziston akoma.
Por thjesht “derdhja” e të dhënave — nuk është teknologjia jonë. Sepse nëse keni njëqind serverë dhe ndodhin rreth 50m kërkesa në sekondë, atëherë kjo do t'ju gjenerojë 100-150GB log-e në ditë. Prandaj na duhej të “punonim” bazën me kujdes.
Së pari, ne realizuam seksionimin sipas ditëve, sepse, në thelb, askujt nuk i intereson korrelacioni ndërmjet ditëve. Çfarë rëndësie ka çfarë ndodhi dje, nëse sonte në mesnatë e lëshuat një version të ri të aplikacionit — dhe tani keni një statistikë të re.
Së dyti, ne mësuam (ishim të detyruar të) shkruajmë shumë-shumë shpejt me ndihmën e COPY. Domethënë, jo thjesht COPY, sepse ai është më i shpejtë se INSERT, por edhe më shpejt.

Momenti i tretë — u detyruam të heqim dorë nga trigjerët, si dhe nga Foreign Keys. Domethënë, nuk kemi fare integritet referencial. Sepse nëse keni një tabelë me një çift FK, dhe i thoni në strukturën e DB-së se “ja, një regjistrim në log referohet me FK, p.sh., në një grup regjistrimesh”, kur e futni atë, PostgreSQL nuk ka asgjë tjetër përveçse të marrë dhe të zbatojë me ndershmëri SELECT 1 FROM master_fk1_table WHERE ... me atë identifikator që po përpiqeni të futni — thjesht për të kontrolluar se ky regjistrim është aty, që nuk po “prishni” referencën tuaj me këtë Foreign Key.
Kështu, ne marrim përveç një regjistrimi në tabelën e synuar dhe indiket e saj, edhe lexime nga të gjitha tabelat në të cilat ajo referon. Dhe për ne, kjo nuk është aspak e nevojshme — objekti ynë është të shkruajmë sa më shumë dhe sa më shpejt me ngarkesë minimale. Prandaj, FK — jashtë!
Momenti tjetër — agregimi dhe hashi. Fillimisht ata ishin implementuar në DB — sepse është e përshtatshme që menjëherë, kur vjen një regjistrim, të bëni në ndonjë tabelë "+ një" drejtpërdrejt në trigger.. Mirë, e rehatshme, por e keqe në të njëjtën kohë — vendosni një shënim, por jeni të detyruar të lexoni dhe shënoni diçka tjetër nga një tavël tjetër. Dhe jo vetëm që duhet ta lexoni dhe shënoni — duhet ta bëni këtë çdo herë.
Tani imagjinoni se keni një tavël, në të cilën thjesht llogaritni numrin e kërkesave që kanë kaluar nëpër një host të caktuar: +1, +1, +1, ..., +1. Dhe kjo në parim nuk ju nevojitet — gjithçka mund të shumohet në memorie në kolektor dhe të dërgohet në bazë një herë +10.
Po, në rast ndonjë problemi, e mundshme që logjika të "rrëzohet", por kjo është praktikisht një rast i pamundur — sepse keni një server normal, ka një bateri në kontrollues, keni një regjistër transaksionesh, një regjistër në sistemin e skedarëve… Në tërësi, nuk e justifikon. Nuk justifikohet ajo humbje performancë që keni për shkak të punës së trigerëve/FK, ato shpenzime që keni gjatë kësaj.
Po ashtu është edhe me hashimin. Një kërkesë vjen te ju, ju llogaritni një identifikues në DB, e shkruani në bazë dhe më pas e thoni të gjithëve. Gjithçka është mirë derisa në momentin e shkrimit të vijë një tjetër që dëshiron ta shkruajë atë — dhe ju do të keni një bllokim, dhe kjo është e keqe. Prandaj, nëse mund ta transferoni gjenerimin e disa ID-ve në klient (relativisht me bazën), është më mirë ta bëni këtë.
Na i përshtatet për mrekulli të përdorim MD5 të tekstit — kërkesës, planit, modelit,… Ne e llogarisim atë në anën e kolektorit, dhe "derdhim" në bazë një ID të gatshme. Gjatësia e MD5 dhe ndarjet ditore na lejojnë të mos shqetësohemi për kolizionet e mundshme.

Por që të shkruajmë gjithçka me shpejtësi, na nevojitet të modifikojmë procedurën e shkrimit.
Si zakonisht shkruhen të dhënat? Ne kemi një dataset, e ndajmë atë në disa tabela, dhe pastaj COPY — së pari në tabelën e parë, pastaj në të dytën, në të tretën… Jo e rehatshme, sepse duket se ne po shkruajmë një stream të dhënash në tri hapa radhazi. E pakëndshme. A mund të bëjmë më shpejt? Po!
Për këtë mjafton të ndajmë këto flukse paralelisht me njëri-tjetrin. Pra, ndodhin në flukse të veçanta gabime, kërkesa, modele, bllokime,… — dhe ne i shkruajmë të gjitha paralelisht. Për këtë nevojitet të mbajmë vazhdimisht të hapur një kanali COPY për çdo tabelë të veçantë.

Domethënë kolektori ka gjithmonë një stream, në të cilin mund të shkruaj të dhënat që më duhen. Por që baza të shohë këto të dhëna, dhe ndokush të mos ngelet në bllokime duke pritur që të dhënat të shkruhen, COPY duhet të ndërpritet me një periudhë të caktuar. Për ne periudha më e efektshme doli të ishte rreth 100 ms — e mbyllim dhe menjëherë e hapim përsëri në të njëjtën tabelë. Po nëse një stream nuk mjafton gjatë ndonjë pika, atëherë ne bëjmë puli deri në një kufi të caktuar.
Për më tepër, ne zbuluam se për këtë profil ngarkese çdo grumbullim, kur shënimet mblidhen në paketa — është e keqe. E keqja klasike është INSERT ... VALUES dhe pastaj 1000 shënime. Sepse në këtë moment ju keni një kulm shkrimi në medium, dhe të gjithë të tjerët që përpiqen të shkruajnë në disk do të presin.
Për t'u shkëputur nga këto anomali, thjesht mos grumbulloni asgjë, mos e buferizoni fare. Dhe nëse buferizimi në disk gjithsesi ndodh (me fat, Stream API në Node.js e lejon që këtë ta zbulojë) — shtyni këtë lidhje. Disa gjë që do të merrni përmes ndonjë ngjarjeje që është e lirë përsëri — shkruani në të nga queu e akumuluar. Ndërkohë që është e zënë — merrni nga puli atë që është tërësisht i lirë dhe shkruani në të.
Para implementimit të këtij qasjeje për shkrimin e të dhënave kishim rreth 4K write ops, dhe në këtë mënyrë e kemi reduktuar ngarkesën në 4 herë. Tani kemi rritur edhe 6 herë për shkak të bazave të reja të vëzhguara — deri në 100MB/s. Dhe tani ruajmë log të muajve të fundit 3 në një volum rreth 10-15TB, duke shpresuar se mund të zgjidhim çdo problem për çdo zhvillues në tri muaj.
E kuptojmë problemet
Por thjesht të mbledhësh të gjitha këto të dhëna - është mirë, e dobishme, e përshtatshme, por e pakët - ato duhet kuptuar. Sepse janë miliona plane të ndryshme në ditë.

Por miliona — janë të pakontrollueshme, duhet fillimisht të bëni "më pak". Dhe, në radhë të parë, duhet të vendosni se si do ta organizoni këtë "më pak".
Ne kemi identifikuar tri pika kyçe:
- kush kjo kërkesë e dërgoi
Domethënë, nga cili aplikacion erdhi: ndërfaqja web, backend, sistemi i pagesave apo diçka tjetër. - ku kjo ndodhi
Në cilin server konkret. Sepse nëse keni disa serverë nën një aplikacion, dhe papritur një "ngërçi" (sepse "disku dështoi", "memoria rrjedh", ndonjë fatkeqësi tjetër), atëherë duhet të adresoheni konkretisht në server. - si shfaqja e problemit ishte në atë ose atë plan
Për të kuptuar "kë ka dërguar" kërkesën tek ne, ne përdorim një mjet standard - vendosjen e një variabli sesioni: SET application_name = '{bl-host}:{bl-method}'; — ruajmë emrin e hostit të logjikës së biznesit, nga i cili vjen kërkesa, dhe emrin e metodës ose aplikacionit që e nisi atë.
Pas kalimit të "pronarit" të kërkesës, e duhet të shkruhet në log - për këtë konfiguroni variablën log_line_prefix = ' %m [%p:%v] [%d] %r %a'. Ata që janë të interesuar, mund , çfarë do të thotë gjithçka kjo.Rezultohet se ne në log shohim:
- koha
- identifikuesit e procesit dhe transaksionit
- emrin e bazës
- IP-në e atij që dërgoi këtë kërkesë
- dhe emrin e metodës

Më tej kuptuam se nuk është shumë interesante të shikosh korrelacionin për një kërkesë midis serverëve të ndryshëm. Është e rrallë që ndodh situata kur keni një aplikacion që "dështoni" në të dyja anët. Por edhe nëse është e njëjtë - shikoni në ndonjë nga këta serverë.
Pra, prerja "një server - një ditë" na duket e përmirësuar për çdo analizë.
Prerja e parë analitike - është ai i njohur "shabllon" — forma e shkurtuar e paraqitjes së planit, e pastruar nga të gjitha treguesit numerikë. Prerja e dytë - aplikacioni ose metoda, dhe e treta - është nyja specifike e planit që na shkaktoi probleme.
Kur kaluam nga ekzemplarët konkret në shabllone, morëm menjëherë dy avantazhe:
- përmbledhje e numrit të objekteve për analizë
Duhet të shqyrtojmë problemin jo sipas mijëra kërkesave ose planeve, por sipas dhjetëra shablloneve. - të përshkruara më mirë
Pra, përmbledh të "faktet" brenda ndonjë prerjeje, mund të tregohet shfaqja e tyre gjatë ditës. Këtu mund të kuptoni se nëse ndonjë shabllon ndodh, për shembull, një herë në orë, e duhet të ndodhë një herë në ditë, është e arsyeshme të mendoni se çfarë ka shkuar keq - kush dhe përse e ka iniciuar atë, ndoshta nuk duhet të jetë këtu. Ky është një tjetër mënyrë analize që është vizuale, jo numerike.

Mënyrat e tjera bazohen në ato tregues që ne nxjerrim nga plani: sa herë ndodhi ky shabllon, koha totale dhe mesatare, sa të dhëna janë lexuar nga disku, dhe sa nga memoria...
Sepse, për shembull, ju vini në faqen e analizës për hostin, shikoni - diçka ka filluar të lexojë shumë nga disku. Disku në server nuk po përballon - dhe kush e lexon nga ai?
Dhe mund të renditni sipas çdo kolone dhe të vendosni se me çfarë do të filloni të shqyrtoni - me ngarkesën e procesorit apo të disku, ose me numrin total të kërkesave... Renditët, shikoni "të preferuarit", rregulloni - nxjerrni një version të ri të aplikacionit.
Dhe menjëherë mund të shihni aplikacione të ndryshme që shkojnë me të njëjtin shabllon nga kërkesa e tipit SELECT * FROM users WHERE login = 'Vasya'. Frontend, backend, procesim... Dhe ju mendoni se përse procesimi po lexon përdoruesin, nëse ai nuk po ndërvepron me të.
Mënyra e kundërt - nga aplikacioni menjëherë të shihni se çfarë po bën. Për shembull, frontend - kjo, kjo, kjo, dhe gjithashtu kjo një herë në orë (pikërisht kjo ndihmon për ndihmën e kohëzgjatjes). Dhe menjëherë shfaqet pyetja - duket se nuk është detyra e frontend-it të bëjë diçka një herë në orë...

Pas një kohe, kuptuam se na mungon statistika e agreguar në prerjen e nyjave të planit.Ne nxorrëm nga planet vetëm ato nyja që bëjnë diçka me të dhënat e tabelave vetë (i lexojnë/shkruajnë ato sipas indeksit apo jo). Në thelb, në lidhje me imazhin e mëparshëm, shtohet vetëm një aspekt - sa regjistrime ky nyje na sjell, dhe sa ka hequr (Rows Removed by Filter).
Nuk keni një indeks të përshtatshëm në tabelë, ju bëni një kërkesë ndaj saj, ajo kalon pa indeksin, bie në Seq Scan... të gjitha regjistrimet, përveç një, i keni filtruar. Pse keni 100M regjistrime të filtruar për një ditë, a nuk është më mirë ta vendosni një indeks?

Pas shqyrtimit të të gjitha planeve sipas nyjave, kuptuam se ka disa struktura tipike në plane, të cilat me shumë probabilitet duken të dyshimta. Dhe do të ishte mirë t'i dërgosh zhvilluesit një mesazh: "Miku, këtu lexon fillimisht sipas indeksit, pastaj rendit e më pas filtron" - zakonisht, aty ka një regjistrim.
Të gjithë ata që shkruan kërkesa, me këtë model, me siguri janë përballur: "Më jep porosinë më të fundit për Vasën, datën e saj" Dhe nëse nuk keni indeks mbi datën, ose në indeksin e përdorur nuk ka datën, do të bëni saktësisht mbi këto "grata".
Por ne e dimë se këto janë "grata" - kështu që pse mos t'i sugjerojmë menjëherë zhvilluesit se çfarë duhet të bëjë. Prandaj, duke hapur planin tani, zhvilluesi ynë menjëherë sheh një pamje të bukur me sugjerime, ku i thonë menjëherë: "Ke probleme këtu dhe këtu, dhe ato zgjidhen në këtë mënyrë dhe në atë mënyrë."
Si pasojë, sasia e atij eksperiencës që ishte e nevojshme për të zgjidhur problemet në fillim dhe tani, ka rënë shumë. Ky është mjeti që kemi arritur.

Burimi: habr.com
